diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 218d549..5805c53 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -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) ## πŸ›οΈ Modernization: Actor/Queue Model & Monitoring diff --git a/package.json b/package.json index 7117e8f..49ae05f 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.3", + "version": "2.33.4", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", @@ -100,6 +100,76 @@ "default": true, "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": { "type": "string", "default": "http://127.0.0.1:11434", diff --git a/src/agent.ts b/src/agent.ts index 3f7be63..c68c665 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -298,12 +298,18 @@ export class AgentExecutor { ? `\n\n[CRITICAL: INTERNET ACCESS ENABLED]\nYou can use 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 ? `\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[] = [ { role: 'system', content: fullSystemPrompt, internal: true }, ...reqMessages @@ -558,17 +564,23 @@ export class AgentExecutor { 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( prompt, modelName, - brainContext, + `${brainContext}${selectedAgentContext}`, signal, (step, msg) => { 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; @@ -917,6 +929,91 @@ export class AgentExecutor { 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('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() { if (!this.historyChangeListener) return; diff --git a/src/agents/AgentWorkflowManager.ts b/src/agents/AgentWorkflowManager.ts index cd70732..5b56052 100644 --- a/src/agents/AgentWorkflowManager.ts +++ b/src/agents/AgentWorkflowManager.ts @@ -1,6 +1,5 @@ -import * as vscode from 'vscode'; -import { PlannerAgent, ResearcherAgent, WriterAgent } from './factory'; -import { AgentEngine, PipelineStage } from '../lib/engine'; +import { ConfigurableWorkflowAgent } from './factory'; +import { DEFAULT_MULTI_AGENT_WORKFLOW, MultiAgentStageConfig } from '../config'; export class AgentWorkflowManager { /** @@ -11,33 +10,28 @@ export class AgentWorkflowManager { modelName: string, brainContext: string, signal: AbortSignal, - onProgress: (step: string, message: string) => void + onProgress: (step: string, message: string) => void, + workflow: MultiAgentStageConfig[] = DEFAULT_MULTI_AGENT_WORKFLOW ): Promise { - - // 1. μ—μ΄μ „νŠΈ μ€€λΉ„ (DIλ₯Ό μœ„ν•œ μΈμŠ€ν„΄μŠ€ν™”) - 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()}`; - + const stages = workflow.length > 0 ? workflow : DEFAULT_MULTI_AGENT_WORKFLOW; + const stageOutputs: string[] = []; try { - // 4. 엔진을 ν†΅ν•œ λ―Έμ…˜ μ‹€ν–‰ (Producer-Consumer & Mutex 적용) - return await engine.runMission( - missionId, - prompt, - brainContext, - signal, - (stage: PipelineStage, message: string) => { - // UI ν”Όλ“œλ°±μ„ μœ„ν•œ ν”„λ‘œκ·Έλ ˆμŠ€ μ—…λ°μ΄νŠΈ - const uiStepName = this.mapStageToUI(stage); - onProgress(uiStepName, message); - } - ); + 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]; } catch (error: any) { if (error.name === 'AbortError' || error.message.includes('cancelled')) { throw error; @@ -46,18 +40,28 @@ export class AgentWorkflowManager { } } - /** - * μ—”μ§„ μŠ€ν…Œμ΄μ§€λ₯Ό UI ν‘œμ‹œμš© λͺ…μΉ­μœΌλ‘œ λ§€ν•‘ - */ - private static mapStageToUI(stage: PipelineStage): string { - const maps: Record = { - idle: 'λŒ€κΈ°', - planner: 'Planner', - researcher: 'Researcher', - writer: 'Writer', - completed: 'μ™„λ£Œ', - error: '였λ₯˜' - }; - return maps[stage] || 'μ§„ν–‰ 쀑'; + 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.`); + } } } diff --git a/src/agents/factory.ts b/src/agents/factory.ts index b46c33a..d0d200e 100644 --- a/src/agents/factory.ts +++ b/src/agents/factory.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { getConfig } from '../config'; +import { MultiAgentStageConfig, getConfig } from '../config'; export abstract class BaseAgent { 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); } } + +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 1c8b668..a9ea6c6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,6 +12,15 @@ 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; @@ -26,8 +35,46 @@ 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; @@ -50,6 +97,21 @@ 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'); @@ -58,9 +120,13 @@ 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'); @@ -99,7 +165,12 @@ export function getConfig(): IAgentConfig { maxContextSize: cfg.get('maxContextSize', 12000), maxAutoSteps: cfg.get('maxAutoSteps', 50), dryRun: cfg.get('dryRun', false), - multiAgentEnabled: cfg.get('multiAgentEnabled', true) + multiAgentEnabled: cfg.get('multiAgentEnabled', true), + 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)), + memoryLongTermFiles: Math.max(0, cfg.get('memoryLongTermFiles', 6)) }; } @@ -154,4 +225,4 @@ export const EXCLUDED_DIRS = new Set([ 'node_modules', '.git', '.vscode', 'out', 'dist', 'build', '.next', '.cache', '__pycache__', '.DS_Store', 'coverage', '.turbo', '.nuxt', '.output', 'vendor', 'target' -]); \ No newline at end of file +]); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index e576a18..b50632c 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -1198,7 +1198,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn display: flex; align-items: center; justify-content: space-between; - padding: 10px 12px 8px; + padding: 10px 12px 6px; gap: 10px; } @@ -1210,7 +1210,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn } .header-actions, - .tool-strip, .tool-group, .select-stack, .select-line, @@ -1219,8 +1218,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn align-items: center; } - .header-actions { gap: 6px; flex-shrink: 0; } - .tool-strip { gap: 6px; flex-wrap: wrap; } + .header-actions { gap: 6px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; } .tool-group { gap: 4px; padding: 3px; @@ -1230,6 +1228,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn } .select-stack { flex-direction: column; gap: 6px; 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 { display: grid; grid-template-columns: minmax(0, 1fr) auto; @@ -1237,12 +1242,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn width: 100%; 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; } .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); } + .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); } } .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; } /* Tooltip System */ @@ -1309,10 +1317,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn .msg-body { - padding-left: 30px; color: var(--text-primary); font-size: 13.5px; word-break: break-word; + max-width: min(88%, 760px); } .msg-user .msg-body { @@ -1320,8 +1328,14 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn border: 1px solid var(--border); border-radius: 12px; padding: 10px 14px; - margin-left: 30px; white-space: pre-wrap; + text-align: left; + } + + .msg-ai .msg-body { + padding-left: 30px; + width: 100%; + max-width: 100%; } /* --- Markdown Style --- */ @@ -1367,8 +1381,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn background: var(--control-bg); border: 1px solid var(--border); color: var(--text-dim); - width: 28px; + min-width: 28px; height: 28px; + padding: 0 8px; border-radius: 6px; display: inline-flex; align-items: center; @@ -1378,7 +1393,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn font-size: 12px; font-weight: 700; 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.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) { .header-controls { - grid-template-columns: minmax(0, 1fr) auto; + grid-template-columns: minmax(0, 1fr); align-items: start; } } + @media (max-width: 520px) { + .paired-row { + grid-template-columns: minmax(0, 1fr); + } + .header-top { + align-items: flex-start; + } + } @keyframes slideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } @@ -1739,8 +1762,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
G1nation
- - + + + + + +
@@ -1749,29 +1776,25 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
Engine
-
-
-
- - - - +
+
+
+
+ + + + +
-
-
-
-
- - - +
+
+
+ + + +
-
- - - - -