diff --git a/package.json b/package.json index 75eab92..7117e8f 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.2", + "version": "2.33.3", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 07fac40..e576a18 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -147,6 +147,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn case 'deleteBrain': await this._deleteBrainProfile(data.id); break; + case 'saveWikiRaw': + await this._saveWikiRaw(); + break; case 'setBrainProfile': await this._setActiveBrainProfile(data.id); break; @@ -665,6 +668,150 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn this.injectSystemMessage(`**[Brain Deleted]** ${target.name}`); } + private async _saveWikiRaw() { + const history = this._agent.getHistory(); + if (history.length === 0) { + vscode.window.showWarningMessage('There is no conversation to save as wiki raw data.'); + return; + } + + const activeBrain = getActiveBrainProfile(); + const rawDir = path.join(activeBrain.localBrainPath, 'raw-data'); + if (!fs.existsSync(rawDir)) { + fs.mkdirSync(rawDir, { recursive: true }); + } + + const category = await vscode.window.showInputBox({ + prompt: 'Wiki raw data category', + value: 'Project Notes' + }); + if (category === undefined) return; + + const expectedValue = await vscode.window.showInputBox({ + prompt: 'Expected value or future use', + value: 'Reusable as source material for a future wiki document.' + }); + if (expectedValue === undefined) return; + + const timestamp = new Date(); + const slug = this._slugify(history.find((message) => message.role === 'user')?.content || 'conversation'); + const fileName = `${this._formatTimestampForFile(timestamp)}-${slug}.md`; + const filePath = path.join(rawDir, fileName); + const markdown = this._buildWikiRawMarkdown(history, { + category: category.trim() || 'Project Notes', + expectedValue: expectedValue.trim() || 'Reusable as source material for a future wiki document.', + activeBrainName: activeBrain.name, + activeBrainPath: activeBrain.localBrainPath, + createdAt: timestamp.toISOString() + }); + + await vscode.workspace.fs.writeFile(vscode.Uri.file(filePath), Buffer.from(markdown, 'utf8')); + vscode.window.showInformationMessage(`Wiki raw data saved: ${path.basename(filePath)}`); + this.injectSystemMessage(`**[Wiki Raw Saved]** \`${filePath}\``); + } + + private _buildWikiRawMarkdown(history: ChatMessage[], meta: { + category: string; + expectedValue: string; + activeBrainName: string; + activeBrainPath: string; + createdAt: string; + }): string { + const firstUserMessage = history.find((message) => message.role === 'user')?.content || ''; + const latestUserMessage = [...history].reverse().find((message) => message.role === 'user')?.content || ''; + const latestAssistantMessage = [...history].reverse().find((message) => message.role === 'assistant')?.content || ''; + const rationaleNotes = history + .filter((message) => message.role === 'assistant' && message.rationale) + .map((message, index) => [ + `### Process Note ${index + 1}`, + message.rationale?.problem ? `- Problem: ${message.rationale.problem}` : '', + message.rationale?.goal ? `- Goal: ${message.rationale.goal}` : '', + message.rationale?.reasoning ? `- Reasoning: ${message.rationale.reasoning}` : '' + ].filter(Boolean).join('\n')) + .join('\n\n'); + + const transcript = history + .map((message, index) => [ + `### ${index + 1}. ${message.role.toUpperCase()}`, + '', + message.content.trim() || '(empty)' + ].join('\n')) + .join('\n\n'); + + return [ + '---', + `title: "${this._escapeYamlString(this._summarizeForTitle(firstUserMessage))}"`, + `category: "${this._escapeYamlString(meta.category)}"`, + `created_at: "${meta.createdAt}"`, + `source: "ConnectAI conversation"`, + `brain: "${this._escapeYamlString(meta.activeBrainName)}"`, + 'status: raw', + '---', + '', + `# ${this._summarizeForTitle(firstUserMessage)}`, + '', + '## Category', + meta.category, + '', + '## What This Contains', + this._summarizeTextForWiki(latestAssistantMessage || firstUserMessage), + '', + '## Expected Value', + meta.expectedValue, + '', + '## Discovery Process', + rationaleNotes || 'No explicit rationale metadata was captured. Use the transcript below to reconstruct the reasoning path.', + '', + '## Conclusion', + [ + 'This conclusion was derived from the latest user request and assistant response.', + '', + `- Latest user request: ${this._summarizeTextForWiki(latestUserMessage)}`, + `- Latest assistant conclusion: ${this._summarizeTextForWiki(latestAssistantMessage)}` + ].join('\n'), + '', + '## Coding Implementation Notes', + 'Use this section to turn the raw transcript into a wiki-ready implementation note. Capture which files changed, why they changed, and what verification was run.', + '', + '## Source Brain', + `- Name: ${meta.activeBrainName}`, + `- Path: ${meta.activeBrainPath}`, + '', + '## Raw Conversation Transcript', + transcript, + '' + ].join('\n'); + } + + private _formatTimestampForFile(date: Date): string { + return date.toISOString().replace(/[:.]/g, '-').replace('T', '_').replace('Z', ''); + } + + private _slugify(value: string): string { + const slug = value + .toLowerCase() + .replace(/[^a-z0-9가-힣]+/g, '-') + .replace(/^-|-$/g, '') + .slice(0, 48); + return slug || 'conversation'; + } + + private _summarizeForTitle(value: string): string { + const normalized = value.replace(/\s+/g, ' ').trim(); + if (!normalized) return 'ConnectAI Conversation Raw Data'; + return normalized.length > 80 ? `${normalized.slice(0, 80)}...` : normalized; + } + + private _summarizeTextForWiki(value: string): string { + const normalized = value.replace(/\s+/g, ' ').trim(); + if (!normalized) return 'Not captured.'; + return normalized.length > 500 ? `${normalized.slice(0, 500)}...` : normalized; + } + + private _escapeYamlString(value: string): string { + return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + } + // --- BridgeInterface Methods --- public injectSystemMessage(msg: string): void { @@ -1083,6 +1230,19 @@ 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; } + .control-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 6px; + 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; } @@ -1589,26 +1749,29 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
Engine
-
+
+
+ + + + +
+
+
+
+ + + +
-
-
-
- - - - +
+ + + +
-
- - - -
- - -
@@ -1795,6 +1958,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn const addBrainBtn = document.getElementById('addBrainBtn'); const editBrainBtn = document.getElementById('editBrainBtn'); const deleteBrainBtn = document.getElementById('deleteBrainBtn'); + const saveWikiRawBtn = document.getElementById('saveWikiRawBtn'); const agentConfigPanel = document.getElementById('agentConfigPanel'); const agentPrompt = document.getElementById('agentPrompt'); const negativePrompt = document.getElementById('negativePrompt'); @@ -2167,6 +2331,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn const syncBrain = () => { Sound.play(550, 'sine', 0.1); vscode.postMessage({ type: 'syncBrain' }); }; document.getElementById('brainBtn').onclick = syncBrain; + saveWikiRawBtn.onclick = () => vscode.postMessage({ type: 'saveWikiRaw' }); addBrainBtn.onclick = () => vscode.postMessage({ type: 'addBrain' }); editBrainBtn.onclick = () => { if (!brainSel.value || brainSel.value === 'new') return;