mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
feat: update CLI timeout handling and add active execution state management
This commit is contained in:
@@ -175,7 +175,7 @@ export function run(argv: string[]): void {
|
|||||||
.option('--model <model>', 'Model override')
|
.option('--model <model>', 'Model override')
|
||||||
.option('--cd <path>', 'Working directory')
|
.option('--cd <path>', 'Working directory')
|
||||||
.option('--includeDirs <dirs>', 'Additional directories (--include-directories for gemini/qwen, --add-dir for codex/claude)')
|
.option('--includeDirs <dirs>', 'Additional directories (--include-directories for gemini/qwen, --add-dir for codex/claude)')
|
||||||
.option('--timeout <ms>', 'Timeout in milliseconds', '300000')
|
.option('--timeout <ms>', 'Timeout in milliseconds (0=disabled, controlled by external caller)', '0')
|
||||||
.option('--stream', 'Enable streaming output (default: non-streaming with caching)')
|
.option('--stream', 'Enable streaming output (default: non-streaming with caching)')
|
||||||
.option('--limit <n>', 'History limit')
|
.option('--limit <n>', 'History limit')
|
||||||
.option('--status <status>', 'Filter by status')
|
.option('--status <status>', 'Filter by status')
|
||||||
|
|||||||
@@ -783,7 +783,7 @@ async function execAction(positionalPrompt: string | undefined, options: CliExec
|
|||||||
model,
|
model,
|
||||||
cd,
|
cd,
|
||||||
includeDirs,
|
includeDirs,
|
||||||
timeout: timeout ? parseInt(timeout, 10) : 300000,
|
timeout: timeout ? parseInt(timeout, 10) : 0, // 0 = no internal timeout, controlled by external caller
|
||||||
resume,
|
resume,
|
||||||
id, // custom execution ID
|
id, // custom execution ID
|
||||||
noNative,
|
noNative,
|
||||||
|
|||||||
@@ -55,6 +55,28 @@ export interface RouteContext {
|
|||||||
broadcastToClients: (data: unknown) => void;
|
broadcastToClients: (data: unknown) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Active Executions State ==========
|
||||||
|
// Stores running CLI executions for state recovery when view is opened/refreshed
|
||||||
|
interface ActiveExecution {
|
||||||
|
id: string;
|
||||||
|
tool: string;
|
||||||
|
mode: string;
|
||||||
|
prompt: string;
|
||||||
|
startTime: number;
|
||||||
|
output: string;
|
||||||
|
status: 'running' | 'completed' | 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeExecutions = new Map<string, ActiveExecution>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all active CLI executions
|
||||||
|
* Used by frontend to restore state when view is opened during execution
|
||||||
|
*/
|
||||||
|
export function getActiveExecutions(): ActiveExecution[] {
|
||||||
|
return Array.from(activeExecutions.values());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle CLI routes
|
* Handle CLI routes
|
||||||
* @returns true if route was handled, false otherwise
|
* @returns true if route was handled, false otherwise
|
||||||
@@ -62,6 +84,14 @@ export interface RouteContext {
|
|||||||
export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
||||||
const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients } = ctx;
|
const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients } = ctx;
|
||||||
|
|
||||||
|
// API: Get Active CLI Executions (for state recovery)
|
||||||
|
if (pathname === '/api/cli/active' && req.method === 'GET') {
|
||||||
|
const executions = getActiveExecutions();
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ executions }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// API: CLI Tools Status
|
// API: CLI Tools Status
|
||||||
if (pathname === '/api/cli/status') {
|
if (pathname === '/api/cli/status') {
|
||||||
const status = await getCliToolsStatus();
|
const status = await getCliToolsStatus();
|
||||||
@@ -504,6 +534,17 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
|
|
||||||
const executionId = `${Date.now()}-${tool}`;
|
const executionId = `${Date.now()}-${tool}`;
|
||||||
|
|
||||||
|
// Store active execution for state recovery
|
||||||
|
activeExecutions.set(executionId, {
|
||||||
|
id: executionId,
|
||||||
|
tool,
|
||||||
|
mode: mode || 'analysis',
|
||||||
|
prompt: prompt.substring(0, 500), // Truncate for display
|
||||||
|
startTime: Date.now(),
|
||||||
|
output: '',
|
||||||
|
status: 'running'
|
||||||
|
});
|
||||||
|
|
||||||
// Broadcast execution started
|
// Broadcast execution started
|
||||||
broadcastToClients({
|
broadcastToClients({
|
||||||
type: 'CLI_EXECUTION_STARTED',
|
type: 'CLI_EXECUTION_STARTED',
|
||||||
@@ -525,11 +566,17 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
model,
|
model,
|
||||||
cd: dir || initialPath,
|
cd: dir || initialPath,
|
||||||
includeDirs,
|
includeDirs,
|
||||||
timeout: timeout || 300000,
|
timeout: timeout || 0, // 0 = no internal timeout, controlled by external caller
|
||||||
category: category || 'user',
|
category: category || 'user',
|
||||||
parentExecutionId,
|
parentExecutionId,
|
||||||
stream: true
|
stream: true
|
||||||
}, (chunk) => {
|
}, (chunk) => {
|
||||||
|
// Append chunk to active execution buffer
|
||||||
|
const activeExec = activeExecutions.get(executionId);
|
||||||
|
if (activeExec) {
|
||||||
|
activeExec.output += chunk.data || '';
|
||||||
|
}
|
||||||
|
|
||||||
broadcastToClients({
|
broadcastToClients({
|
||||||
type: 'CLI_OUTPUT',
|
type: 'CLI_OUTPUT',
|
||||||
payload: {
|
payload: {
|
||||||
@@ -540,6 +587,9 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove from active executions on completion
|
||||||
|
activeExecutions.delete(executionId);
|
||||||
|
|
||||||
// Broadcast completion
|
// Broadcast completion
|
||||||
broadcastToClients({
|
broadcastToClients({
|
||||||
type: 'CLI_EXECUTION_COMPLETED',
|
type: 'CLI_EXECUTION_COMPLETED',
|
||||||
@@ -557,6 +607,9 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
// Remove from active executions on error
|
||||||
|
activeExecutions.delete(executionId);
|
||||||
|
|
||||||
broadcastToClients({
|
broadcastToClients({
|
||||||
type: 'CLI_EXECUTION_ERROR',
|
type: 'CLI_EXECUTION_ERROR',
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
@@ -9,6 +9,55 @@ var ccwEndpointTools = [];
|
|||||||
var cliToolConfig = null; // Store loaded CLI config
|
var cliToolConfig = null; // Store loaded CLI config
|
||||||
var predefinedModels = {}; // Store predefined models per tool
|
var predefinedModels = {}; // Store predefined models per tool
|
||||||
|
|
||||||
|
// ========== Active Execution Sync ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync active CLI executions from server
|
||||||
|
* Called when view is opened to restore running execution state
|
||||||
|
*/
|
||||||
|
async function syncActiveExecutions() {
|
||||||
|
try {
|
||||||
|
var response = await fetch('/api/cli/active');
|
||||||
|
if (!response.ok) return;
|
||||||
|
|
||||||
|
var data = await response.json();
|
||||||
|
if (!data.executions || data.executions.length === 0) return;
|
||||||
|
|
||||||
|
// Restore the first active execution
|
||||||
|
var active = data.executions[0];
|
||||||
|
|
||||||
|
// Restore execution state
|
||||||
|
currentCliExecution = {
|
||||||
|
executionId: active.id,
|
||||||
|
tool: active.tool,
|
||||||
|
mode: active.mode,
|
||||||
|
startTime: active.startTime
|
||||||
|
};
|
||||||
|
cliExecutionOutput = active.output || '';
|
||||||
|
|
||||||
|
// Update UI if output panel exists
|
||||||
|
var outputPanel = document.getElementById('cli-output-panel');
|
||||||
|
var outputContent = document.getElementById('cli-output-content');
|
||||||
|
|
||||||
|
if (outputPanel && outputContent) {
|
||||||
|
outputPanel.style.display = 'block';
|
||||||
|
outputContent.textContent = cliExecutionOutput;
|
||||||
|
outputContent.scrollTop = outputContent.scrollHeight;
|
||||||
|
|
||||||
|
// Update status indicator
|
||||||
|
var statusIndicator = outputPanel.querySelector('.cli-status-indicator');
|
||||||
|
if (statusIndicator) {
|
||||||
|
statusIndicator.className = 'cli-status-indicator running';
|
||||||
|
statusIndicator.textContent = t('cli.running') || 'Running...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[CLI Manager] Restored active execution:', active.id);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[CLI Manager] Failed to sync active executions:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Navigation Helpers ==========
|
// ========== Navigation Helpers ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -437,6 +486,9 @@ async function renderCliManager() {
|
|||||||
|
|
||||||
// Initialize Lucide icons
|
// Initialize Lucide icons
|
||||||
if (window.lucide) lucide.createIcons();
|
if (window.lucide) lucide.createIcons();
|
||||||
|
|
||||||
|
// Sync active executions to restore running state
|
||||||
|
await syncActiveExecutions();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Helper Functions ==========
|
// ========== Helper Functions ==========
|
||||||
|
|||||||
@@ -1854,9 +1854,9 @@ async function selectRerankerModel(modelName) {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch between Embedding and Reranker tabs
|
* Switch between Embedding and Reranker tabs in CodexLens manager
|
||||||
*/
|
*/
|
||||||
function switchModelTab(tabName) {
|
function switchCodexLensModelTab(tabName) {
|
||||||
console.log('[CodexLens] Switching to tab:', tabName);
|
console.log('[CodexLens] Switching to tab:', tabName);
|
||||||
|
|
||||||
// Update tab buttons using direct style manipulation for reliability
|
// Update tab buttons using direct style manipulation for reliability
|
||||||
@@ -3013,10 +3013,10 @@ function buildCodexLensManagerPage(config) {
|
|||||||
// Tabs for Embedding / Reranker
|
// Tabs for Embedding / Reranker
|
||||||
'<div class="border-b border-border">' +
|
'<div class="border-b border-border">' +
|
||||||
'<div class="flex">' +
|
'<div class="flex">' +
|
||||||
'<button class="model-tab flex-1 px-4 py-2.5 text-sm font-medium border-b-2 border-primary text-primary bg-primary/5" data-tab="embedding" onclick="switchModelTab(\'embedding\')">' +
|
'<button class="model-tab flex-1 px-4 py-2.5 text-sm font-medium border-b-2 border-primary text-primary bg-primary/5" data-tab="embedding" onclick="switchCodexLensModelTab(\'embedding\')">' +
|
||||||
'<i data-lucide="layers" class="w-3.5 h-3.5 inline mr-1"></i>Embedding' +
|
'<i data-lucide="layers" class="w-3.5 h-3.5 inline mr-1"></i>Embedding' +
|
||||||
'</button>' +
|
'</button>' +
|
||||||
'<button class="model-tab flex-1 px-4 py-2.5 text-sm font-medium border-b-2 border-transparent text-muted-foreground hover:text-foreground" data-tab="reranker" onclick="switchModelTab(\'reranker\')">' +
|
'<button class="model-tab flex-1 px-4 py-2.5 text-sm font-medium border-b-2 border-transparent text-muted-foreground hover:text-foreground" data-tab="reranker" onclick="switchCodexLensModelTab(\'reranker\')">' +
|
||||||
'<i data-lucide="arrow-up-down" class="w-3.5 h-3.5 inline mr-1"></i>Reranker' +
|
'<i data-lucide="arrow-up-down" class="w-3.5 h-3.5 inline mr-1"></i>Reranker' +
|
||||||
'</button>' +
|
'</button>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
|||||||
Reference in New Issue
Block a user