From e6d34a5cb67e69ca024cbaff98508d104dba31a1 Mon Sep 17 00:00:00 2001 From: yesung Date: Tue, 28 Apr 2026 10:17:04 +0900 Subject: [PATCH] feat: implement Agentic Skill and Negative Prompt injection --- package-lock.json | 4 +- src/agent.ts | 10 +++- src/sidebarProvider.ts | 111 +++++++++++++++++++++++++++++++++++++++-- task_plan.md | 86 +++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 task_plan.md diff --git a/package-lock.json b/package-lock.json index ceb6ec0..8780f23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "g1nation", - "version": "2.2.47", + "version": "2.2.57", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "g1nation", - "version": "2.2.47", + "version": "2.2.57", "license": "MIT", "dependencies": { "marked": "^18.0.2" diff --git a/src/agent.ts b/src/agent.ts index 5a9f723..26b500e 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -85,7 +85,9 @@ export class AgentExecutor { visionContent?: any[], temperature?: number, systemPrompt?: string, - runId?: number + runId?: number, + agentSkillContext?: string, + negativePrompt?: string } ) { const { @@ -191,7 +193,11 @@ export class AgentExecutor { const internetCtx = internetEnabled ? `\n\n[CRITICAL: INTERNET ACCESS ENABLED]\nYou can use to search. Current time: ${new Date().toLocaleString()}` : ''; - const fullSystemPrompt = `${systemPrompt}${internetCtx}\n\n[CONTEXT]\n${brainContext}\n${contextBlock}`; + + const agentSkillCtx = options.agentSkillContext ? `\n\n[AGENT PERSONA & SKILLS]\n${options.agentSkillContext}` : ''; + const negativeCtx = options.negativePrompt ? `\n\n[STRICT NEGATIVE PROMPT - DO NOT DO THIS]\n${options.negativePrompt}` : ''; + + 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/sidebarProvider.ts b/src/sidebarProvider.ts index 52e54fb..83e6ea8 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -89,6 +89,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn case 'getModels': await this._sendModels(); break; + case 'getAgents': + await this._sendAgentsList(); + break; + case 'createAgent': + await this._createAgent(); + break; case 'newChat': this._currentSessionId = null; this._currentSessionBrainId = getActiveBrainProfile().id; @@ -560,16 +566,79 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn }); } + private _getAgentsDir(): string { + const defaultPath = 'E:\\Wiki\\Agent\\.agent\\skills'; + if (fs.existsSync(defaultPath)) return defaultPath; + + const workspaceFolders = vscode.workspace.workspaceFolders; + if (workspaceFolders) { + const localPath = path.join(workspaceFolders[0].uri.fsPath, '.agent', 'skills'); + if (!fs.existsSync(localPath)) { + fs.mkdirSync(localPath, { recursive: true }); + } + return localPath; + } + return ''; + } + + private async _sendAgentsList() { + if (!this._view) return; + const dir = this._getAgentsDir(); + const agents = []; + if (dir && fs.existsSync(dir)) { + const files = fs.readdirSync(dir); + for (const f of files) { + if (f.endsWith('.md')) { + agents.push({ name: f.replace('.md', ''), path: path.join(dir, f) }); + } + } + } + this._view.webview.postMessage({ type: 'agentsList', value: agents }); + } + + private async _createAgent() { + const name = await vscode.window.showInputBox({ + prompt: 'Name of the new Agent (e.g., frontend_expert)', + placeHolder: 'Agent name...' + }); + if (!name) return; + + const safeName = name.trim().replace(/[^a-zA-Z0-9_\\-\\u3131-\\uD79D]/g, '_'); + if (!safeName) return; + + const dir = this._getAgentsDir(); + if (!dir) { + vscode.window.showErrorMessage('Agent directory could not be determined.'); + return; + } + + const filePath = path.join(dir, `${safeName}.md`); + if (!fs.existsSync(filePath)) { + fs.writeFileSync(filePath, `# Agent Persona: ${safeName}\\n\\nAdd your instructions here...\\n`, 'utf8'); + } + + const doc = await vscode.workspace.openTextDocument(filePath); + await vscode.window.showTextDocument(doc); + await this._sendAgentsList(); + } + private async _handlePrompt(data: any) { if (!this._view) return; - const { value, model, internet, files } = data; + const { value, model, internet, files, agentFile, negativePrompt } = data; this._currentSessionBrainId = getActiveBrainProfile().id; + let agentSkillContext = undefined; + if (agentFile && fs.existsSync(agentFile)) { + agentSkillContext = fs.readFileSync(agentFile, 'utf8'); + } + try { await this._agent.handlePrompt(value, model, { internetEnabled: internet, - visionContent: files // Agent seems to handle files via visionContent + visionContent: files, + agentSkillContext, + negativePrompt }); } catch (error: any) { logError('Prompt handling failed in sidebar provider.', { error: error?.message || String(error), promptPreview: summarizeText(value || '', 200) }); @@ -905,6 +974,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
+ + @@ -930,6 +1001,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
+
@@ -959,6 +1033,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn const attachBtn = document.getElementById('attachBtn'); const fileInput = document.getElementById('fileInput'); const attachPreview = document.getElementById('attachPreview'); + const agentSel = document.getElementById('agentSel'); + const addAgentBtn = document.getElementById('addAgentBtn'); + const agentConfigPanel = document.getElementById('agentConfigPanel'); + const negativePrompt = document.getElementById('negativePrompt'); let streamBody = null; let internetEnabled = false; @@ -1078,6 +1156,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn statusLabel.innerText = msg.value; thinkingBar.classList.add('active'); setTimeout(() => { thinkingBar.classList.remove('active'); }, 3000); break; + case 'agentsList': + agentSel.innerHTML = ''; + msg.value.forEach(a => { + const o = document.createElement('option'); o.value = a.path; o.innerText = a.name; + agentSel.appendChild(o); + }); + break; case 'error': thinkingBar.classList.remove('active'); sendBtn.disabled = false; addMsg(msg.value, 'error'); @@ -1114,7 +1199,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn const val = input.value.trim(); if (!val && pendingFiles.length === 0) return; addMsg(val || (pendingFiles.length > 0 ? \`[Sent \${pendingFiles.length} files]\` : ''), 'user'); - vscode.postMessage({ type: 'prompt', value: val, model: modelSel.value, internet: internetEnabled, files: pendingFiles.length > 0 ? pendingFiles : undefined }); + vscode.postMessage({ + type: 'prompt', + value: val, + model: modelSel.value, + internet: internetEnabled, + files: pendingFiles.length > 0 ? pendingFiles : undefined, + agentFile: agentSel.value === 'none' ? undefined : agentSel.value, + negativePrompt: negativePrompt.value.trim() || undefined + }); input.value = ''; input.style.height = 'auto'; pendingFiles = []; renderAttachments(); sendBtn.disabled = true; thinkingBar.classList.add('active'); } @@ -1147,7 +1240,19 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn } }; + agentSel.onchange = () => { + if (agentSel.value !== 'none') { + agentConfigPanel.style.display = 'block'; + } else { + agentConfigPanel.style.display = 'none'; + negativePrompt.value = ''; + } + }; + + addAgentBtn.onclick = () => vscode.postMessage({ type: 'createAgent' }); + vscode.postMessage({ type: 'getModels' }); + vscode.postMessage({ type: 'getAgents' }); vscode.postMessage({ type: 'ready' }); diff --git a/task_plan.md b/task_plan.md new file mode 100644 index 0000000..1874148 --- /dev/null +++ b/task_plan.md @@ -0,0 +1,86 @@ +# ๐Ÿ“‹ Task Plan: Agentic Skill & Negative Prompt Integration + +## 1. ๊ฐœ์š” (Overview) +ConnectAI(G1nation) ์ต์Šคํ…์…˜์˜ ์‚ฌ์ด๋“œ๋ฐ”(Webview)์— 'Agentic Skill(์ž์œจ ์—์ด์ „ํŠธ ์Šคํ‚ฌ)' ์„ ํƒ ๊ธฐ๋Šฅ๊ณผ 'Negative Prompt(๊ธˆ์ง€์–ด/๊ธˆ์ง€ํ–‰๋™)' ์ž…๋ ฅ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. +์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ์ง์ ‘ ์ƒˆ๋กœ์šด ์—์ด์ „ํŠธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด, ์ด๋ฅผ ์ง€์ •๋œ ๋””๋ ‰ํ† ๋ฆฌ์— Markdown(.md) ํŒŒ์ผ๋กœ ์˜๊ตฌ ์ €์žฅํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ ์—์ด์ „ํŠธ ์Šคํ‚ฌ๊ณผ ๋„ค๊ฑฐํ‹ฐ๋ธŒ ํ”„๋กฌํ”„ํŠธ๋Š” LLM ์งˆ์˜ ์‹œ System Prompt๋กœ ๋™์  ๋ณ‘ํ•ฉ(Injection)๋ฉ๋‹ˆ๋‹ค. + +## 2. ๊ธฐ์ˆ  ์Šคํƒ (Tech Stack) +- **Frontend (Webview)**: HTML, Vanilla JavaScript, VS Code Webview API, TailwindCSS (๊ธฐ์กด ์Šคํƒ€์ผ๋ง ๊ธฐ์ค€) +- **Backend (Extension)**: TypeScript, Node.js `fs` (File System), `path` ๋ชจ๋“ˆ +- **Data Storage**: ๋กœ์ปฌ ํŒŒ์ผ ์‹œ์Šคํ…œ (Markdown `.md` ํŒŒ์ผ) +- **Target Path**: `E:\Wiki\Agent\.agent\skills\` (๋˜๋Š” VSCode Workspace ์„ค์ •์— ๋”ฐ๋ฅธ ์ƒ๋Œ€ ๊ฒฝ๋กœ, ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํ™œ์šฉ) + +## 3. UI/UX ์ƒ์„ธ ์„ค๊ณ„ (์„ธ๋ฐ€ํ•œ ํ…์ŠคํŠธ ๋ฌ˜์‚ฌ) +์‚ฌ์ด๋“œ๋ฐ”(`sidebarProvider.ts` ๋‚ด๋ถ€ HTML ํ…œํ”Œ๋ฆฟ)์˜ ํ˜•ํƒœ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝ/์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. + +1. **Agentic Skill ์„น์…˜** + - **๋ผ๋ฒจ**: `Agentic Skill` + - **๋“œ๋กญ๋‹ค์šด (``)**: ์—์ด์ „ํŠธ์˜ ์ด๋ฆ„ (ํŒŒ์ผ๋ช…์œผ๋กœ ์‚ฌ์šฉ๋จ). + - **Agent Prompt (`