feat: Add Export to MD feature, persistent agent selection, and fix export bug (v2.2.67)

This commit is contained in:
2026-04-29 13:38:14 +09:00
parent 04c8f17cb7
commit f8a57cfbb0
6 changed files with 111 additions and 50 deletions
+2 -2
View File
@@ -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
+8
View File
@@ -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}.`);
});
+89 -22
View File
@@ -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<string>(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<string>(`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 = '<option value="none">No Agent</option>';
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()
});
}
};