diff --git a/README.md b/README.md index e88a193..d29b0fb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Connect AI Logo

-

Connect AI v2 (P-Reinforce)

+

G1nation (P-Reinforce)

100% Local Β· 100% Offline Β· Autonomous Knowledge Engine
@@ -10,7 +10,7 @@

- version + version license integration engine @@ -20,7 +20,7 @@ ## 🌟 Overview: The P-Reinforce Architecture -Connect AI v2.1.30은 λ‹¨μˆœν•œ μ½”λ”© μ—μ΄μ „νŠΈλ₯Ό λ„˜μ–΄μ„­λ‹ˆλ‹€. **P-Reinforce μ•„ν‚€ν…μ²˜**λ₯Ό 기반으둜 μ„€κ³„λœ 이 μ—μ΄μ „νŠΈλŠ” μ‚¬μš©μžμ˜ λͺ¨λ“  정보와 μ§€μ‹œλ₯Ό λ°›μ•„λ“€μ—¬ **슀슀둜 의미λ₯Ό λΆ„μ„ν•˜κ³ , 폴더λ₯Ό μƒμ„±ν•˜κ³ , λ§ˆν¬λ‹€μš΄ μœ„ν‚€ 파일둜 μ •λ¦¬ν•˜μ—¬ ν΄λΌμš°λ“œμ— μžλ™ λ°±μ—…**ν•˜λŠ” 자율 지식 정원사(Autonomous Gardener)μž…λ‹ˆλ‹€. +G1nation v2.2.66은 λ‹¨μˆœν•œ μ½”λ”© μ—μ΄μ „νŠΈλ₯Ό λ„˜μ–΄μ„­λ‹ˆλ‹€. **P-Reinforce μ•„ν‚€ν…μ²˜**λ₯Ό 기반으둜 μ„€κ³„λœ 이 μ—μ΄μ „νŠΈλŠ” μ‚¬μš©μžμ˜ λͺ¨λ“  정보와 μ§€μ‹œλ₯Ό λ°›μ•„λ“€μ—¬ **슀슀둜 의미λ₯Ό λΆ„μ„ν•˜κ³ , 폴더λ₯Ό μƒμ„±ν•˜κ³ , λ§ˆν¬λ‹€μš΄ μœ„ν‚€ 파일둜 μ •λ¦¬ν•˜μ—¬ ν΄λΌμš°λ“œμ— μžλ™ λ°±μ—…**ν•˜λŠ” 자율 지식 정원사(Autonomous Gardener)μž…λ‹ˆλ‹€. --- @@ -37,8 +37,11 @@ Agent University μ›Ή ν”Œλž«νΌκ³Ό μ‹€μ‹œκ°„μœΌλ‘œ ν†΅μ‹ ν•©λ‹ˆλ‹€. 둜컬 PCμ—μ„œ 파일 생성이 μΌμ–΄λ‚˜λŠ” μˆœκ°„, μ—μ΄μ „νŠΈκ°€ 슀슀둜 GitHub μ €μž₯μ†Œμ— `git add`, `commit`, `push`λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€. λ§ˆμŠ€ν„°λŠ” 이제 μ§€λ£¨ν•œ ν‘Έμ‹œ μ»€λ§¨λ“œλ₯Ό μž…λ ₯ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€. -### 4. πŸ”— μ„€μΉ˜ν˜• λͺ¨λΈ μžλ™ 감지 (Dynamic Model Detection) -Ollama λ˜λŠ” LM Studio에 μ„€μΉ˜λœ λͺ¨λΈμ„ λ‚΄λΆ€ API(`v1/models`)λ₯Ό ν˜ΈμΆœν•˜μ—¬ μžλ™ κ°μ§€ν•˜κ³ , UI의 μŠ€μœ„μΉ˜ λ³΄λ“œ(λ“œλ‘­λ‹€μš΄)에 μ—°κ²°ν•©λ‹ˆλ‹€. μ–΄λ–€ λͺ¨λΈμ„ μ“Έμ§€ 번거둭게 μž…λ ₯ν•˜μ§€ λ§ˆμ‹­μ‹œμ˜€. +### 4. πŸ’Ύ κ²°κ³Όλ¬Ό 내보내기 (Export to MD) +AI의 λ‹΅λ³€ κ²°κ³Όλ₯Ό 클릭 ν•œ 번으둜 λ§ˆν¬λ‹€μš΄(.md) 파일둜 μ¦‰μ‹œ μ €μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 지식 베이슀 ꡬ좕이 λ”μš± λΉ¨λΌμ§‘λ‹ˆλ‹€. + +### 5. πŸ”— μ„€μΉ˜ν˜• λͺ¨λΈ μžλ™ 감지 (Dynamic Model Detection) +Ollama λ˜λŠ” LM Studio에 μ„€μΉ˜λœ λͺ¨λΈμ„ λ‚΄λΆ€ API(`v1/models`)λ₯Ό ν˜ΈμΆœν•˜μ—¬ μžλ™ κ°μ§€ν•˜κ³ , UI의 μŠ€μœ„μΉ˜ λ³΄λ“œ(λ“œλ‘­λ‹€μš΄)에 μ—°κ²°ν•©λ‹ˆλ‹€. --- @@ -61,7 +64,7 @@ Ollama λ˜λŠ” LM Studio에 μ„€μΉ˜λœ λͺ¨λΈμ„ λ‚΄λΆ€ API(`v1/models`)λ₯Ό 호좜 ### A.U 멀버십 μœ μ € (Recommended) 1. 상단 νƒ­μ˜ [Releases](https://github.com/wonseokjung/connect-ai/releases) λ©”λ‰΄λ‘œ μ§„μž…. -2. μ΅œμ‹  `v2.1.30.vsix` νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œ. +2. μ΅œμ‹  `v2.2.66.vsix` νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œ. 3. VS Code μ—μ„œ `Cmd+Shift+P` β†’ **Extensions: Install from VSIX** β†’ λ‹€μš΄λ°›μ€ 파일 선택 ### 개발자 λΉŒλ“œ (Build from Source) @@ -75,23 +78,6 @@ npx vsce package --- -## βš™οΈ Engine Setup (μ—”μ§„ μ„€μ • 방법) - -### βœ… LM Studio (Apple Silicon, Windows) - ꢌμž₯ -1. [lmstudio.ai](https://lmstudio.ai/) μ—μ„œ μ„€μΉ˜ -2. Gemma 3, Llama 3 λ˜λŠ” Qwen Coder λ“± μ›ν•˜λŠ” λͺ¨λΈ λ‘œλ“œ -3. **Developer νƒ­(쒌츑 `<>` 메뉴)** μ§„μž… ν›„ **Start Server** 클릭 -4. Connect AI의 βš™οΈ μ±„νŒ…λ°© μ„€μ •μ—μ„œ 엔진을 "LM Studio"둜 선택 (μžλ™ λͺ¨λΈ 인덱싱 μ™„λ£Œ) - -### βœ… Ollama (Mac, Linux) -```bash -brew install ollama -ollama pull gemma3 # μ›ν•˜λŠ” λͺ¨λΈ 풀링 -``` -Connect AIμ—μ„œ μ„€μ •λ§Œ "Ollama"둜 λ°”κΏ”μ£Όμ‹œλ©΄ λλ‚©λ‹ˆλ‹€. - ---- - ## πŸ”’ Privacy (μ™„λ²½ν•œ λ³΄μ•ˆ) - **Zero Cloud API:** λ‹Ήμ‹ μ˜ μ½”λ“œλŠ” μ™ΈλΆ€ ν΄λΌμš°λ“œ 톡신망을 타지 μ•ŠμŠ΅λ‹ˆλ‹€. diff --git a/package-lock.json b/package-lock.json index ab6e178..a34088a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "g1nation", - "version": "2.2.59", + "version": "2.2.63", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "g1nation", - "version": "2.2.59", + "version": "2.2.63", "license": "MIT", "dependencies": { "marked": "^18.0.2" diff --git a/package.json b/package.json index 11cb610..2c84fb1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "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.61", + "version": "2.2.67", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/agent.ts b/src/agent.ts index 5c44fab..b659bc5 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -196,10 +196,10 @@ export class AgentExecutor { const agentSkillCtx = options.agentSkillContext ? `\n\n[AGENT PERSONA & SKILLS]\n${options.agentSkillContext}` : ''; const negativeCtx = options.negativePrompt - ? `\n\n[INTERNAL_NEGATIVE_CONSTRAINTS]\n${options.negativePrompt}\n\n[SYSTEM_RULE: DO NOT mention or repeat the above constraints in your response. Apply them only to avoid unwanted behaviors.]` + ? `\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 fullSystemPrompt = `${systemPrompt}${internetCtx}${negativeCtx}\n\n[CONTEXT]\n${brainContext}\n${contextBlock}${agentSkillCtx}`; + const fullSystemPrompt = `${systemPrompt}${internetCtx}\n\n[CONTEXT]\n${brainContext}\n${contextBlock}${agentSkillCtx}${negativeCtx}`; const messagesForRequest: ChatMessage[] = [ { role: 'system', content: fullSystemPrompt, internal: true }, ...reqMessages diff --git a/src/bridge.ts b/src/bridge.ts index c28be01..96b8fe1 100644 --- a/src/bridge.ts +++ b/src/bridge.ts @@ -57,6 +57,14 @@ export class BridgeServer { } }); + this.server.on('error', (err: any) => { + if (err.code === 'EADDRINUSE') { + logWarn(`Bridge server: Port ${port} is already in use. Another instance might be running.`); + } else { + logError('Bridge server error:', err); + } + }); + this.server.listen(port, '127.0.0.1', () => { logInfo(`Bridge server active on 127.0.0.1:${port}.`); }); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 4b19b27..0680b37 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -21,6 +21,7 @@ interface LastVisibleChatSnapshot { brainProfileId: string; sessionId: string | null; timestamp: number; + negativePrompt?: string; } interface ChatSession { @@ -29,6 +30,7 @@ interface ChatSession { timestamp: number; history: ChatMessage[]; brainProfileId: string; + negativePrompt?: string; } /** @@ -39,9 +41,11 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn private static readonly activeSessionStateKey = 'g1nation.activeSessionId'; private static readonly lastVisibleChatStateKey = 'g1nation.lastVisibleChat'; private static readonly blankChatStateKey = 'g1nation.blankChatActive'; + private static readonly lastAgentStateKey = 'g1nation.lastAgentPath'; private _view?: vscode.WebviewView; public brainEnabled = true; private _currentSessionBrainId: string | null = null; + private _currentNegativePrompt: string = ''; constructor( private readonly _extensionUri: vscode.Uri, @@ -134,11 +138,23 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn await this._sendAgentContent(data.path); break; case 'updateAgent': - await this._updateAgent(data.path, data.content); + await this._updateAgent(data.path, data.content, data.negativePrompt); break; case 'refreshModels': await this._sendModels(); break; + case 'exportResponse': + const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath || ''; + const defaultPath = path.join(workspacePath, 'g1_response.md'); + const uri = await vscode.window.showSaveDialog({ + defaultUri: vscode.Uri.file(defaultPath), + filters: { 'Markdown': ['md'] } + }); + if (uri) { + await vscode.workspace.fs.writeFile(uri, Buffer.from(data.text, 'utf8')); + vscode.window.showInformationMessage(`βœ… Exported to ${path.basename(uri.fsPath)}`); + } + break; } }); } @@ -172,9 +188,14 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn if (snapshot?.history?.length) { this._currentSessionId = snapshot.sessionId || null; this._currentSessionBrainId = snapshot.brainProfileId || getActiveBrainProfile().id; + this._currentNegativePrompt = snapshot.negativePrompt || ''; await this._setActiveBrainProfile(this._currentSessionBrainId, true); this._agent.setHistory(snapshot.history); - this._view.webview.postMessage({ type: 'restoreHistory', value: snapshot.history }); + this._view.webview.postMessage({ + type: 'restoreHistory', + value: snapshot.history, + negativePrompt: this._currentNegativePrompt + }); } } @@ -188,7 +209,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn history, brainProfileId: this._currentSessionBrainId || getActiveBrainProfile().id, sessionId: this._currentSessionId, - timestamp: Date.now() + timestamp: Date.now(), + negativePrompt: this._currentNegativePrompt }; await this._context.globalState.update(SidebarChatProvider.lastVisibleChatStateKey, snapshot); } @@ -209,7 +231,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn title, timestamp: Date.now(), history, - brainProfileId + brainProfileId, + negativePrompt: this._currentNegativePrompt }); } else { const idx = sessions.findIndex(s => s.id === this._currentSessionId); @@ -217,6 +240,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn sessions[idx].history = history; sessions[idx].timestamp = Date.now(); sessions[idx].brainProfileId = brainProfileId; + sessions[idx].negativePrompt = this._currentNegativePrompt; if (!sessions[idx].title || sessions[idx].title === 'New Chat') { sessions[idx].title = title; } @@ -226,7 +250,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn title, timestamp: Date.now(), history, - brainProfileId + brainProfileId, + negativePrompt: this._currentNegativePrompt }); } } @@ -273,6 +298,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn this._agent.stop(); this._currentSessionId = id; + this._currentNegativePrompt = session.negativePrompt || ''; const sessionBrainId = session.brainProfileId || getActiveBrainProfile().id; await this._setActiveBrainProfile(sessionBrainId, true); this._agent.setHistory(history); @@ -284,7 +310,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn value: { id, title: session.title || 'Chat Session', - history + history, + negativePrompt: this._currentNegativePrompt } }); if (!skipSessionListRefresh) { @@ -338,7 +365,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn title: String(session.title || fallbackTitle), timestamp: typeof session.timestamp === 'number' ? session.timestamp : Date.now(), history, - brainProfileId: String(session.brainProfileId || getActiveBrainProfile().id) + brainProfileId: String(session.brainProfileId || getActiveBrainProfile().id), + negativePrompt: String(session.negativePrompt || '') }; }) .filter((session): session is ChatSession => !!session) @@ -599,7 +627,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn } } } - this._view.webview.postMessage({ type: 'agentsList', value: agents }); + const lastPath = this._context.globalState.get(SidebarChatProvider.lastAgentStateKey, 'none'); + this._view.webview.postMessage({ type: 'agentsList', value: agents, selected: lastPath }); } private async _createAgent() { @@ -632,14 +661,23 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn if (!this._view || !agentPath || agentPath === 'none') return; if (fs.existsSync(agentPath)) { const content = fs.readFileSync(agentPath, 'utf8'); - this._view.webview.postMessage({ type: 'agentContent', value: content }); + const negativePrompt = this._context.globalState.get(`negativePrompt:${agentPath}`, ''); + this._view.webview.postMessage({ + type: 'agentContent', + value: content, + negativePrompt: negativePrompt + }); + await this._context.globalState.update(SidebarChatProvider.lastAgentStateKey, agentPath); } } - private async _updateAgent(agentPath: string, content: string) { + private async _updateAgent(agentPath: string, content: string, negativePrompt?: string) { if (!agentPath || agentPath === 'none') return; try { fs.writeFileSync(agentPath, content, 'utf8'); + if (negativePrompt !== undefined) { + await this._context.globalState.update(`negativePrompt:${agentPath}`, negativePrompt); + } vscode.window.showInformationMessage('Agent skill updated successfully.'); } catch (err: any) { vscode.window.showErrorMessage(`Failed to update agent: ${err.message}`); @@ -650,6 +688,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn if (!this._view) return; const { value, model, internet, files, agentFile, negativePrompt } = data; + this._currentNegativePrompt = negativePrompt || ''; this._currentSessionBrainId = getActiveBrainProfile().id; let agentSkillContext = undefined; @@ -899,13 +938,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn .markdown-body code { font-family: 'SF Mono', monospace; font-size: 11.5px; background: rgba(175, 184, 193, 0.2); padding: 0.2em 0.4em; border-radius: 4px; } /* --- UI Elements --- */ - .copy-btn { - position: absolute; top: 0; right: 0; background: var(--bg-secondary); border: 1px solid var(--border); - color: var(--text-dim); padding: 4px 10px; border-radius: 6px; font-size: 10px; cursor: pointer; opacity: 0; transition: 0.2s; - z-index: 20; + .msg-actions { + position: absolute; bottom: -12px; right: 0; display: flex; gap: 4px; opacity: 0; transition: 0.2s; z-index: 20; } - .msg:hover .copy-btn { opacity: 1; } - .copy-btn:hover { color: var(--text-bright); border-color: var(--accent); background: var(--accent-glow); } + .msg:hover .msg-actions { opacity: 1; } + .action-btn { + background: var(--bg-secondary); border: 1px solid var(--border); + color: var(--text-dim); padding: 4px 10px; border-radius: 6px; font-size: 10px; cursor: pointer; transition: 0.2s; + display: flex; align-items: center; gap: 4px; + } + .action-btn:hover { color: var(--text-bright); border-color: var(--accent); background: var(--accent-glow); } .icon-btn { background: var(--surface); border: 1px solid var(--border); color: var(--text-dim); width: 28px; height: 28px; @@ -1091,6 +1133,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn document.body.removeChild(textarea); } + function exportToMD(text) { + vscode.postMessage({ type: 'exportResponse', text: text }); + } + function addMsg(text, role) { const isUser = role === 'user'; const msgEl = document.createElement('div'); @@ -1110,12 +1156,22 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn body.innerHTML = fmt(text); } - const copyBtn = document.createElement('button'); - copyBtn.className = 'copy-btn'; copyBtn.innerText = 'πŸ“‹ Copy'; - copyBtn.onclick = (e) => { e.stopPropagation(); copyToClipboard(msgEl._raw, copyBtn); }; - msgEl.appendChild(copyBtn); + const actions = document.createElement('div'); + actions.className = 'msg-actions'; + const copyBtn = document.createElement('button'); + copyBtn.className = 'action-btn'; copyBtn.innerText = 'πŸ“‹ Copy'; + copyBtn.onclick = (e) => { e.stopPropagation(); copyToClipboard(msgEl._raw, copyBtn); }; + + const exportBtn = document.createElement('button'); + exportBtn.className = 'action-btn'; exportBtn.innerText = 'πŸ’Ύ Export'; + exportBtn.onclick = (e) => { e.stopPropagation(); exportToMD(msgEl._raw); }; + + actions.appendChild(copyBtn); + actions.appendChild(exportBtn); + msgEl.appendChild(head); msgEl.appendChild(body); + msgEl.appendChild(actions); chat.appendChild(msgEl); chat.scrollTop = chat.scrollHeight; return { body, msgEl }; } @@ -1143,11 +1199,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn break; case 'restoreHistory': case 'sessionLoaded': - const history = msg.type === 'sessionLoaded' ? msg.value.history : msg.value; + const historyData = msg.type === 'sessionLoaded' ? msg.value : msg; + const history = Array.isArray(historyData.history) ? historyData.history : (Array.isArray(historyData) ? historyData : []); + if (history && history.length > 0) { chat.innerHTML = ''; history.forEach(m => addMsg(m.content, m.role === 'assistant' ? 'assistant' : 'user')); } + if (historyData.negativePrompt !== undefined) { + negativePrompt.value = historyData.negativePrompt; + } historyOverlay.classList.remove('visible'); break; case 'clearChat': @@ -1196,11 +1257,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn agentSel.innerHTML = ''; msg.value.forEach(a => { const o = document.createElement('option'); o.value = a.path; o.innerText = a.name; + if (a.path === msg.selected) o.selected = true; agentSel.appendChild(o); }); + if (msg.selected && msg.selected !== 'none') { + vscode.postMessage({ type: 'getAgentContent', path: msg.selected }); + } break; case 'agentContent': agentPrompt.value = msg.value; + negativePrompt.value = msg.negativePrompt || ''; break; case 'error': thinkingBar.classList.remove('active'); sendBtn.disabled = false; @@ -1304,7 +1370,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn vscode.postMessage({ type: 'updateAgent', path: agentSel.value, - content: agentPrompt.value + content: agentPrompt.value, + negativePrompt: negativePrompt.value.trim() }); } };