feat: add task queue sidebar and resume functionality for CLI sessions

- Implemented task queue sidebar for managing active tasks with filtering options.
- Added functionality to close notification sidebar when opening task queue.
- Enhanced CLI history view to support resuming previous sessions with optional prompts.
- Updated CLI executor to handle resuming sessions for Codex, Gemini, and Qwen tools.
- Introduced utility functions for finding CLI history directories recursively.
- Improved task queue data management and rendering logic.
This commit is contained in:
catlog22
2025-12-13 11:51:53 +08:00
parent 335f5e9ec6
commit 93d3df1e08
14 changed files with 2000 additions and 128 deletions

View File

@@ -8,7 +8,8 @@ import {
cliExecutorTool,
getCliToolsStatus,
getExecutionHistory,
getExecutionDetail
getExecutionDetail,
resumeCliSession
} from '../tools/cli-executor.js';
interface CliExecOptions {
@@ -27,6 +28,12 @@ interface HistoryOptions {
status?: string;
}
interface ResumeOptions {
tool?: string;
last?: boolean;
prompt?: string;
}
/**
* Show CLI tool status
*/
@@ -75,23 +82,25 @@ async function execAction(prompt: string | undefined, options: CliExecOptions):
prompt,
mode,
model,
dir: cd,
include: includeDirs,
timeout: timeout ? parseInt(timeout, 10) : 300000,
stream: !noStream
});
cd,
includeDirs,
timeout: timeout ? parseInt(timeout, 10) : 300000
}, onOutput);
// If not streaming, print output now
if (noStream && result.stdout) {
console.log(result.stdout);
}
// Print summary
// Print summary with execution ID for resume
console.log();
if (result.success) {
console.log(chalk.green(` ✓ Completed in ${(result.execution.duration_ms / 1000).toFixed(1)}s`));
console.log(chalk.gray(` ID: ${result.execution.id}`));
console.log(chalk.dim(` Resume: ccw cli resume ${result.execution.id}`));
} else {
console.log(chalk.red(` ✗ Failed (${result.execution.status})`));
console.log(chalk.gray(` ID: ${result.execution.id}`));
if (result.stderr) {
console.error(chalk.red(result.stderr));
}
@@ -185,6 +194,64 @@ async function detailAction(executionId: string | undefined): Promise<void> {
console.log();
}
/**
* Resume a CLI session
* @param {string | undefined} executionId - Optional execution ID to resume
* @param {Object} options - CLI options
*/
async function resumeAction(executionId: string | undefined, options: ResumeOptions): Promise<void> {
const { tool, last, prompt } = options;
// Determine resume mode
let resumeMode = '';
if (executionId) {
resumeMode = `session ${executionId}`;
} else if (last) {
resumeMode = tool ? `last ${tool} session` : 'last session';
} else if (tool === 'codex') {
resumeMode = 'codex (interactive picker)';
} else {
console.error(chalk.red('Error: Please specify --last or provide an execution ID'));
console.error(chalk.gray('Usage: ccw cli resume [id] --last --tool gemini'));
process.exit(1);
}
console.log(chalk.cyan(`\n Resuming ${resumeMode}...\n`));
try {
const result = await resumeCliSession(
process.cwd(),
{
tool,
executionId,
last,
prompt
},
(chunk) => {
process.stdout.write(chunk.data);
}
);
console.log();
if (result.success) {
console.log(chalk.green(` ✓ Completed in ${(result.execution.duration_ms / 1000).toFixed(1)}s`));
console.log(chalk.gray(` ID: ${result.execution.id}`));
console.log(chalk.dim(` Resume: ccw cli resume ${result.execution.id}`));
} else {
console.log(chalk.red(` ✗ Failed (${result.execution.status})`));
console.log(chalk.gray(` ID: ${result.execution.id}`));
if (result.stderr) {
console.error(chalk.red(result.stderr));
}
process.exit(1);
}
} catch (error) {
const err = error as Error;
console.error(chalk.red(` Error: ${err.message}`));
process.exit(1);
}
}
/**
* Get human-readable time ago string
* @param {Date} date
@@ -202,14 +269,14 @@ function getTimeAgo(date: Date): string {
/**
* CLI command entry point
* @param {string} subcommand - Subcommand (status, exec, history, detail)
* @param {string} subcommand - Subcommand (status, exec, history, detail, resume)
* @param {string[]} args - Arguments array
* @param {Object} options - CLI options
*/
export async function cliCommand(
subcommand: string,
args: string | string[],
options: CliExecOptions | HistoryOptions
options: CliExecOptions | HistoryOptions | ResumeOptions
): Promise<void> {
const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
@@ -230,12 +297,17 @@ export async function cliCommand(
await detailAction(argsArray[0]);
break;
case 'resume':
await resumeAction(argsArray[0], options as ResumeOptions);
break;
default:
console.log(chalk.bold.cyan('\n CCW CLI Tool Executor\n'));
console.log(' Unified interface for Gemini, Qwen, and Codex CLI tools.\n');
console.log(' Subcommands:');
console.log(chalk.gray(' status Check CLI tools availability'));
console.log(chalk.gray(' exec <prompt> Execute a CLI tool'));
console.log(chalk.gray(' resume [id] Resume a previous session'));
console.log(chalk.gray(' history Show execution history'));
console.log(chalk.gray(' detail <id> Show execution detail'));
console.log();
@@ -243,13 +315,19 @@ export async function cliCommand(
console.log(chalk.gray(' --tool <tool> Tool to use: gemini, qwen, codex (default: gemini)'));
console.log(chalk.gray(' --mode <mode> Mode: analysis, write, auto (default: analysis)'));
console.log(chalk.gray(' --model <model> Model override'));
console.log(chalk.gray(' --cd <path> Working directory (-C for codex)'));
console.log(chalk.gray(' --cd <path> Working directory'));
console.log(chalk.gray(' --includeDirs <dirs> Additional directories (comma-separated)'));
console.log(chalk.gray(' → gemini/qwen: --include-directories'));
console.log(chalk.gray(' → codex: --add-dir'));
console.log(chalk.gray(' --timeout <ms> Timeout in milliseconds (default: 300000)'));
console.log(chalk.gray(' --no-stream Disable streaming output'));
console.log();
console.log(' Resume Options:');
console.log(chalk.gray(' --last Resume most recent session'));
console.log(chalk.gray(' --tool <tool> Tool filter (gemini, qwen, codex)'));
console.log(chalk.gray(' --prompt <text> Additional prompt for continuation'));
console.log(chalk.gray(' [id] Specific execution ID to resume'));
console.log();
console.log(' History Options:');
console.log(chalk.gray(' --limit <n> Number of results (default: 20)'));
console.log(chalk.gray(' --tool <tool> Filter by tool'));
@@ -260,6 +338,10 @@ export async function cliCommand(
console.log(chalk.gray(' ccw cli exec "Analyze the auth module" --tool gemini'));
console.log(chalk.gray(' ccw cli exec "Analyze with context" --tool gemini --includeDirs ../shared,../types'));
console.log(chalk.gray(' ccw cli exec "Implement feature" --tool codex --mode auto --includeDirs ./lib'));
console.log(chalk.gray(' ccw cli resume --last # Resume last session'));
console.log(chalk.gray(' ccw cli resume --last --tool gemini # Resume last Gemini session'));
console.log(chalk.gray(' ccw cli resume --tool codex # Codex interactive picker'));
console.log(chalk.gray(' ccw cli resume <id> --prompt "Continue..." # Resume specific session'));
console.log(chalk.gray(' ccw cli history --tool gemini --limit 10'));
console.log();
}