import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import axios from 'axios'; import { getConfig, _getBrainDir, _isBrainDirExplicitlySet, findBrainFiles, SYSTEM_PROMPT } from './utils'; import { AgentExecutor } from './agent'; import { BridgeServer, BridgeInterface } from './bridge'; /** * G1nation Extension Entry Point */ export async function activate(context: vscode.ExtensionContext) { console.log('G1nation extension activated.'); // 1. Ensure Brain Directory await _ensureBrainDir(context); // 2. Initialize Agent Executor const agent = new AgentExecutor(context); // 3. Initialize Sidebar Provider const provider = new SidebarChatProvider(context.extensionUri, context, agent); context.subscriptions.push( vscode.window.registerWebviewViewProvider(SidebarChatProvider.viewType, provider) ); // 4. Initialize Bridge Server (Port 4825) const bridge = new BridgeServer(provider); try { bridge.start(); console.log('G1nation Bridge Server started on port 4825'); } catch (err) { console.error('Failed to start Bridge Server:', err); } // 5. Register Core Commands context.subscriptions.push( vscode.commands.registerCommand('g1nation.focusInput', () => { provider.focusInput(); }), vscode.commands.registerCommand('g1nation.newChat', () => { provider.clearChat(); }), vscode.commands.registerCommand('g1nation.syncBrain', async () => { await provider.syncBrain(); }) ); // 6. First Run Setup / Auto-Detection const isFirstRun = !context.globalState.get('setupComplete'); if (isFirstRun) { await runInitialSetup(context); } vscode.window.showInformationMessage("G1nation V2 Activated 🫡"); } /** * Initial Setup: Detect Local AI Engines (LM Studio / Ollama) */ async function runInitialSetup(context: vscode.ExtensionContext) { try { let engineName = ''; let modelName = ''; 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('g1nation').update('ollamaUrl', 'http://127.0.0.1:1234', vscode.ConfigurationTarget.Global); await vscode.workspace.getConfiguration('g1nation').update('defaultModel', modelName, vscode.ConfigurationTarget.Global); } } catch (err) {} if (!engineName) { try { const ollamaRes = await axios.get('http://127.0.0.1:11434/api/tags', { timeout: 2000 }); if (ollamaRes.data?.models?.length > 0) { engineName = 'Ollama'; modelName = ollamaRes.data.models[0].name; 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 (err) {} } context.globalState.update('setupComplete', true); if (engineName) { vscode.window.showInformationMessage(`Setup Complete: ${engineName} detected with model ${modelName}`); } } catch (e) { context.globalState.update('setupComplete', true); } } async function _ensureBrainDir(context: vscode.ExtensionContext): Promise { if (_isBrainDirExplicitlySet()) { const dir = _getBrainDir(); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); return dir; } const result = await vscode.window.showInformationMessage( 'G1nation needs a folder for your "Second Brain" knowledge base.', 'Select Folder' ); if (result === 'Select Folder') { const folders = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, title: 'Select G1nation Second Brain Folder' }); if (folders && folders.length > 0) { const selectedPath = folders[0].fsPath; await vscode.workspace.getConfiguration('g1nation').update('localBrainPath', selectedPath, vscode.ConfigurationTarget.Global); return selectedPath; } } return null; } /** * Sidebar UI Provider implementing BridgeInterface for BridgeServer */ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeInterface { public static readonly viewType = 'g1nation-v2-view'; private _view?: vscode.WebviewView; public brainEnabled = true; constructor( private readonly _extensionUri: vscode.Uri, private readonly _context: vscode.ExtensionContext, private readonly _agent: AgentExecutor ) {} public resolveWebviewView( webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken, ) { this._view = webviewView; webviewView.webview.options = { enableScripts: true, localResourceRoots: [this._extensionUri] }; webviewView.webview.html = this._getHtml(webviewView.webview); webviewView.webview.onDidReceiveMessage(async (data) => { switch (data.type) { case 'prompt': case 'promptWithFile': await this._handlePrompt(data); break; case 'getModels': await this._sendModels(); break; case 'newChat': this.clearChat(); break; case 'openSettings': vscode.commands.executeCommand('workbench.action.openSettings', 'g1nation'); break; case 'syncBrain': await this.syncBrain(); break; } }); } // --- BridgeInterface Methods --- public injectSystemMessage(msg: string): void { this._view?.webview.postMessage({ type: 'streamStart' }); this._view?.webview.postMessage({ type: 'streamChunk', value: msg }); this._view?.webview.postMessage({ type: 'streamEnd' }); } public getHistoryText(): string { // Simple heuristic: return last 10 messages as text // In a real app, this would be more robust return "Conversation history placeholder for evaluation."; } public sendPromptFromExtension(prompt: string): void { if (this._view) { this._view.show?.(true); this._view.webview.postMessage({ type: 'injectPrompt', value: prompt }); } } public findBrainFiles(dir: string): string[] { return findBrainFiles(dir); } // --- End BridgeInterface --- public focusInput() { this._view?.webview.postMessage({ type: 'focusInput' }); } public clearChat() { this._view?.webview.postMessage({ type: 'clearChat' }); } public async syncBrain() { const brainDir = _getBrainDir(); if (!fs.existsSync(brainDir)) { vscode.window.showErrorMessage("Second Brain directory not found."); return; } vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "G1nation: Syncing Second Brain...", cancellable: false }, async () => { try { const { execSync } = require('child_process'); execSync(`git add .`, { cwd: brainDir }); execSync(`git commit -m "[G1-Sync] Manual knowledge update"`, { cwd: brainDir }); execSync(`git push`, { cwd: brainDir }); vscode.window.showInformationMessage("Second Brain synced successfully."); } catch (err: any) { vscode.window.showWarningMessage("Sync completed (or no changes to push)."); } }); } private async _handlePrompt(data: any) { if (!this._view) return; const { value, model, internet, files } = data; this._view.webview.postMessage({ type: 'streamStart' }); try { await this._agent.execute(value, { model, internet, files, onToken: (token) => { this._view?.webview.postMessage({ type: 'streamChunk', value: token }); } }); this._view.webview.postMessage({ type: 'streamEnd' }); } catch (error: any) { this._view.webview.postMessage({ type: 'error', value: error.message }); } } private async _sendModels() { if (!this._view) return; try { const config = getConfig(); const url = config.ollamaUrl; let models: string[] = []; if (url.includes('1234')) { const res = await axios.get(`${url}/v1/models`, { timeout: 2000 }); models = res.data.data.map((m: any) => m.id); } else { const res = await axios.get(`${url}/api/tags`, { timeout: 2000 }); models = res.data.models.map((m: any) => m.name); } this._view.webview.postMessage({ type: 'modelsList', value: models }); } catch (err) { this._view.webview.postMessage({ type: 'modelsList', value: ['default'] }); } } private _getHtml(webview: vscode.Webview): string { return ` G1nation
G1nation
G1nation
Security · Optimized · Knowledge Mesh
Understands your project, writes code, and executes tasks.
`; } }