Compare commits

...

5 Commits

Author SHA1 Message Date
Simos Mikelatos
9c85ddc807 Merge branch 'main' into fix/replace-all-spawn-with-cross-spawn 2026-04-14 23:52:44 +02:00
Simos Mikelatos
8ff5f35c05 Update model constants for Opus and Gemini versions 2026-04-14 23:06:43 +02:00
Haile
641304242d fix(version-upgrade-modal): implement reload countdown and update UI messages (#655)
Co-authored-by: Haileyesus <something@gmail.com>
2026-04-14 23:02:20 +02:00
Haileyesus
3bbd56e8e9 fix: remove unnecessary child_process imports 2026-04-10 16:50:50 +03:00
Haileyesus
11733918e5 fix: replace child_process with cross-spawn 2026-04-10 16:37:47 +03:00
17 changed files with 54 additions and 32 deletions

View File

@@ -1,12 +1,8 @@
import { spawn } from 'child_process';
import crossSpawn from 'cross-spawn';
import { spawn } from 'cross-spawn';
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
import { cursorAdapter } from './providers/cursor/adapter.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
const WORKSPACE_TRUST_PATTERNS = [
@@ -122,7 +118,7 @@ async function spawnCursor(command, options = {}, ws) {
console.log('Working directory:', workingDir);
console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
const cursorProcess = spawnFunction('cursor-agent', args, {
const cursorProcess = spawn('cursor-agent', args, {
cwd: workingDir,
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env } // Inherit all environment variables

View File

@@ -1,8 +1,4 @@
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 { spawn } from 'cross-spawn';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
@@ -168,7 +164,7 @@ async function spawnGemini(command, options = {}, ws) {
}
return new Promise((resolve, reject) => {
const geminiProcess = spawnFunction(spawnCmd, spawnArgs, {
const geminiProcess = spawn(spawnCmd, spawnArgs, {
cwd: workingDir,
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env } // Inherit all environment variables

View File

@@ -39,7 +39,7 @@ import os from 'os';
import http from 'http';
import cors from 'cors';
import { promises as fsPromises } from 'fs';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import pty from 'node-pty';
import fetch from 'node-fetch';
import mime from 'mime-types';

View File

@@ -1,5 +1,5 @@
import express from 'express';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import path from 'path';
import os from 'os';
import { promises as fs } from 'fs';

View File

@@ -1,5 +1,5 @@
import express from 'express';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';

View File

@@ -1,5 +1,5 @@
import express from 'express';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';

View File

@@ -2,7 +2,7 @@ import express from 'express';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import crypto from 'crypto';
@@ -795,4 +795,4 @@ router.get('/sessions/:sessionId', async (req, res) => {
}
});
export default router;
export default router;

View File

@@ -1,5 +1,5 @@
import express from 'express';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import path from 'path';
import { promises as fs } from 'fs';
import { extractProjectDirectory } from '../projects.js';

View File

@@ -4,7 +4,7 @@ import path from 'path';
import os from 'os';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
const router = express.Router();
const __filename = fileURLToPath(import.meta.url);
@@ -549,4 +549,4 @@ function parseClaudeGetOutput(output) {
}
}
export default router;
export default router;

View File

@@ -1,7 +1,7 @@
import express from 'express';
import { promises as fs } from 'fs';
import path from 'path';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import os from 'os';
import { addProjectManually } from '../projects.js';

View File

@@ -12,7 +12,7 @@ import express from 'express';
import fs from 'fs';
import path from 'path';
import { promises as fsPromises } from 'fs';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import os from 'os';

View File

@@ -2,7 +2,7 @@ import express from 'express';
import { userDb } from '../database/db.js';
import { authenticateToken } from '../middleware/auth.js';
import { getSystemGitConfig } from '../utils/gitConfig.js';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
const router = express.Router();

View File

@@ -1,4 +1,4 @@
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
function spawnAsync(command, args) {
return new Promise((resolve, reject) => {

View File

@@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
import os from 'os';
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
const PLUGINS_DIR = path.join(os.homedir(), '.claude-code-ui', 'plugins');
const PLUGINS_CONFIG_PATH = path.join(os.homedir(), '.claude-code-ui', 'plugins.json');

View File

@@ -1,4 +1,4 @@
import { spawn } from 'child_process';
import { spawn } from 'cross-spawn';
import path from 'path';
import { scanPlugins, getPluginsConfig, getPluginDir } from './plugin-loader.js';

View File

@@ -18,9 +18,10 @@ export const CLAUDE_MODELS = {
{ value: "haiku", label: "Haiku" },
{ value: "opusplan", label: "Opus Plan" },
{ value: "sonnet[1m]", label: "Sonnet [1M]" },
{ value: "opus[1m]", label: "Opus [1M]" },
],
DEFAULT: "sonnet",
DEFAULT: "opus",
};
/**
@@ -58,6 +59,7 @@ export const CURSOR_MODELS = {
export const CODEX_MODELS = {
OPTIONS: [
{ value: "gpt-5.4", label: "GPT-5.4" },
{ value: "gpt-5.4-mini", label: "GPT-5.4 mini" },
{ value: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
{ value: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
{ value: "gpt-5.2", label: "GPT-5.2" },
@@ -88,5 +90,5 @@ export const GEMINI_MODELS = {
},
],
DEFAULT: "gemini-2.5-flash",
DEFAULT: "gemini-3.1-pro-preview",
};

View File

@@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { authenticatedFetch } from "../../../utils/api";
import { ReleaseInfo } from "../../../types/sharedTypes";
@@ -15,6 +15,8 @@ interface VersionUpgradeModalProps {
installMode: InstallMode;
}
const RELOAD_COUNTDOWN_START = 30;
export function VersionUpgradeModal({
isOpen,
onClose,
@@ -32,10 +34,30 @@ export function VersionUpgradeModal({
const [isUpdating, setIsUpdating] = useState(false);
const [updateOutput, setUpdateOutput] = useState('');
const [updateError, setUpdateError] = useState('');
const [reloadCountdown, setReloadCountdown] = useState<number | null>(null);
useEffect(() => {
if (!IS_PLATFORM || reloadCountdown === null || reloadCountdown <= 0) {
return;
}
const timeoutId = window.setTimeout(() => {
setReloadCountdown((previousCountdown) => {
if (previousCountdown === null) {
return null;
}
return Math.max(previousCountdown - 1, 0);
});
}, 1000);
return () => window.clearTimeout(timeoutId);
}, [reloadCountdown]);
const handleUpdateNow = useCallback(async () => {
setIsUpdating(true);
setUpdateOutput('Starting update...\n');
setReloadCountdown(IS_PLATFORM ? RELOAD_COUNTDOWN_START : null);
setUpdateError('');
try {
@@ -49,8 +71,7 @@ export function VersionUpgradeModal({
if (response.ok) {
setUpdateOutput(prev => prev + data.output + '\n');
setUpdateOutput(prev => prev + '\n✅ Update completed successfully!\n');
const text = IS_PLATFORM ? 'Please refresh the page after 5 seconds to load the new version. If that doesn\'t work, RESTART the environment.' : 'Please restart the server to apply changes.';
setUpdateOutput(prev => prev + text + '\n');
setUpdateOutput(prev => prev + 'Please restart the server to apply changes.' + '\n');
} else {
setUpdateError(data.error || 'Update failed');
setUpdateOutput(prev => prev + '\n❌ Update failed: ' + (data.error || 'Unknown error') + '\n');
@@ -147,6 +168,13 @@ export function VersionUpgradeModal({
<div className="max-h-48 overflow-y-auto rounded-lg border border-gray-700 bg-gray-900 p-4 dark:bg-gray-950">
<pre className="whitespace-pre-wrap font-mono text-xs text-green-400">{updateOutput}</pre>
</div>
{IS_PLATFORM && reloadCountdown !== null && (
<div className="rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-700 dark:border-blue-900/40 dark:bg-blue-900/20 dark:text-blue-200">
{reloadCountdown === 0
? 'Refresh the page now. If that doesn\'t work, RESTART the environment.'
: `Refresh the page in ${reloadCountdown} ${reloadCountdown === 1 ? 'second' : 'seconds'}. If that doesn\'t work, RESTART the environment.`}
</div>
)}
{updateError && (
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900/40 dark:bg-red-900/20 dark:text-red-200">
{updateError}