mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-15 10:01:31 +00:00
Compare commits
1 Commits
fix/replac
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31f28a2c18 |
@@ -1,8 +1,12 @@
|
|||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
|
import crossSpawn from 'cross-spawn';
|
||||||
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
||||||
import { cursorAdapter } from './providers/cursor/adapter.js';
|
import { cursorAdapter } from './providers/cursor/adapter.js';
|
||||||
import { createNormalizedMessage } from './providers/types.js';
|
import { createNormalizedMessage } from './providers/types.js';
|
||||||
|
|
||||||
|
// Use cross-spawn on Windows for better command execution
|
||||||
|
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
||||||
|
|
||||||
let activeCursorProcesses = new Map(); // Track active processes by session ID
|
let activeCursorProcesses = new Map(); // Track active processes by session ID
|
||||||
|
|
||||||
const WORKSPACE_TRUST_PATTERNS = [
|
const WORKSPACE_TRUST_PATTERNS = [
|
||||||
@@ -118,7 +122,7 @@ async function spawnCursor(command, options = {}, ws) {
|
|||||||
console.log('Working directory:', workingDir);
|
console.log('Working directory:', workingDir);
|
||||||
console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
|
console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
|
||||||
|
|
||||||
const cursorProcess = spawn('cursor-agent', args, {
|
const cursorProcess = spawnFunction('cursor-agent', args, {
|
||||||
cwd: workingDir,
|
cwd: workingDir,
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
env: { ...process.env } // Inherit all environment variables
|
env: { ...process.env } // Inherit all environment variables
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
|
import crossSpawn from 'cross-spawn';
|
||||||
|
|
||||||
|
// Use cross-spawn on Windows for correct .cmd resolution (same pattern as cursor-cli.js)
|
||||||
|
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
@@ -164,7 +168,7 @@ async function spawnGemini(command, options = {}, ws) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const geminiProcess = spawn(spawnCmd, spawnArgs, {
|
const geminiProcess = spawnFunction(spawnCmd, spawnArgs, {
|
||||||
cwd: workingDir,
|
cwd: workingDir,
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
env: { ...process.env } // Inherit all environment variables
|
env: { ...process.env } // Inherit all environment variables
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import os from 'os';
|
|||||||
import http from 'http';
|
import http from 'http';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import pty from 'node-pty';
|
import pty from 'node-pty';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import mime from 'mime-types';
|
import mime from 'mime-types';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import express from 'express';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { spawn } from 'cross-spawn';
|
|
||||||
import sqlite3 from 'sqlite3';
|
import sqlite3 from 'sqlite3';
|
||||||
import { open } from 'sqlite';
|
import { open } from 'sqlite';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
@@ -578,221 +577,4 @@ router.get('/sessions', async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/cursor/sessions/:sessionId - Get specific Cursor session from SQLite
|
|
||||||
router.get('/sessions/:sessionId', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { sessionId } = req.params;
|
|
||||||
const { projectPath } = req.query;
|
|
||||||
|
|
||||||
// Calculate cwdID hash for the project path
|
|
||||||
const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex');
|
|
||||||
const storeDbPath = path.join(os.homedir(), '.cursor', 'chats', cwdId, sessionId, 'store.db');
|
|
||||||
|
|
||||||
|
|
||||||
// Open SQLite database
|
|
||||||
const db = await open({
|
|
||||||
filename: storeDbPath,
|
|
||||||
driver: sqlite3.Database,
|
|
||||||
mode: sqlite3.OPEN_READONLY
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get all blobs to build the DAG structure
|
|
||||||
const allBlobs = await db.all(`
|
|
||||||
SELECT rowid, id, data FROM blobs
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Build the DAG structure from parent-child relationships
|
|
||||||
const blobMap = new Map(); // id -> blob data
|
|
||||||
const parentRefs = new Map(); // blob id -> [parent blob ids]
|
|
||||||
const childRefs = new Map(); // blob id -> [child blob ids]
|
|
||||||
const jsonBlobs = []; // Clean JSON messages
|
|
||||||
|
|
||||||
for (const blob of allBlobs) {
|
|
||||||
blobMap.set(blob.id, blob);
|
|
||||||
|
|
||||||
// Check if this is a JSON blob (actual message) or protobuf (DAG structure)
|
|
||||||
if (blob.data && blob.data[0] === 0x7B) { // Starts with '{' - JSON blob
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(blob.data.toString('utf8'));
|
|
||||||
jsonBlobs.push({ ...blob, parsed });
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to parse JSON blob:', blob.rowid);
|
|
||||||
}
|
|
||||||
} else if (blob.data) { // Protobuf blob - extract parent references
|
|
||||||
const parents = [];
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
// Scan for parent references (0x0A 0x20 followed by 32-byte hash)
|
|
||||||
while (i < blob.data.length - 33) {
|
|
||||||
if (blob.data[i] === 0x0A && blob.data[i+1] === 0x20) {
|
|
||||||
const parentHash = blob.data.slice(i+2, i+34).toString('hex');
|
|
||||||
if (blobMap.has(parentHash)) {
|
|
||||||
parents.push(parentHash);
|
|
||||||
}
|
|
||||||
i += 34;
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parents.length > 0) {
|
|
||||||
parentRefs.set(blob.id, parents);
|
|
||||||
// Update child references
|
|
||||||
for (const parentId of parents) {
|
|
||||||
if (!childRefs.has(parentId)) {
|
|
||||||
childRefs.set(parentId, []);
|
|
||||||
}
|
|
||||||
childRefs.get(parentId).push(blob.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform topological sort to get chronological order
|
|
||||||
const visited = new Set();
|
|
||||||
const sorted = [];
|
|
||||||
|
|
||||||
// DFS-based topological sort
|
|
||||||
function visit(nodeId) {
|
|
||||||
if (visited.has(nodeId)) return;
|
|
||||||
visited.add(nodeId);
|
|
||||||
|
|
||||||
// Visit all parents first (dependencies)
|
|
||||||
const parents = parentRefs.get(nodeId) || [];
|
|
||||||
for (const parentId of parents) {
|
|
||||||
visit(parentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this node after all its parents
|
|
||||||
const blob = blobMap.get(nodeId);
|
|
||||||
if (blob) {
|
|
||||||
sorted.push(blob);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with nodes that have no parents (roots)
|
|
||||||
for (const blob of allBlobs) {
|
|
||||||
if (!parentRefs.has(blob.id)) {
|
|
||||||
visit(blob.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visit any remaining nodes (disconnected components)
|
|
||||||
for (const blob of allBlobs) {
|
|
||||||
visit(blob.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now extract JSON messages in the order they appear in the sorted DAG
|
|
||||||
const messageOrder = new Map(); // JSON blob id -> order index
|
|
||||||
let orderIndex = 0;
|
|
||||||
|
|
||||||
for (const blob of sorted) {
|
|
||||||
// Check if this blob references any JSON messages
|
|
||||||
if (blob.data && blob.data[0] !== 0x7B) { // Protobuf blob
|
|
||||||
// Look for JSON blob references
|
|
||||||
for (const jsonBlob of jsonBlobs) {
|
|
||||||
try {
|
|
||||||
const jsonIdBytes = Buffer.from(jsonBlob.id, 'hex');
|
|
||||||
if (blob.data.includes(jsonIdBytes)) {
|
|
||||||
if (!messageOrder.has(jsonBlob.id)) {
|
|
||||||
messageOrder.set(jsonBlob.id, orderIndex++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Skip if can't convert ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort JSON blobs by their appearance order in the DAG
|
|
||||||
const sortedJsonBlobs = jsonBlobs.sort((a, b) => {
|
|
||||||
const orderA = messageOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
||||||
const orderB = messageOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
||||||
if (orderA !== orderB) return orderA - orderB;
|
|
||||||
// Fallback to rowid if not in order map
|
|
||||||
return a.rowid - b.rowid;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use sorted JSON blobs
|
|
||||||
const blobs = sortedJsonBlobs.map((blob, idx) => ({
|
|
||||||
...blob,
|
|
||||||
sequence_num: idx + 1,
|
|
||||||
original_rowid: blob.rowid
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Get metadata from meta table
|
|
||||||
const metaRows = await db.all(`
|
|
||||||
SELECT key, value FROM meta
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Parse metadata
|
|
||||||
let metadata = {};
|
|
||||||
for (const row of metaRows) {
|
|
||||||
if (row.value) {
|
|
||||||
try {
|
|
||||||
// Try to decode as hex-encoded JSON
|
|
||||||
const hexMatch = row.value.toString().match(/^[0-9a-fA-F]+$/);
|
|
||||||
if (hexMatch) {
|
|
||||||
const jsonStr = Buffer.from(row.value, 'hex').toString('utf8');
|
|
||||||
metadata[row.key] = JSON.parse(jsonStr);
|
|
||||||
} else {
|
|
||||||
metadata[row.key] = row.value.toString();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
metadata[row.key] = row.value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract messages from sorted JSON blobs
|
|
||||||
const messages = [];
|
|
||||||
for (const blob of blobs) {
|
|
||||||
try {
|
|
||||||
// We already parsed JSON blobs earlier
|
|
||||||
const parsed = blob.parsed;
|
|
||||||
|
|
||||||
if (parsed) {
|
|
||||||
// Filter out ONLY system messages at the server level
|
|
||||||
// Check both direct role and nested message.role
|
|
||||||
const role = parsed?.role || parsed?.message?.role;
|
|
||||||
if (role === 'system') {
|
|
||||||
continue; // Skip only system messages
|
|
||||||
}
|
|
||||||
messages.push({
|
|
||||||
id: blob.id,
|
|
||||||
sequence: blob.sequence_num,
|
|
||||||
rowid: blob.original_rowid,
|
|
||||||
content: parsed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Skip blobs that cause errors
|
|
||||||
console.log(`Skipping blob ${blob.id}: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.close();
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
session: {
|
|
||||||
id: sessionId,
|
|
||||||
projectPath: projectPath,
|
|
||||||
messages: messages,
|
|
||||||
metadata: metadata,
|
|
||||||
cwdId: cwdId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error reading Cursor session:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: 'Failed to read Cursor session',
|
|
||||||
details: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { extractProjectDirectory } from '../projects.js';
|
import { extractProjectDirectory } from '../projects.js';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from 'path';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { addProjectManually } from '../projects.js';
|
import { addProjectManually } from '../projects.js';
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import express from 'express';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import express from 'express';
|
|||||||
import { userDb } from '../database/db.js';
|
import { userDb } from '../database/db.js';
|
||||||
import { authenticateToken } from '../middleware/auth.js';
|
import { authenticateToken } from '../middleware/auth.js';
|
||||||
import { getSystemGitConfig } from '../utils/gitConfig.js';
|
import { getSystemGitConfig } from '../utils/gitConfig.js';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
function spawnAsync(command, args) {
|
function spawnAsync(command, args) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
const PLUGINS_DIR = path.join(os.homedir(), '.claude-code-ui', 'plugins');
|
const PLUGINS_DIR = path.join(os.homedir(), '.claude-code-ui', 'plugins');
|
||||||
const PLUGINS_CONFIG_PATH = path.join(os.homedir(), '.claude-code-ui', 'plugins.json');
|
const PLUGINS_CONFIG_PATH = path.join(os.homedir(), '.claude-code-ui', 'plugins.json');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { spawn } from 'cross-spawn';
|
import { spawn } from 'child_process';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { scanPlugins, getPluginsConfig, getPluginDir } from './plugin-loader.js';
|
import { scanPlugins, getPluginsConfig, getPluginDir } from './plugin-loader.js';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user