Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/vsix-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ jobs:
working-directory: vscode-extension
run: npm install

- name: Lint extension source files
run: |
ERRORS=0
for file in vscode-extension/extension.js vscode-extension/agent-bridge.mjs; do
if [ -f "$file" ]; then
if ! node --check "$file" 2>/tmp/lint-err.txt; then
echo "::error file=${file}::Syntax error in ${file}"
cat /tmp/lint-err.txt
ERRORS=$((ERRORS + 1))
else
echo "::notice::${file} — OK"
fi
fi
done
if [ "$ERRORS" -gt 0 ]; then
echo "::error::${ERRORS} extension file(s) have syntax errors"
exit 1
fi

- name: Package VSIX
working-directory: vscode-extension
run: npm run package
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ code --install-extension open-claude-code-1.2.0.vsix
- **`@claude` chat participant** — access Claude directly from VS Code's built-in Chat panel
- **Full tool access** — all 25+ agent tools (Read, Write, Edit, Bash, Glob, Grep, WebFetch, …)
- **Rich markdown + syntax highlighting** — code blocks with copy & Apply-to-file buttons
- **Copy whole answer** — ⎘ Copy button on every assistant reply copies the full response to the clipboard
- **Chat history** — History button shows all past conversations (Cursor-style panel); "New" auto-saves the current session; sessions persist across VS Code restarts
- **⚙ Settings shortcut** — gear button in the header opens the extension settings directly — no need to navigate through the marketplace
- **Streaming responses** — tokens arrive in real time with an animated cursor
- **Tool visualization** — collapsible cards showing each tool execution and result
- **`@file` context injection** — type `@filename` or click 📄 to add a file to the prompt
Expand Down Expand Up @@ -400,9 +403,15 @@ This is a **clean-room implementation** — no leaked source used. Architecture

## 🆕 What's New

### v1.2.0 — Proactive Workspace Analysis
### v1.2.0 — Chat History, Settings Button & Copy Answer

**Fix: Proactive workspace analysis for all models** _(this PR)_
**New UI features** _(this PR)_
- **⚙ Settings button** — a gear icon added to the chat header opens `openClaudeCode` settings directly; no more navigating through the Extensions marketplace
- **⎘ Copy whole answer** — each finalized assistant reply now has a `⎘ Copy` button in the message header; one click copies the full raw markdown response to the clipboard
- **Chat history panel (Cursor-style)** — clicking the new **History** header button opens a full-overlay panel listing all past sessions. Pressing **New** auto-saves the current conversation to history first. Sessions are persisted in VS Code `globalState` and survive restarts; up to 30 sessions are retained
- **Cursor-style session navigation** — click any session in the history list to view its messages read-only (with Copy buttons intact); a Back button returns to the session list

