Skip to content
Open
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
14 changes: 8 additions & 6 deletions src/PixelAgentsViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as path from 'path';
import * as vscode from 'vscode';

import {
getAllProjectDirPaths,
getProjectDirPath,
launchNewTerminal,
persistAgents,
Expand Down Expand Up @@ -77,7 +78,7 @@ export class PixelAgentsViewProvider implements vscode.WebviewViewProvider {
webviewView.webview.html = getWebviewContent(webviewView.webview, this.extensionUri);

webviewView.webview.onDidReceiveMessage(async (message) => {
if (message.type === 'openClaude') {
if (message.type === 'openAgent') {
await launchNewTerminal(
this.nextAgentId,
this.nextTerminalIndex,
Expand All @@ -92,6 +93,7 @@ export class PixelAgentsViewProvider implements vscode.WebviewViewProvider {
this.projectScanTimer,
this.webview,
this.persistAgents,
message.agentType as string,
message.folderPath as string | undefined,
);
} else if (message.type === 'focusAgent') {
Expand Down Expand Up @@ -144,13 +146,13 @@ export class PixelAgentsViewProvider implements vscode.WebviewViewProvider {
}

// Ensure project scan runs even with no restored agents (to adopt external terminals)
const projectDir = getProjectDirPath();
const projectDirs = getAllProjectDirPaths();
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
console.log('[Extension] workspaceRoot:', workspaceRoot);
console.log('[Extension] projectDir:', projectDir);
if (projectDir) {
console.log('[Extension] projectDirs:', projectDirs);
if (projectDirs.length > 0) {
ensureProjectScan(
projectDir,
projectDirs,
this.knownJsonlFiles,
this.projectScanTimer,
this.activeAgentId,
Expand Down Expand Up @@ -266,7 +268,7 @@ export class PixelAgentsViewProvider implements vscode.WebviewViewProvider {
}
sendExistingAgents(this.agents, this.context, this.webview);
} else if (message.type === 'openSessionsFolder') {
const projectDir = getProjectDirPath();
const projectDir = getProjectDirPath('claude'); // Use claude as default
if (projectDir && fs.existsSync(projectDir)) {
vscode.env.openExternal(vscode.Uri.file(projectDir));
}
Expand Down
53 changes: 44 additions & 9 deletions src/agentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,33 @@ import {
import { ensureProjectScan, readNewLines, startFileWatching } from './fileWatcher.js';
import { migrateAndLoadLayout } from './layoutPersistence.js';
import { cancelPermissionTimer, cancelWaitingTimer } from './timerManager.js';
import type { AgentState, PersistedAgent } from './types.js';
import type { AgentState, AgentType, PersistedAgent } from './types.js';

export function getProjectDirPath(cwd?: string): string | null {
export function getProjectDirPath(agentType: AgentType | string, cwd?: string): string | null {
const workspacePath = cwd || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!workspacePath) return null;
const dirName = workspacePath.replace(/[^a-zA-Z0-9-]/g, '-');
const projectDir = path.join(os.homedir(), '.claude', 'projects', dirName);
console.log(`[Pixel Agents] Project dir: ${workspacePath} → ${dirName}`);

let agentFolder = '.claude';
if (agentType === 'codex') agentFolder = '.codex';
if (agentType === 'antigravity') agentFolder = '.antigravity';

const projectDir = path.join(os.homedir(), agentFolder, 'projects', dirName);
console.log(`[Pixel Agents] Project dir: ${workspacePath} → ${dirName} for ${agentType}`);
return projectDir;
}

export function getAllProjectDirPaths(cwd?: string): string[] {
const workspacePath = cwd || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!workspacePath) return [];
const dirName = workspacePath.replace(/[^a-zA-Z0-9-]/g, '-');
return [
path.join(os.homedir(), '.claude', 'projects', dirName),
path.join(os.homedir(), '.codex', 'projects', dirName),
path.join(os.homedir(), '.antigravity', 'projects', dirName),
];
}

export async function launchNewTerminal(
nextAgentIdRef: { current: number },
nextTerminalIndexRef: { current: number },
Expand All @@ -37,22 +53,38 @@ export async function launchNewTerminal(
projectScanTimerRef: { current: ReturnType<typeof setInterval> | null },
webview: vscode.Webview | undefined,
persistAgents: () => void,
agentType: string,
folderPath?: string,
): Promise<void> {
const folders = vscode.workspace.workspaceFolders;
const cwd = folderPath || folders?.[0]?.uri.fsPath;
const isMultiRoot = !!(folders && folders.length > 1);
const idx = nextTerminalIndexRef.current++;

// Format the agent name based on type
const agentNamePrefix = agentType.charAt(0).toUpperCase() + agentType.slice(1);
const terminalName =
agentType === 'claude' ? `${TERMINAL_NAME_PREFIX} #${idx}` : `${agentNamePrefix} #${idx}`;

const terminal = vscode.window.createTerminal({
name: `${TERMINAL_NAME_PREFIX} #${idx}`,
name: terminalName,
cwd,
});
terminal.show();

const sessionId = crypto.randomUUID();
terminal.sendText(`claude --session-id ${sessionId}`);

const projectDir = getProjectDirPath(cwd);
if (agentType === 'claude') {
terminal.sendText(`claude --session-id ${sessionId}`);
} else if (agentType === 'codex') {
terminal.sendText(`codex --session-id ${sessionId}`);
} else if (agentType === 'antigravity') {
terminal.sendText(`antigravity --session-id ${sessionId}`);
} else {
terminal.sendText(`${agentType} --session-id ${sessionId}`); // Fallback
}

const projectDir = getProjectDirPath(agentType, cwd);
if (!projectDir) {
console.log(`[Pixel Agents] No project dir, cannot track agent`);
return;
Expand All @@ -70,6 +102,7 @@ export async function launchNewTerminal(
terminalRef: terminal,
projectDir,
jsonlFile: expectedFile,
agentType: agentType as AgentType,
fileOffset: 0,
lineBuffer: '',
activeToolIds: new Set(),
Expand All @@ -90,7 +123,7 @@ export async function launchNewTerminal(
webview?.postMessage({ type: 'agentCreated', id, folderName });

ensureProjectScan(
projectDir,
[projectDir],
knownJsonlFiles,
projectScanTimerRef,
activeAgentIdRef,
Expand Down Expand Up @@ -186,6 +219,7 @@ export function persistAgents(
terminalName: agent.terminalRef.name,
jsonlFile: agent.jsonlFile,
projectDir: agent.projectDir,
agentType: agent.agentType,
folderName: agent.folderName,
});
}
Expand Down Expand Up @@ -225,6 +259,7 @@ export function restoreAgents(
terminalRef: terminal,
projectDir: p.projectDir,
jsonlFile: p.jsonlFile,
agentType: p.agentType || 'claude', // Fallback to claude for older versions
fileOffset: 0,
lineBuffer: '',
activeToolIds: new Set(),
Expand Down Expand Up @@ -313,7 +348,7 @@ export function restoreAgents(
// Start project scan for /clear detection
if (restoredProjectDir) {
ensureProjectScan(
restoredProjectDir,
[restoredProjectDir],
knownJsonlFiles,
projectScanTimerRef,
activeAgentIdRef,
Expand Down
51 changes: 28 additions & 23 deletions src/fileWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function readNewLines(
}

export function ensureProjectScan(
projectDir: string,
projectDirs: string[],
knownJsonlFiles: Set<string>,
projectScanTimerRef: { current: ReturnType<typeof setInterval> | null },
activeAgentIdRef: { current: number | null },
Expand All @@ -111,32 +111,36 @@ export function ensureProjectScan(
): void {
if (projectScanTimerRef.current) return;
// Seed with all existing JSONL files so we only react to truly new ones
try {
const files = fs
.readdirSync(projectDir)
.filter((f) => f.endsWith('.jsonl'))
.map((f) => path.join(projectDir, f));
for (const f of files) {
knownJsonlFiles.add(f);
for (const projectDir of projectDirs) {
try {
const files = fs
.readdirSync(projectDir)
.filter((f) => f.endsWith('.jsonl'))
.map((f) => path.join(projectDir, f));
for (const f of files) {
knownJsonlFiles.add(f);
}
} catch {
/* dir may not exist yet */
}
} catch {
/* dir may not exist yet */
}

projectScanTimerRef.current = setInterval(() => {
scanForNewJsonlFiles(
projectDir,
knownJsonlFiles,
activeAgentIdRef,
nextAgentIdRef,
agents,
fileWatchers,
pollingTimers,
waitingTimers,
permissionTimers,
webview,
persistAgents,
);
for (const projectDir of projectDirs) {
scanForNewJsonlFiles(
projectDir,
knownJsonlFiles,
activeAgentIdRef,
nextAgentIdRef,
agents,
fileWatchers,
pollingTimers,
waitingTimers,
permissionTimers,
webview,
persistAgents,
);
}
}, PROJECT_SCAN_INTERVAL_MS);
}

Expand Down Expand Up @@ -235,6 +239,7 @@ function adoptTerminalForFile(
terminalRef: terminal,
projectDir,
jsonlFile,
agentType: 'claude', // Default for adopted terminals
fileOffset: 0,
lineBuffer: '',
activeToolIds: new Set(),
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type * as vscode from 'vscode';

export type AgentType = 'claude' | 'codex' | 'antigravity';

export interface AgentState {
id: number;
terminalRef: vscode.Terminal;
projectDir: string;
jsonlFile: string;
agentType: AgentType;
fileOffset: number;
lineBuffer: string;
activeToolIds: Set<string>;
Expand All @@ -24,6 +27,7 @@ export interface PersistedAgent {
terminalName: string;
jsonlFile: string;
projectDir: string;
agentType?: AgentType;
/** Workspace folder name (only set for multi-root workspaces) */
folderName?: string;
}
2 changes: 1 addition & 1 deletion webview-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<span style={{ fontSize: '22px', color: 'var(--pixel-reset-text)' }}>Reset?</span>
<button
style={{ ...actionBarBtnStyle, background: 'var(--pixel-danger-bg)', color: '#fff' }}

Check warning on line 105 in webview-ui/src/App.tsx

View workflow job for this annotation

GitHub Actions / ci

Use shared constants or `--pixel-*` tokens instead of inline color literals
onClick={() => {
setShowResetConfirm(false);
editor.handleReset();
Expand Down Expand Up @@ -266,7 +266,7 @@

<BottomToolbar
isEditMode={editor.isEditMode}
onOpenClaude={editor.handleOpenClaude}
onOpenAgent={editor.handleOpenAgent}
onToggleEditMode={editor.handleToggleEditMode}
isDebugMode={isDebugMode}
onToggleDebugMode={handleToggleDebugMode}
Expand All @@ -286,7 +286,7 @@
transform: 'translateX(-50%)',
zIndex: 49,
background: 'var(--pixel-hint-bg)',
color: '#fff',

Check warning on line 289 in webview-ui/src/App.tsx

View workflow job for this annotation

GitHub Actions / ci

Use shared constants or `--pixel-*` tokens instead of inline color literals
fontSize: '20px',
padding: '3px 8px',
borderRadius: 0,
Expand Down Expand Up @@ -358,7 +358,7 @@
style={{
position: 'absolute',
inset: 0,
background: 'rgba(0, 0, 0, 0.7)',

Check warning on line 361 in webview-ui/src/App.tsx

View workflow job for this annotation

GitHub Actions / ci

Use shared constants or `--pixel-*` tokens instead of inline color literals
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
Expand Down Expand Up @@ -402,7 +402,7 @@
padding: '6px 24px 8px',
fontSize: '30px',
background: 'var(--pixel-accent)',
color: '#fff',

Check warning on line 405 in webview-ui/src/App.tsx

View workflow job for this annotation

GitHub Actions / ci

Use shared constants or `--pixel-*` tokens instead of inline color literals
border: '2px solid var(--pixel-accent)',
borderRadius: 0,
cursor: 'pointer',
Expand Down
Loading
Loading