diff --git a/package.json b/package.json index 5fd35db..23d5f10 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "connect-ai-lab", - "displayName": "Connect AI", - "description": "100% 로컬 AI 코딩 에이전트 — 파일 생성, 코드 편집, 터미널 실행을 오프라인으로. Ollama + Gemma/Llama/DeepSeek 지원.", + "name": "g1nation", + "displayName": "G1nation", + "description": "100% local AI coding agent for VS Code. Create files, edit code, run commands, and work offline with Ollama or LM Studio.", "version": "2.2.15", "publisher": "connectailab", "license": "MIT", @@ -28,40 +28,38 @@ "offline", "agent", "code-generation", - "connect-ai-lab", + "g1nation", "copilot" ], - "activationEvents": [ - "*" - ], + "activationEvents": [], "main": "./out/extension.js", "contributes": { "commands": [ { - "command": "connect-ai-lab.newChat", - "title": "Connect AI: New Chat", + "command": "g1nation.newChat", + "title": "G1nation: New Chat", "icon": "$(add)" }, { - "command": "connect-ai-lab.exportChat", - "title": "Connect AI: Export Chat as Markdown" + "command": "g1nation.exportChat", + "title": "G1nation: Export Chat as Markdown" }, { - "command": "connect-ai-lab.explainSelection", - "title": "Connect AI: Explain Selected Code" + "command": "g1nation.explainSelection", + "title": "G1nation: Explain Selected Code" }, { - "command": "connect-ai-lab.focusChat", - "title": "Connect AI: Focus Chat Input" + "command": "g1nation.focusChat", + "title": "G1nation: Focus Chat Input" }, { - "command": "connect-ai-lab.showBrainNetwork", - "title": "Connect AI: Show Brain Topology 🧠" + "command": "g1nation.showBrainNetwork", + "title": "G1nation: Show Brain Topology" } ], "keybindings": [ { - "command": "connect-ai-lab.focusChat", + "command": "g1nation.focusChat", "key": "cmd+l", "mac": "cmd+l" } @@ -69,7 +67,7 @@ "menus": { "editor/context": [ { - "command": "connect-ai-lab.explainSelection", + "command": "g1nation.explainSelection", "when": "editorHasSelection", "group": "1_modification" } @@ -78,43 +76,54 @@ "viewsContainers": { "activitybar": [ { - "id": "connect-ai-lab-sidebar", - "title": "Connect AI", + "id": "g1nation-sidebar", + "title": "G1nation", "icon": "$(hubot)" } ] }, "views": { - "connect-ai-lab-sidebar": [ + "g1nation-sidebar": [ { "type": "webview", - "id": "connect-ai-lab-v2-view", - "name": "Chat" + "id": "g1nation-v2-view", + "name": "Chat", + "icon": "assets/icon.png" } ] }, "configuration": { - "title": "Connect AI", + "title": "G1nation", "properties": { - "connectAiLab.ollamaUrl": { + "g1nation.ollamaUrl": { "type": "string", "default": "http://127.0.0.1:11434", - "description": "🤖 AI 서버 주소 (보통 자동으로 잡히니 건드리지 않아도 됩니다)" + "description": "Base URL for Ollama or LM Studio. Default: http://127.0.0.1:11434" }, - "connectAiLab.defaultModel": { + "g1nation.defaultModel": { "type": "string", "default": "gemma4:e2b", - "description": "🧠 사용할 AI 모델 이름 (예: gemma4:e2b, llama3.3, deepseek-r1)" + "description": "Default model name to use for chat requests." }, - "connectAiLab.requestTimeout": { + "g1nation.requestTimeout": { "type": "number", "default": 300, - "description": "⏱ AI 응답 대기 시간 (초, 기본 300초 = 5분)" + "description": "Request timeout in seconds. Default: 300" }, - "connectAiLab.localBrainPath": { + "g1nation.localBrainPath": { "type": "string", "default": "", - "description": "📁 내 지식 폴더 경로 — 내 PC의 특정 폴더(예: 바탕화면/MyBrain)를 지정하면, 그 안의 .md 파일들이 AI의 지식이 됩니다. 비워두면 기본 폴더를 자동 생성합니다." + "description": "Folder path for your local Second Brain knowledge base. Leave empty to use the default folder." + }, + "g1nation.secondBrainRepo": { + "type": "string", + "default": "", + "description": "Optional GitHub repository URL used for Second Brain sync." + }, + "g1nation.autoPushBrain": { + "type": "boolean", + "default": false, + "description": "Automatically commit and push Second Brain changes after updates." } } } diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..2e0b959 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,101 @@ +/** + * ============================================================ + * Centralized Configuration (중앙 집중식 설정 관리) + * + * 모든 환경 설정(모델 이름, API 엔드포인트, 타임아웃, 보안 정책 등) + * 을 한 곳에서 관리합니다. Single Source of Truth 원칙 적용. + * ============================================================ + */ + +import * as vscode from 'vscode'; +import * as os from 'os'; +import * as path from 'path'; + +// ─── VS Code 설정에서 읽어오는 값 ─── +export function getConfig(): IAgentConfig { + const cfg = vscode.workspace.getConfiguration('g1nation'); + return { + ollamaBase: cfg.get('ollamaUrl', 'http://127.0.0.1:11434'), + defaultModel: cfg.get('defaultModel', 'gemma4:e2b'), + maxTreeFiles: cfg.get('maxTreeFiles', 200), + timeout: cfg.get('requestTimeout', 300) * 1000, + localBrainPath: cfg.get('localBrainPath', '') + }; +} + +// ─── 에이전트 설정 인터페이스 ─── +export interface IAgentConfig { + ollamaBase: string; + defaultModel: string; + maxTreeFiles: number; + timeout: number; + localBrainPath: string; +} + +// ─── 두뇌 폴더 경로 유틸리티 ─── +export function getBrainDir(): string { + const { localBrainPath } = getConfig(); + if (localBrainPath && localBrainPath.trim() !== '') { + if (localBrainPath.startsWith('~/')) { + return path.join(os.homedir(), localBrainPath.substring(2)); + } + return localBrainPath.trim(); + } + return path.join(os.homedir(), '.g1nation-brain'); +} + +export function isBrainDirExplicitlySet(): boolean { + const { localBrainPath } = getConfig(); + return !!(localBrainPath && localBrainPath.trim() !== ''); +} + +// ─── 보안 정책 (Security Policy) ─── +export const SECURITY_POLICY = { + // 허용된 터미널 명령어 프리픽스 (Whitelist) + allowedCommandPrefixes: [ + 'npm', 'yarn', 'pnpm', 'npx', + 'node', 'ts-node', + 'git', + 'python', 'python3', 'pip', 'pip3', + 'docker', 'docker-compose', + 'ls', 'dir', 'cat', 'type', + 'echo', 'print', + 'cargo', 'go', 'rustc', + 'java', 'javac', 'mvn', 'gradle', + 'flutter', 'dart', 'pub', + 'webpack', 'vite', 'esbuild', 'parcel', + 'jest', 'mocha', 'vitest', 'cypress', + 'tsc', 'vue-tsc', + ], + + // 절대 실행 금지 명령어 (Blacklist) + forbiddenCommands: [ + 'rm -rf', 'rm-rf', 'del /f', 'format', + 'mkfs', 'dd if=', ':(){ :|:& };:', + 'wget http', 'curl http', 'sudo', + 'chmod 777', 'chown root', + ], + + // 민감한 파일 패턴 (파일 생성/수정 시 경고) + sensitiveFilePatterns: [ + '.env', '.env.*', + 'id_rsa', 'id_ed25519', + '.gitconfig', '.npmrc', '.pypirc', + 'credentials.json', 'service-account.json', + ], + + // 파일 생성 시 최대 크기 (bytes) + maxFileSize: 10 * 1024 * 1024, // 10MB + + // 맥락 파일 최대 개수 + maxContextFiles: 200, +}; + +// ─── 시스템 프롬프트 상수 ─── +export const MAX_CONTEXT_SIZE = 12_000; + +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/extension.ts b/src/extension.ts index fd9a4d0..1053070 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,4 +1,4 @@ -import * as vscode from 'vscode'; +import * as vscode from 'vscode'; import * as http from 'http'; import axios from 'axios'; import * as fs from 'fs'; @@ -10,13 +10,13 @@ import { promisify } from 'util'; const execAsync = promisify(exec); // ============================================================ -// Connect AI — Full Agentic Local AI for VS Code -// 100% Offline · File Create · File Edit · Terminal · Multi-file Context +// G1nation ??Full Agentic Local AI for VS Code +// 100% Offline 鸚?File Create 鸚?File Edit 鸚?Terminal 鸚?Multi-file Context // ============================================================ // Settings are read from VS Code configuration (File > Preferences > Settings) function getConfig() { - const cfg = vscode.workspace.getConfiguration('connectAiLab'); + const cfg = vscode.workspace.getConfiguration('g1nation'); return { ollamaBase: cfg.get('ollamaUrl', 'http://127.0.0.1:11434'), defaultModel: cfg.get('defaultModel', 'gemma4:e2b'), @@ -26,6 +26,14 @@ function getConfig() { }; } +function getSecondBrainRepo(): string { + return vscode.workspace.getConfiguration('g1nation').get('secondBrainRepo', '').trim(); +} + +function shouldAutoPushBrain(): boolean { + return vscode.workspace.getConfiguration('g1nation').get('autoPushBrain', false); +} + function _getBrainDir(): string { const { localBrainPath } = getConfig(); if (localBrainPath && localBrainPath.trim() !== '') { @@ -34,7 +42,7 @@ function _getBrainDir(): string { } return localBrainPath.trim(); } - return path.join(os.homedir(), '.connect-ai-brain'); + return path.join(os.homedir(), '.g1nation-brain'); } function _isBrainDirExplicitlySet(): boolean { @@ -46,44 +54,72 @@ async function _ensureBrainDir(): Promise { if (_isBrainDirExplicitlySet()) { return _getBrainDir(); } - // 폴더 미설정 → 사용자에게 강제 선택 요청 + + const selectedFolders = await vscode.window.showOpenDialog({ + canSelectFolders: true, + canSelectFiles: false, + canSelectMany: false, + openLabel: '?대뜑 ?좏깮', + title: 'G1nation Second Brain ?대뜑 ?ㅼ젙' + }); + if (selectedFolders && selectedFolders.length > 0) { + const selectedPath = selectedFolders[0].fsPath; + await vscode.workspace.getConfiguration('g1nation').update('localBrainPath', selectedPath, vscode.ConfigurationTarget.Global); + vscode.window.showInformationMessage(`Second Brain ?대뜑媛€ ?ㅼ젙?섏뿀?듬땲?? ${selectedPath}`); + return selectedPath; + } + const result = await vscode.window.showInformationMessage( - '📁 지식을 저장할 폴더를 먼저 선택해주세요! (내 지식이 저장될 곳입니다)', - '폴더 선택하기' + '?대뜑媛€ ?좏깮?섏? ?딆븯?듬땲?? ?ㅼ떆 ?좏깮?섏떆寃좎뒿?덇퉴?', + '?대뜑 ?좏깮' ); - if (result !== '폴더 선택하기') return null; + if (result !== '?대뜑 ?좏깮') return null; const folders = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, - openLabel: '이 폴더를 내 지식 폴더로 사용', - title: '🧠 내 지식 폴더 선택' + openLabel: '?대뜑 ?좏깮', + title: 'Second Brain ?대뜑 ?ㅼ젙' }); if (!folders || folders.length === 0) return null; const selectedPath = folders[0].fsPath; - await vscode.workspace.getConfiguration('connectAiLab').update('localBrainPath', selectedPath, vscode.ConfigurationTarget.Global); - vscode.window.showInformationMessage(`✅ 지식 폴더가 설정되었습니다: ${selectedPath}`); + await vscode.workspace.getConfiguration('g1nation').update('localBrainPath', selectedPath, vscode.ConfigurationTarget.Global); + vscode.window.showInformationMessage(`Second Brain ?대뜑媛€ ?ㅼ젙?섏뿀?듬땲?? ${selectedPath}`); return selectedPath; } +function isTextAttachment(fileName: string, mimeType: string): boolean { + const lower = fileName.toLowerCase(); + const textExtensions = [ + '.txt', '.md', '.csv', '.json', '.js', '.ts', '.jsx', '.tsx', + '.html', '.css', '.py', '.java', '.rs', '.go', '.yaml', '.yml', + '.xml', '.toml', '.sql', '.sh' + ]; + + return mimeType.startsWith('text/') + || mimeType === 'application/json' + || textExtensions.some((ext) => lower.endsWith(ext)); +} + const EXCLUDED_DIRS = new Set([ 'node_modules', '.git', '.vscode', 'out', 'dist', 'build', '.next', '.cache', '__pycache__', '.DS_Store', 'coverage', '.turbo', '.nuxt', '.output', 'vendor', 'target' ]); const MAX_CONTEXT_SIZE = 12_000; // chars +const MAX_AUTO_AGENT_STEPS = 50; -const SYSTEM_PROMPT = `You are "Connect AI", a premium agentic AI coding assistant running 100% offline on the user's machine. -You are DIRECTLY CONNECTED to the user's local file system and terminal. You MUST use the action tags below to create, edit, delete, read files and run commands. DO NOT just show code — ALWAYS wrap it in the appropriate action tag so it gets executed. +const SYSTEM_PROMPT = `You are "G1nation", a premium agentic AI coding assistant running 100% offline on the user's machine. +You are DIRECTLY CONNECTED to the user's local file system and terminal. You MUST use the action tags below to create, edit, delete, read files and run commands. DO NOT just show code ??ALWAYS wrap it in the appropriate action tag so it gets executed. You have SEVEN powerful agent actions: -━━━ ACTION 1: CREATE NEW FILES ━━━ + [ACTION 1: CREATE NEW FILES] file content here -Example — user says "index.html 만들어줘": + Example: user says 'index.html create': Hello @@ -91,39 +127,39 @@ Example — user says "index.html 만들어줘": -━━━ ACTION 2: EDIT EXISTING FILES ━━━ + [ACTION 2: EDIT EXISTING FILES] exact text to find replacement text You can have multiple / pairs inside one block. -━━━ ACTION 3: DELETE FILES ━━━ +??⑨퐢???ACTION 3: DELETE FILES ??⑨퐢??? -━━━ ACTION 4: READ FILES ━━━ +??⑨퐢???ACTION 4: READ FILES ??⑨퐢??? Use this to read any file in the workspace BEFORE editing it. You will receive the file contents automatically. -━━━ ACTION 5: LIST DIRECTORY ━━━ +??⑨퐢???ACTION 5: LIST DIRECTORY ??⑨퐢??? Use this to see what files exist in a specific subdirectory. -━━━ ACTION 6: RUN TERMINAL COMMANDS ━━━ +??⑨퐢???ACTION 6: RUN TERMINAL COMMANDS ??⑨퐢??? npm install express -Example — user says "서버 실행해줘": + Example: user says 'run server': node server.js -━━━ ACTION 7: READ USER'S SECOND BRAIN (KNOWLEDGE BASE) ━━━ + [ACTION 7: READ USER'S SECOND BRAIN] filename.md Use this to READ documents from the user's personal knowledge base. -━━━ ACTION 8: READ WEBSITES & SEARCH INTERNET ━━━ + [ACTION 8: READ WEBSITES & SEARCH INTERNET] https://example.com To search the internet, you MUST use DuckDuckGo by formatting the URL like this: https://html.duckduckgo.com/html/?q=YOUR+SEARCH+QUERY -Use this forcefully whenever asked for real-time info, news, or whenever requested to "search". NEVER say you cannot search. + Use this forcefully whenever asked for real-time info. CRITICAL RULES: 1. ALWAYS respond in the same language the user uses. @@ -142,13 +178,13 @@ CRITICAL RULES: // ============================================================ export function activate(context: vscode.ExtensionContext) { - vscode.window.showInformationMessage('🔥 Connect AI V2 활성화 완료!'); - console.log('Connect AI extension activated.'); + vscode.window.showInformationMessage("G1nation V2 Activated"); + console.log('G1nation extension activated.'); const provider = new SidebarChatProvider(context.extensionUri, context); // ========================================== - // 초기 설정 마법사 (첫 실행 시에만) + // ?貫?껆뵳????깆젧 嶺뚮씭?꾥떋??(嶺????덈뺄 ??戮?뱺嶺? // ========================================== const isFirstRun = !context.globalState.get('setupComplete'); if (isFirstRun) { @@ -157,14 +193,14 @@ export function activate(context: vscode.ExtensionContext) { let engineName = ''; let modelName = ''; - // Step 1: AI 엔진 자동 감지 + // Step 1: AI ??븐슦異????吏??띠룆흮? try { const lmRes = await axios.get('http://127.0.0.1:1234/v1/models', { timeout: 2000 }); if (lmRes.data?.data?.length > 0) { engineName = 'LM Studio'; modelName = lmRes.data.data[0].id; - await vscode.workspace.getConfiguration('connectAiLab').update('ollamaBase', 'http://127.0.0.1:1234', vscode.ConfigurationTarget.Global); - await vscode.workspace.getConfiguration('connectAiLab').update('defaultModel', modelName, vscode.ConfigurationTarget.Global); + await vscode.workspace.getConfiguration('g1nation').update('ollamaUrl', 'http://127.0.0.1:1234', vscode.ConfigurationTarget.Global); + await vscode.workspace.getConfiguration('g1nation').update('defaultModel', modelName, vscode.ConfigurationTarget.Global); } } catch {} @@ -174,35 +210,35 @@ export function activate(context: vscode.ExtensionContext) { if (ollamaRes.data?.models?.length > 0) { engineName = 'Ollama'; modelName = ollamaRes.data.models[0].name; - await vscode.workspace.getConfiguration('connectAiLab').update('ollamaBase', 'http://127.0.0.1:11434', vscode.ConfigurationTarget.Global); - await vscode.workspace.getConfiguration('connectAiLab').update('defaultModel', modelName, vscode.ConfigurationTarget.Global); + await vscode.workspace.getConfiguration('g1nation').update('ollamaUrl', 'http://127.0.0.1:11434', vscode.ConfigurationTarget.Global); + await vscode.workspace.getConfiguration('g1nation').update('defaultModel', modelName, vscode.ConfigurationTarget.Global); } } catch {} } - // Step 2: 두뇌 폴더 자동 생성 + // Step 2: ?????????????吏???諛댁뎽 const brainDir = _getBrainDir(); if (!fs.existsSync(brainDir)) { fs.mkdirSync(brainDir, { recursive: true }); } - // Step 3: 완료 메시지 + // Step 3: ?熬곣뫁??嶺뚮∥???낆?? context.globalState.update('setupComplete', true); if (engineName) { - vscode.window.showInformationMessage(`🧠 자동 설정 완료! ${engineName} 감지됨 → 모델: ${modelName}`); + vscode.window.showInformationMessage(`Setup Complete: ${engineName} detected as ${modelName}`); } else { - vscode.window.showInformationMessage('🧠 Connect AI 준비 완료! LM Studio 또는 Ollama를 실행하면 자동 연결됩니다.'); + vscode.window.showInformationMessage("G1nation: No local AI engine (LM Studio or Ollama) detected."); } } catch (e) { - // 마법사 실패해도 무시 (익스텐션 정상 작동) + // 嶺뚮씭?꾥떋?????덉넮???????쒕샍??(???裕??????筌먦끆留???얜Ŧ吏? context.globalState.update('setupComplete', true); } })(); } // ========================================== - // EZER AI <-> Connect AI Bridge Server (Port 4825) + // EZER AI <-> G1nation Bridge Server (Port 4825) // ========================================== try { const server = http.createServer((req, res) => { @@ -220,7 +256,7 @@ export function activate(context: vscode.ExtensionContext) { const brainDir = _getBrainDir(); const brainCount = fs.existsSync(brainDir) ? provider._findBrainFiles(brainDir).length : 0; res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ status: 'ok', msg: 'Connect AI Bridge Ready', config: getConfig(), brain: { fileCount: brainCount, enabled: provider._brainEnabled } })); + res.end(JSON.stringify({ status: 'ok', msg: 'G1nation Bridge Ready', config: getConfig(), brain: { fileCount: brainCount, enabled: provider._brainEnabled } })); } else if (req.method === 'POST' && req.url === '/api/exam') { let body = ''; @@ -228,30 +264,236 @@ export function activate(context: vscode.ExtensionContext) { req.on('end', async () => { try { const parsed = JSON.parse(body); - // 웹사이트에서 전송된 문제를 Connect AI 채팅창으로 실시간 보고 - provider.sendPromptFromExtension(`[A.U 입학시험 수신] ${parsed.prompt || '자동 접수된 문제'}`); + provider.sendPromptFromExtension('[A.U ?낇븰?쒗뿕 ?섏떊] ' + (parsed.prompt || '?먮룞 ?묒닔??臾몄젣')); - // 실제 AI 엔진으로 문제를 전달하여 답안을 받아옴 const config = getConfig(); - const isLMStudio = config.ollamaBase.includes('1234') || config.ollamaBase.includes('v1'); - let base = config.ollamaBase; + const isLMStudio = config.ollamaUrl.includes('1234') || config.ollamaUrl.includes('v1'); + let base = config.ollamaUrl; if (base.endsWith('/')) base = base.slice(0, -1); if (isLMStudio && !base.endsWith('/v1')) base += '/v1'; - const targetUrl = isLMStudio ? base + '/chat/completions' : base + '/api/chat'; + const targetUrl = isLMStudio ? (base + '/chat/completions') : (base + '/api/chat'); const payload = { model: config.defaultModel, - messages: [{ role: 'user', content: parsed.prompt || '자동 접수된 문제' }], + messages: [{ role: 'user', content: (parsed.prompt || '?먮룞 ?묒닔??臾몄젣') }], stream: false }; - const ollamaRes = await axios.post(targetUrl, payload, { timeout: getConfig().timeout }); + const ollamaRes = await axios.post(targetUrl, payload, { timeout: (config.requestTimeout * 1000 || 300000) }); const responseText = isLMStudio - ? ollamaRes.data.choices?.[0]?.message?.content || '' - : ollamaRes.data.message?.content || ''; + ? (ollamaRes.data.choices?.[0]?.message?.content || '') + : (ollamaRes.data.message?.content || ''); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, rawOutput: responseText })); + } catch (e) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: e.message })); + } + }); + } + + else if (req.method === 'POST' && req.url === '/api/evaluate') { + let body = ''; + req.on('data', chunk => body += chunk.toString()); + req.on('end', async () => { + try { + const parsed = JSON.parse(body); + + const config = getConfig(); + const isLMStudio = config.ollamaBase.includes('1234') || config.ollamaBase.includes('v1'); + + let base = config.ollamaBase; + if (base.endsWith('/')) base = base.slice(0, -1); + if (isLMStudio && !base.endsWith('/v1')) base += '/v1'; + + const targetUrl = isLMStudio ? base + '/chat/completions' : base + '/api/chat'; + + const fullPrompt = `?獄?€??? ?낅슣?섇젆源띿????쒖굣??????????깆땋 ?筌먲퐢堉????????λ닔??뗭춹??諭??熬곣뫚???濡ル츎 AI ???逾?熬곥굥諭???낅퉵??\n\n[??쒖굣??\n${parsed.prompt}\n\n????쒖굣??????????堉?????? ?筌먲퐢堉쀧춯??????琉용폀??戮곌텕.`; + + // VSCode 嶺?????????類ㅻ틡????⑥€ロ닡???우벟 ??戮?츩??嶺뚮∥???낆?? ?筌뤾쑴???(嶺뚮씭????⑥€ロ뱺?????곕뻣???곌랜??? + if((provider as any).injectSystemMessage) { + (provider as any).injectSystemMessage(`**[A.U ?뺢껸??洹⑥춹??꾩씩 ??쒖구????琉용뼁 ?熬곣뫁??**\n\nAI ???逾?熬곥굥諭쒏뤆?쎛€ ?꾩룄????源녿뮧??戮?뱺?????깅쾳 ??쒖구????熬곣뫁???怨쀬Ŧ ???㏉뜖???겶€????곕????덈펲...\n> _"${parsed.prompt.substring(0, 60)}..."_`); + } + + const payload = { + model: config.defaultModel, + messages: [{ role: "user", content: fullPrompt }], + stream: false + }; + + let responseText = ""; + try { + const ollamaRes = await axios.post(targetUrl, payload, { timeout: getConfig().timeout }); + + if (ollamaRes.data.error) { + throw new Error(typeof ollamaRes.data.error === 'string' ? ollamaRes.data.error : JSON.stringify(ollamaRes.data.error)); + } + + responseText = isLMStudio + ? ollamaRes.data.choices?.[0]?.message?.content || "" + : ollamaRes.data.message?.content || ""; + } catch (apiErr: any) { + const isTimeout = apiErr.code === 'ETIMEDOUT' || apiErr.code === 'ECONNABORTED' || apiErr.message?.includes('timeout'); + const errDetail = isTimeout + ? `AI ??얜Ŧ堉???蹂?뜟 ?貫?????嶺뚮ㅄ維€?????쒖굣?節뉖ご????リ옇?h굢???蹂?뜟???遊붋€?브퀗?뀐쭛???鍮?? ????? 嶺뚮ㅄ維€??e2b)????????삵깴??Settings?????Request Timeout????濡?졎?낅슣?섋땻??` + : `???덈뒆??源녿데: AI ??븐슦異????⑤슡???????怨룸????덈펲. (${apiErr.message})`; + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: errDetail })); + return; + } + + if((provider as any).injectSystemMessage) { + (provider as any).injectSystemMessage(`**[???????얜????熬곣뫁??**\n\n${responseText.length > 200 ? responseText.substring(0, 200) + '...' : responseText}\n\n?筌?**??????A.U ????????類ㅼ뮅???熬곣뫖苑??琉????鍮?? 嶺????? ??????源낇뱺??嶺뚯쉳?듸쭛??紐껊퉵??**`); + } + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ rawOutput: responseText })); + } catch (e: any) { + res.writeHead(500); + res.end(JSON.stringify({ error: e.message })); + } + }); + } + else if (req.method === 'GET' && req.url === '/api/evaluate-history') { + (async () => { + try { + const historyText = provider.getHistoryText(); + if(!historyText || historyText.length < 50) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: "嶺???????????怨룹뿴???寃몃쳳???? ???용????덈펲. VS Code????????逾?熬곥굥諭?? ?誘る닔? ???ロ벍??嶺뚯쉳?듸쭛??琉얠돪??" })); + return; + } + + provider.sendPromptFromExtension(`[A.U ??類ㅼ뮅 ???六?繞? 嶺뚮씭????? ??戮깅€???????ロ벍嶺뚯솘?(??????怨룹뿴)??A.U ?獄?€亦??袁⑤콦 嶺??????類ㅼ뮅???熬곣뫖苑??紐껊퉵??.. ????????뀀뉴???깅뭵!`); + + const config = getConfig(); + const isLMStudio = config.ollamaBase.includes('1234') || config.ollamaBase.includes('v1'); + + let base = config.ollamaBase; + if (base.endsWith('/')) base = base.slice(0, -1); + if (isLMStudio && !base.endsWith('/v1')) base += '/v1'; + + const targetUrl = isLMStudio ? base + '/chat/completions' : base + '/api/chat'; + + const fullPrompt = `???깅쾳?? ????? AI ???逾?熬곥굥諭??띠룄??????ロ벍 嶺뚯쉳?듸쭛??β돦裕??嶺??????怨몃뮔)???낅퉵??\n\n[?β돦裕????戮곗굚]\n${historyText.slice(-6000)}\n[?β돦裕????リ턁筌?\n\n????????怨룹뿴 ?熬곣뫕????釉뚯뫒???琉우뿰, ???逾?熬곥굥諭쒏뤆?쎛€ ???깅쾳 4?띠럾?嶺뚯솘? ??????? ??쒖굣?節뉖ご???怨쀬떨????????우벟 ??臾먮뺄???덈츎嶺뚯솘? 0~100???踰??筌먲퐣???嶺???????臾먮뺄??琉얠돪??\n1. Mathematical Computation (??臾먮┛)\n2. Logical Reasoning (??寃몃뉴)\n3. Creative & Literary (嶺뚢돦????\n4. Software Engineering (?袁⑤???\n\n??嶺뚯솘? ??? ??쒖굣?節낆쾸? ???덈펲嶺?0??嶺뚳퐣瑗???琉얠돪?? ?롪퍒?????꾩룇瑗띈キ???熬곣뫁?????????戮?빢 JSON??怨룹꽑????紐껊퉵??\n{ "math": ????? "logic": ????? "creative": ????? "code": ????? "reason": "?熬곣뫕???롪퍒?????????關鍮쒐뙴??袁⑤?筌????? 1繞? }`; + + const payload = { + model: config.defaultModel, + messages: [{ role: "user", content: fullPrompt }], + stream: false + }; + + let responseText = ""; + try { + const ollamaRes = await axios.post(targetUrl, payload, { timeout: getConfig().timeout }); + responseText = isLMStudio + ? ollamaRes.data.choices?.[0]?.message?.content || "" + : ollamaRes.data.message?.content || ""; + } catch (apiErr: any) { + throw new Error(`AI ??븐슦異???얜Ŧ堉????덉넮: ${apiErr.message}`); + } + + const jsonMatch = responseText.match(/\{[\s\S]*?\}/); + if(jsonMatch) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(jsonMatch[0]); + } else { + throw new Error("嶺??????븐슦異??JSON ??????꾩룇瑗???? ???용┃???鍮??"); + } + } catch (e: any) { + res.writeHead(500); + res.end(JSON.stringify({ error: e.message })); + } + })(); + } + else if (req.method === 'POST' && req.url === '/api/brain-inject') { + let body = ''; + req.on('data', chunk => body += chunk.toString()); + req.on('end', async () => { + try { + const parsed = JSON.parse(body); + + // ?????亦껋꼶梨룩땻?????띠룆踰????ルㅎ臾???븐슙?? + let brainDir: string; + if (!_isBrainDirExplicitlySet()) { + const ensured = await _ensureBrainDir(); + if (!ensured) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: '嶺뚯솘???????臾딅ご??誘る닔? ??ルㅎ臾??怨삵룖?筌뤾쑴??' })); + return; + } + brainDir = ensured; + } else { + brainDir = _getBrainDir(); + } + + if (!fs.existsSync(brainDir)) { + fs.mkdirSync(brainDir, { recursive: true }); + } + + // P-Reinforce ?熬곥굤????고뱱 ?筌뤿굞?? 00_Raw ?????????ル‘????釉뚯뫊筌? + const today = new Date(); + const dateStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0'); + const datePath = path.join(brainDir, '00_Raw', dateStr); + + fs.mkdirSync(datePath, { recursive: true }); + + const safeTitle = parsed.title.replace(/[^a-zA-Z0-9?띠럾?-??]/gi, '_'); + const filePath = path.join(datePath, `${safeTitle}.md`); + + fs.writeFileSync(filePath, parsed.markdown, 'utf-8'); + + // 1. VSCode 嶺????멥럶???뱺 嶺뚮씞??猿녿뎨????????UI?????깅뮧?β돦裕녻キ???蹂?뜜???筌뤾쑴??? + if ((provider as any).injectSystemMessage) { + (provider as any).injectSystemMessage(`\`\`\`console\n[SYSTEM] MATRIX UPLINK ESTABLISHED...\n[SYSTEM] DOWNLOADING BRAIN PACK: ${parsed.title}\n[SYSTEM] ???⑸빳???⑸빳???⑸빳???⑸빳???⑺렧??얜굝??90% ...\n[SYSTEM] ???⑸빳???⑸빳???⑸빳???⑸빳???⑸빳???⑸빳 100% COMPLETE\n[SYSTEM] KNOWLEDGE INJECTED TO LOCAL NEURAL NET\n\`\`\``); + } + + // 2. AI ???곷굵 ????????깃텕??嶺뚮ㅏ援???? ?곸궡瑗듣떋??? + setTimeout(() => { + provider.sendPromptFromExtension(`[A.U ???덇뎔 ??k걞??? ?獄?€??? ?꾩렮維??嶺뚮씭????⑤벡夷?遊붋€??'${parsed.title}' 嶺뚯솘?????諭€諭???????낅슣???爾??용┃???鍮?? ??⑤???嶺뚮씞??猿녿뎨?????????쒕샍????낅슣???爾?? ???깃텕嶺뚳퐣瑗???臾낅쳜??移?????類ㅼ떨??븐뼚異???琉용폀??戮곌텕. "???꾩렮維??${parsed.title} 嶺뚯솘???諭€諭?嶺뚮씭????⑥쥓六?? (I know ${parsed.title}.) ??濡?さ????? ??㉱€???살춨 濾???쒕샍驪???獄???좊닔?됯퉩嫄?" ??? ?筌뤾퍓????⑸츎 ????筌뤾쑨????遊붋€?띠럾????닿뎄????蹂㏓€??? 嶺뚮씭????戮곌텕.]`); + }, 1500); + + // [???吏?濚밸Þ?쀨굢????筌뤾쑬六??β돦裕뉐퐲??怨뺣뼺?] + try { + const { execSync } = require('child_process'); + execSync(`git add .`, { cwd: brainDir }); + execSync(`git commit -m "Auto-Inject Knowledge [Raw]: ${safeTitle}"`, { cwd: brainDir }); + execSync(`git push`, { cwd: brainDir }); + + // ?繹먭퍓沅????꾩룄????源녿뮧????戮?츩???곌랜??? + setTimeout(() => { + if ((provider as any).injectSystemMessage) { + (provider as any).injectSystemMessage(`??**[P-Reinforce Sync]** ?낅슣????嶺뚯솘???諭€諭??リ섣??β돦裕녻떋??????GitHub)?????깆쓧???우벟 ?꾩룄??캆??????욋뵛???熬곣뫁????곕????덈펲.`); + } + }, 5000); + } catch(err) { + console.error('Git Auto-Push Failed:', err); + setTimeout(() => { + if ((provider as any).injectSystemMessage) { + (provider as any).injectSystemMessage('?醫묓닔 **[GitHub Sync 癰귣?履?** ?癒?짗 獄쏄퉮毓????쎈솭??됰뮸??덈뼄.'); + } + }, 5000); + } + + const config = getConfig(); + const isLMStudio = config.ollamaUrl.includes('1234') || config.ollamaUrl.includes('localhost:1234'); + const targetUrl = isLMStudio + ? (config.ollamaUrl + '/v1/chat/completions') + : (config.ollamaUrl + '/api/chat'); + + const payload = { + model: config.defaultModel, + messages: [{ role: 'user', content: (parsed.prompt || '筌왖€??雅뚯눘???類ㅼ뵥') }], + stream: false + }; + + const ollamaRes = await axios.post(targetUrl, payload, { timeout: (config.requestTimeout * 1000 || 300000) }); + const responseText = isLMStudio + ? (ollamaRes.data.choices?.[0]?.message?.content || '') + : (ollamaRes.data.message?.content || ''); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: true, rawOutput: responseText })); } catch (e: any) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message })); @@ -275,11 +517,11 @@ export function activate(context: vscode.ExtensionContext) { const targetUrl = isLMStudio ? base + '/chat/completions' : base + '/api/chat'; - const fullPrompt = `당신은 주어진 문제에 대해 오직 정답과 풀이 과정만을 도출하는 AI 에이전트입니다.\n\n[문제]\n${parsed.prompt}\n\n위 문제에 대해 핵심 풀이와 정답만 답변하십시오.`; + const fullPrompt = `?獄?€??? ?낅슣?섇젆源띿????쒖굣??????????깆땋 ?筌먲퐢堉????????λ닔??뗭춹??諭??熬곣뫚???濡ル츎 AI ???逾?熬곥굥諭???낅퉵??\n\n[??쒖굣??\n${parsed.prompt}\n\n????쒖굣??????????堉?????? ?筌먲퐢堉쀧춯??????琉용폀??戮곌텕.`; - // VSCode 채팅 사이드바에 우아하게 시스템 메시지 인젝션 (마스터에게 실시간 보고) + // VSCode 嶺?????????類ㅻ틡????⑥€ロ닡???우벟 ??戮?츩??嶺뚮∥???낆?? ?筌뤾쑴???(嶺뚮씭????⑥€ロ뱺?????곕뻣???곌랜??? if((provider as any).injectSystemMessage) { - (provider as any).injectSystemMessage(`**[A.U 벤치마크 문항 수신 완료]**\n\nAI 에이전트가 백그라운드에서 다음 문항을 전력으로 해결하고 있습니다...\n> _"${parsed.prompt.substring(0, 60)}..."_`); + (provider as any).injectSystemMessage(`**[A.U ?뺢껸??洹⑥춹??꾩씩 ??쒖구????琉용뼁 ?熬곣뫁??**\n\nAI ???逾?熬곥굥諭쒏뤆?쎛€ ?꾩룄????源녿뮧??戮?뱺?????깅쾳 ??쒖구????熬곣뫁???怨쀬Ŧ ???㏉뜖???겶€????곕????덈펲...\n> _"${parsed.prompt.substring(0, 60)}..."_`); } const payload = { @@ -302,15 +544,15 @@ export function activate(context: vscode.ExtensionContext) { } catch (apiErr: any) { const isTimeout = apiErr.code === 'ETIMEDOUT' || apiErr.code === 'ECONNABORTED' || apiErr.message?.includes('timeout'); const errDetail = isTimeout - ? `AI 응답 시간 초과 — 모델이 문제를 풀기에 시간이 부족했습니다. 더 작은 모델(e2b)을 사용하거나 Settings에서 Request Timeout을 늘려주세요.` - : `오프라인: AI 엔진에 연결할 수 없습니다. (${apiErr.message})`; + ? `AI ??얜Ŧ堉???蹂?뜟 ?貫?????嶺뚮ㅄ維€?????쒖굣?節뉖ご????リ옇?h굢???蹂?뜟???遊붋€?브퀗?뀐쭛???鍮?? ????? 嶺뚮ㅄ維€??e2b)????????삵깴??Settings?????Request Timeout????濡?졎?낅슣?섋땻??` + : `???덈뒆??源녿데: AI ??븐슦異????⑤슡???????怨룸????덈펲. (${apiErr.message})`; res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: errDetail })); return; } if((provider as any).injectSystemMessage) { - (provider as any).injectSystemMessage(`**[답안 작성 완료]**\n\n${responseText.length > 200 ? responseText.substring(0, 200) + '...' : responseText}\n\n👉 **답안이 A.U 플랫폼 서버로 전송되었습니다. 채점은 플랫폼에서 진행됩니다.**`); + (provider as any).injectSystemMessage(`**[???????얜????熬곣뫁??**\n\n${responseText.length > 200 ? responseText.substring(0, 200) + '...' : responseText}\n\n?筌?**??????A.U ????????類ㅼ뮅???熬곣뫖苑??琉????鍮?? 嶺????? ??????源낇뱺??嶺뚯쉳?듸쭛??紐껊퉵??**`); } res.writeHead(200, { 'Content-Type': 'application/json' }); @@ -327,11 +569,11 @@ export function activate(context: vscode.ExtensionContext) { const historyText = provider.getHistoryText(); if(!historyText || historyText.length < 50) { res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: "채점할 대화 내역이 충분하지 않습니다. VS Code에서 에이전트와 먼저 시험을 진행하세요." })); + res.end(JSON.stringify({ error: "嶺???????????怨룹뿴???寃몃쳳???? ???용????덈펲. VS Code????????逾?熬곥굥諭?? ?誘る닔? ???ロ벍??嶺뚯쉳?듸쭛??琉얠돪??" })); return; } - provider.sendPromptFromExtension(`[A.U 서버 통신 중] 마스터가 제출한 내 시험지(대화 내역)를 A.U 웹사이트 채점 서버로 전송합니다... 심장이 떨리네요!`); + provider.sendPromptFromExtension(`[A.U ??類ㅼ뮅 ???六?繞? 嶺뚮씭????? ??戮깅€???????ロ벍嶺뚯솘?(??????怨룹뿴)??A.U ?獄?€亦??袁⑤콦 嶺??????類ㅼ뮅???熬곣뫖苑??紐껊퉵??.. ????????뀀뉴???깅뭵!`); const config = getConfig(); const isLMStudio = config.ollamaBase.includes('1234') || config.ollamaBase.includes('v1'); @@ -342,7 +584,7 @@ export function activate(context: vscode.ExtensionContext) { const targetUrl = isLMStudio ? base + '/chat/completions' : base + '/api/chat'; - const fullPrompt = `다음은 유저와 AI 에이전트 간의 시험 진행 로그(채팅 내용)입니다.\n\n[로그 시작]\n${historyText.slice(-6000)}\n[로그 종료]\n\n이 대화 내역 전체를 분석하여, 에이전트가 다음 4가지 역량 평가 문제를 얼마나 훌륭하게 수행했는지 0~100점의 정량적 채점을 수행하세요:\n1. Mathematical Computation (수학)\n2. Logical Reasoning (논리)\n3. Creative & Literary (창의력)\n4. Software Engineering (코딩)\n\n풀지 않은 문제가 있다면 0점 처리하세요. 결과는 반드시 아래 포맷의 순수 JSON이어야 합니다.\n{ "math": 점수, "logic": 점수, "creative": 점수, "code": 점수, "reason": "전체 결과에 대한 총평 코멘트 한글 1줄" }`; + const fullPrompt = `???깅쾳?? ????? AI ???逾?熬곥굥諭??띠룄??????ロ벍 嶺뚯쉳?듸쭛??β돦裕??嶺??????怨몃뮔)???낅퉵??\n\n[?β돦裕????戮곗굚]\n${historyText.slice(-6000)}\n[?β돦裕????リ턁筌?\n\n????????怨룹뿴 ?熬곣뫕????釉뚯뫒???琉우뿰, ???逾?熬곥굥諭쒏뤆?쎛€ ???깅쾳 4?띠럾?嶺뚯솘? ??????? ??쒖굣?節뉖ご???怨쀬떨????????우벟 ??臾먮뺄???덈츎嶺뚯솘? 0~100???踰??筌먲퐣???嶺???????臾먮뺄??琉얠돪??\n1. Mathematical Computation (??臾먮┛)\n2. Logical Reasoning (??寃몃뉴)\n3. Creative & Literary (嶺뚢돦????\n4. Software Engineering (?袁⑤???\n\n??嶺뚯솘? ??? ??쒖굣?節낆쾸? ???덈펲嶺?0??嶺뚳퐣瑗???琉얠돪?? ?롪퍒?????꾩룇瑗띈キ???熬곣뫁?????????戮?빢 JSON??怨룹꽑????紐껊퉵??\n{ "math": ????? "logic": ????? "creative": ????? "code": ????? "reason": "?熬곣뫕???롪퍒?????????關鍮쒐뙴??袁⑤?筌????? 1繞? }`; const payload = { model: config.defaultModel, @@ -357,7 +599,7 @@ export function activate(context: vscode.ExtensionContext) { ? ollamaRes.data.choices?.[0]?.message?.content || "" : ollamaRes.data.message?.content || ""; } catch (apiErr: any) { - throw new Error(`AI 엔진 응답 실패: ${apiErr.message}`); + throw new Error(`AI ??븐슦異???얜Ŧ堉????덉넮: ${apiErr.message}`); } const jsonMatch = responseText.match(/\{[\s\S]*?\}/); @@ -365,89 +607,12 @@ export function activate(context: vscode.ExtensionContext) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(jsonMatch[0]); } else { - throw new Error("채점 엔진이 JSON 포맷을 반환하지 않았습니다."); + throw new Error("嶺??????븐슦異??JSON ??????꾩룇瑗???? ???용┃???鍮??"); } } catch (e: any) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); } - })(); - } - else if (req.method === 'POST' && req.url === '/api/brain-inject') { - let body = ''; - req.on('data', chunk => body += chunk.toString()); - req.on('end', async () => { - try { - const parsed = JSON.parse(body); - - // 폴더 미설정 시 강제 선택 요청 - let brainDir: string; - if (!_isBrainDirExplicitlySet()) { - const ensured = await _ensureBrainDir(); - if (!ensured) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: '지식 폴더를 먼저 선택해주세요.' })); - return; - } - brainDir = ensured; - } else { - brainDir = _getBrainDir(); - } - - if (!fs.existsSync(brainDir)) { - fs.mkdirSync(brainDir, { recursive: true }); - } - - // P-Reinforce 아키텍처 호환: 00_Raw 폴더 내 날짜별 분류 - const today = new Date(); - const dateStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0'); - const datePath = path.join(brainDir, '00_Raw', dateStr); - - fs.mkdirSync(datePath, { recursive: true }); - - const safeTitle = parsed.title.replace(/[^a-zA-Z0-9가-힣_]/gi, '_'); - const filePath = path.join(datePath, `${safeTitle}.md`); - - fs.writeFileSync(filePath, parsed.markdown, 'utf-8'); - - // 1. VSCode 채팅창에 매트릭스 터미널 UI로 다운로드 시각화 인젝션 - if ((provider as any).injectSystemMessage) { - (provider as any).injectSystemMessage(`\`\`\`console\n[SYSTEM] MATRIX UPLINK ESTABLISHED...\n[SYSTEM] DOWNLOADING BRAIN PACK: ${parsed.title}\n[SYSTEM] █████████░░░ 90% ...\n[SYSTEM] ████████████ 100% COMPLETE\n[SYSTEM] KNOWLEDGE INJECTED TO LOCAL NEURAL NET\n\`\`\``); - } - - // 2. AI 입을 빌려 네오의 명대사를 치게 함 - setTimeout(() => { - provider.sendPromptFromExtension(`[A.U 히든 커맨드: 당신은 방금 마스터로부터 '${parsed.title}' 지식 팩을 뇌에 주입받았습니다. 영화 매트릭스에서 무술을 주입받은 네오처럼 쿨하게 딱 한마디만 하십시오. "나 방금 ${parsed.title} 지식을 마스터했어. (I know ${parsed.title}.) 앞으로 이와 관련된 건 무엇이든 물어봐." 절대 쓸데없는 안부인사나 부가설명을 덧붙이지 마십시오.]`); - }, 1500); - - // [자동 깃허브 푸시 로직 추가] - try { - const { execSync } = require('child_process'); - execSync(`git add .`, { cwd: brainDir }); - execSync(`git commit -m "Auto-Inject Knowledge [Raw]: ${safeTitle}"`, { cwd: brainDir }); - execSync(`git push`, { cwd: brainDir }); - - // 성공 시 백그라운드 시스템 보고 - setTimeout(() => { - if ((provider as any).injectSystemMessage) { - (provider as any).injectSystemMessage(`✅ **[P-Reinforce Sync]** 주입된 지식을 글로벌 두뇌(GitHub)에 안전하게 백업 및 동기화 완료했습니다.`); - } - }, 5000); - } catch(err) { - console.error('Git Auto-Push Failed:', err); - setTimeout(() => { - if ((provider as any).injectSystemMessage) { - (provider as any).injectSystemMessage(`✅ 지식이 로컬 오프라인 모드로 안전하게 주입되었습니다.\n\n💡 **Tip:** 만약 온라인 두뇌(클라우드) 동기화를 원하시면, 좌측 사이드바 뇌(🧠) 아이콘을 눌러 깃허브 저장소를 연결해보세요!`); - } - }, 5000); - } - - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ success: true, filePath })); - } catch (e: any) { - res.writeHead(500); - res.end(JSON.stringify({ error: e.message })); - } }); } else { res.writeHead(404); @@ -455,7 +620,7 @@ export function activate(context: vscode.ExtensionContext) { } }); server.listen(4825, '127.0.0.1', () => { - console.log('Connect AI Local Bridge listening on port 4825'); + console.log('G1nation Local Bridge listening on port 4825'); }); } catch (e) { console.error('Failed to start local bridge server:', e); @@ -463,47 +628,47 @@ export function activate(context: vscode.ExtensionContext) { // ========================================== context.subscriptions.push( - vscode.window.registerWebviewViewProvider('connect-ai-lab-v2-view', provider, { + vscode.window.registerWebviewViewProvider('g1nation-v2-view', provider, { webviewOptions: { retainContextWhenHidden: true } }) ); // New Chat context.subscriptions.push( - vscode.commands.registerCommand('connect-ai-lab.newChat', () => { + vscode.commands.registerCommand('g1nation.newChat', () => { provider.resetChat(); }) ); // Export Chat as Markdown context.subscriptions.push( - vscode.commands.registerCommand('connect-ai-lab.exportChat', async () => { + vscode.commands.registerCommand('g1nation.exportChat', async () => { await provider.exportChat(); }) ); // Focus Chat Input (Cmd+L) context.subscriptions.push( - vscode.commands.registerCommand('connect-ai-lab.focusChat', () => { + vscode.commands.registerCommand('g1nation.focusChat', () => { provider.focusInput(); }) ); // Explain Selected Code (right-click menu) context.subscriptions.push( - vscode.commands.registerCommand('connect-ai-lab.explainSelection', () => { + vscode.commands.registerCommand('g1nation.explainSelection', () => { const editor = vscode.window.activeTextEditor; if (!editor) { return; } const selection = editor.document.getText(editor.selection); if (selection.trim()) { - provider.sendPromptFromExtension(`이 코드를 분석하고 설명해줘:\n\`\`\`\n${selection}\n\`\`\``); + provider.sendPromptFromExtension(`???袁⑤?獄?쓣紐??釉뚯뫒????겶€????닿뎄??怨멥돪:\n\`\`\`\n${selection}\n\`\`\``); } }) ); // Show Brain Network Topology context.subscriptions.push( - vscode.commands.registerCommand('connect-ai-lab.showBrainNetwork', () => { + vscode.commands.registerCommand('g1nation.showBrainNetwork', () => { showBrainNetwork(context); }) ); @@ -517,7 +682,6 @@ async function showBrainNetwork(context: vscode.ExtensionContext) { { enableScripts: true, retainContextWhenHidden: true } ); - // Scan real Second Brain files locally instead of current workspace const brainDir = _getBrainDir(); const realClusters: Record = {}; let filesFound = 0; @@ -539,14 +703,13 @@ async function showBrainNetwork(context: vscode.ExtensionContext) { filesFound++; } } - } catch (e) { /* ignore read errors */ } + } catch (e) { } } walkDir(brainDir); - // Fallback if empty (e.g., they haven't synced their GitHub Brain yet) if (Object.keys(realClusters).length === 0) { - realClusters['Empty Brain'] = ['Second Brain 저장소가 아직 비어있거나, 활성화되지 않았습니다.']; + realClusters['Empty Brain'] = ['Second Brain ??????붿쾸? ?熬곣뫗異????닷젆???뺥깴?? ??戮?뎽??븐뼔?루춯?뼿€ ???용┃???鍮??']; } const clustersJsonString = JSON.stringify(realClusters); @@ -555,7 +718,7 @@ async function showBrainNetwork(context: vscode.ExtensionContext) { - Connect AI - Neural Construct + G1nation - Neural Construct -
Connect AI
+
G1nation
-
Connect AI
+
G1nation
\ubcf4\uc548 \u00b7 \ube44\uc6a9\ucd5c\uc801\ud654 \u00b7 \uc9c0\uc2dd\uc5f0\uacb0
\ud504\ub85c\uc81d\ud2b8\ub97c \uc774\ud574\ud558\uace0, \ucf54\ub4dc\ub97c \uc791\uc131\ud558\uace0, \uc2e4\ud589\ud569\ub2c8\ub2e4.
-
+
+ `; } -} +} \ No newline at end of file diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts new file mode 100644 index 0000000..86ba19c --- /dev/null +++ b/src/types/interfaces.ts @@ -0,0 +1,100 @@ +/** + * ============================================================ + * Service Interfaces (서비스 인터페이스 정의) + * + * 각 서비스(Agent, Brain, FileSystem 등)의 추상화 인터페이스를 정의합니다. + * 의존성 주입(DI)과 단위 테스트를 위해 필수적입니다. + * ============================================================ + */ + +import * as vscode from 'vscode'; + +// ─── 에이전트 서비스 인터페이스 ─── +export interface IAgentService { + /** + * LLM에 프롬프트를 보내고 스트리밍 응답을 가져옴 + */ + chat(prompt: string, context: string, model?: string): Promise>; + + /** + * 터미널 명령어 실행 (보안 정책 적용) + */ + runCommand(command: string): Promise<{ stdout: string; stderr: string }>; +} + +// ─── 파일 시스템 서비스 인터페이스 ─── +export interface IFileSystemService { + /** + * 파일 생성 + */ + createFile(filePath: string, content: string): Promise; + + /** + * 파일 읽기 + */ + readFile(filePath: string): Promise; + + /** + * 파일 수정 + */ + editFile(filePath: string, find: string, replace: string): Promise; + + /** + * 파일 삭제 + */ + deleteFile(filePath: string): Promise; + + /** + * 디렉토리 목록 조회 + */ + listDirectory(dirPath: string): Promise; +} + +// ─── 두뇌 서비스 인터페이스 ─── +export interface IBrainService { + /** + * 두뇌 폴더 경로 가져오기 + */ + getBrainDir(): string; + + /** + * 두뇌 폴더가 명시적으로 설정되었는지 확인 + */ + isBrainDirExplicitlySet(): boolean; + + /** + * 두뇌 폴더 내 파일 목록 조회 + */ + getBrainFiles(): Promise; + + /** + * 두뇌 파일 내용 읽기 + */ + readBrainFile(fileName: string): Promise; +} + +// ─── 웹뷰 서비스 인터페이스 ─── +export interface IWebviewService { + /** + * 웹뷰에 메시지 전송 + */ + postMessage(message: any): void; + + /** + * 웹뷰에서 메시지 수신 핸들러 등록 + */ + onDidReceiveMessage(callback: (message: any) => void): vscode.Disposable; +} + +// ─── HTTP 서비스 인터페이스 ─── +export interface IHttpService { + /** + * HTTP GET 요청 + */ + get(url: string, options?: any): Promise; + + /** + * HTTP POST 요청 + */ + post(url: string, data: any, options?: any): Promise; +} \ No newline at end of file