**Fix: Proactive workspace analysis for all models**
- All models now receive a strong agentic system prompt declaring the workspace `cwd` and instructing them to explore files with LS / Glob / Read / Grep / Bash before answering — never asking the user to paste code
- New `buildWorkspaceSnapshot` helper recursively walks the workspace (skipping `node_modules`, `.git`, `dist`, etc.) and returns a compact indented file tree capped at 200 entries
- New `buildWorkspaceContent` helper reads key project files (README, package.json, entry points, etc.) and returns their contents for inline injection — capped at 64 KB total
Expand Down
2 changes: 1 addition & 1 deletion v2/src/permissions/checker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function createPermissionChecker(config = {}) {
return true;
case 'auto': return true; // AI decides
case 'dontAsk': return false; // deny everything not pre-approved
case 'plan': return toolName === 'Read' || toolName === 'Glob' || toolName === 'Grep';
case 'plan': return ['Read', 'Glob', 'Grep', 'LS', 'WebFetch', 'WebSearch', 'ToolSearch'].includes(toolName);
case 'default':
default:
// In default mode, safe tools pass through
Expand Down
11 changes: 9 additions & 2 deletions vscode-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ A **Cursor-style AI coding assistant** built directly into VSCode — no termina

### 🖥️ Cursor-style Sidebar Panel (new in v1.1)
- **Dedicated activity bar icon** — opens a full chat panel in the VS Code sidebar
- **⚙ Settings button** — gear icon in the header opens the `openClaudeCode` settings section directly — no navigating through the Extensions marketplace
- **Chat history** — the **History** header button opens a Cursor-style panel listing all past sessions; sessions auto-save when you click **New** and persist across VS Code restarts (up to 30 sessions stored in `globalState`)
- **Copy whole answer** — ⎘ Copy button on every assistant reply copies the full response to the clipboard (visible on hover)
- **Rich markdown rendering** — headers, tables, bold/italic, blockquotes
- **Syntax-highlighted code blocks** — JavaScript, TypeScript, Python, Go, Rust, JSON, Bash and more
- **Copy button on every code block** — one click to copy to clipboard
Expand All @@ -25,7 +28,7 @@ A **Cursor-style AI coding assistant** built directly into VSCode — no termina
- **Model & mode selector** — switch model and permission mode directly from the UI
- **Session stats** — token count, cost estimate, and elapsed time always visible
- **Stop button** — cancel generation at any time
- **New conversation** — clear history with one click
- **New conversation** — clear history with one click (auto-saves the current session)

### 💬 `@claude` Chat Participant (VSCode built-in chat)
- Ask questions, request code changes, and run agentic tools without leaving the editor
Expand Down Expand Up @@ -128,13 +131,17 @@ Click the **✦ Claude Code** icon in the activity bar (left sidebar) to open th
- Type your message and press **Enter** to send (Shift+Enter for a new line)
- Use `@filename` to inject file contents into the prompt
- Click **📄** to pick a file from the workspace
- Click **New** to start a fresh conversation
- Click **History** to browse and reopen past conversations (sessions auto-save when you click **New**)
- Click **New** to start a fresh conversation (saves the current session first)
- Click **⚙** (gear) to open the extension settings directly from the chat panel
- Use the **Model** and **Mode** dropdowns to configure the agent

When Claude suggests code, every code block has:
- **Copy** — copy the code to clipboard
- **Apply to file…** — apply the code to the active editor (or pick a file)

Every assistant message also has a **⎘ Copy** button (visible on hover) that copies the full response text.

### @claude chat participant (VSCode built-in chat)

Open the **Chat** panel (`Ctrl+Alt+I` / `Cmd+Alt+I`) and type:
Expand Down
154 changes: 154 additions & 0 deletions vscode-extension/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,138 @@ class ClaudeCodeViewProvider {
break;
}

case 'saveSession': {
if (msg.messages && msg.messages.length > 0) {
const sessions = this._context.globalState.get('openClaudeCode.chatHistory', []);
const firstUser = msg.messages.find(m => m.type === 'user');
const title = firstUser
? firstUser.text.slice(0, 80).replace(/\n/g, ' ')
: 'Untitled conversation';
sessions.unshift({
id: Date.now().toString(),
title,
createdAt: Date.now(),
messages: msg.messages,
});
// Keep the 30 most recent sessions
if (sessions.length > 30) sessions.length = 30;
await this._context.globalState.update('openClaudeCode.chatHistory', sessions);
}
break;
}

case 'getHistory': {
const sessions = this._context.globalState.get('openClaudeCode.chatHistory', []);
const sessionList = sessions.map(s => ({
id: s.id,
title: s.title,
createdAt: s.createdAt,
messageCount: s.messages ? s.messages.length : 0,
}));
this.postMessage({ type: 'historyData', sessions: sessionList });
break;
}

case 'loadSession': {
const sessions = this._context.globalState.get('openClaudeCode.chatHistory', []);
const session = sessions.find(s => s.id === msg.id);
if (session) {
this.postMessage({ type: 'sessionData', messages: session.messages || [] });
}
break;
}

case 'renameSession': {
const sessions = this._context.globalState.get('openClaudeCode.chatHistory', []);
const sess = sessions.find(s => s.id === msg.id);
if (sess && msg.title) {
sess.title = String(msg.title).slice(0, 120);
await this._context.globalState.update('openClaudeCode.chatHistory', sessions);
const sessionList = sessions.map(s => ({
id: s.id, title: s.title, createdAt: s.createdAt,
messageCount: s.messages ? s.messages.length : 0,
}));
this.postMessage({ type: 'historyData', sessions: sessionList });
}
break;
}

case 'deleteSession': {
let sessions = this._context.globalState.get('openClaudeCode.chatHistory', []);
sessions = sessions.filter(s => s.id !== msg.id);
await this._context.globalState.update('openClaudeCode.chatHistory', sessions);
const sessionList = sessions.map(s => ({
id: s.id, title: s.title, createdAt: s.createdAt,
messageCount: s.messages ? s.messages.length : 0,
}));
this.postMessage({ type: 'historyData', sessions: sessionList });
break;
}

case 'exportConversation': {
const content = String(msg.markdown || '');
const defaultName = 'conversation-' + new Date().toISOString().slice(0, 10) + '.md';
const defaultUri = vscode.Uri.file(
path.join(
vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd(),
defaultName
)
);
const uri = await vscode.window.showSaveDialog({
defaultUri,
filters: { 'Markdown': ['md'] },
title: 'Export conversation as Markdown',
});
if (uri) {
await vscode.workspace.fs.writeFile(uri, Buffer.from(content, 'utf8'));
vscode.window.showInformationMessage('Exported to ' + path.basename(uri.fsPath));
}
break;
}

case 'getActiveFileContent': {
const editor = vscode.window.activeTextEditor;
if (editor) {
this.postMessage({
type: 'activeFileContent',
content: editor.document.getText(),
fileName: path.basename(editor.document.fileName),
});
} else {
this.postMessage({ type: 'activeFileContent', content: null, fileName: null });
}
break;
}

case 'getPinnedFiles': {
const config = vscode.workspace.getConfiguration('openClaudeCode');
const pinned = (config.get('pinnedFiles') || []).filter(p => {
try { return fs.existsSync(p); } catch { return false; }
});
this.postMessage({
type: 'pinnedFiles',
files: pinned.map(p => ({ name: path.basename(p), path: p })),
});
break;
}

case 'pinFile': {
const config = vscode.workspace.getConfiguration('openClaudeCode');
const pinned = [...(config.get('pinnedFiles') || [])];
if (msg.path && !pinned.includes(msg.path)) {
pinned.push(msg.path);
await config.update('pinnedFiles', pinned, vscode.ConfigurationTarget.Global);
}
break;
}

case 'unpinFile': {
const config = vscode.workspace.getConfiguration('openClaudeCode');
const pinned = (config.get('pinnedFiles') || []).filter(p => p !== msg.path);
await config.update('pinnedFiles', pinned, vscode.ConfigurationTarget.Global);
break;
}

default:
break;
}
Expand Down Expand Up @@ -669,6 +801,28 @@ function activate(context) {
})
);

context.subscriptions.push(
vscode.commands.registerCommand('openClaudeCode.inlineEdit', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('Open Claude Code: No active editor for inline edit.');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection.isEmpty ? undefined : selection);
const fileName = path.basename(editor.document.fileName);
await vscode.commands.executeCommand('claudeCode.chatView.focus');
if (viewProvider) {
viewProvider.postMessage({
type: 'inlineEditRequest',
selectedText,
fileName,
hasSelection: !selection.isEmpty,
});
}
})
);

// Reload bridge when settings change
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration((e) => {
Expand Down
Loading