fix(multi-cli): populate multiCliPlan sessions in liteTaskDataStore

Fix task click handlers not working in multi-CLI planning detail page.

Root cause: liteTaskDataStore was not being populated with multiCliPlan
sessions during initialization, so task click handlers couldn't access
session data using currentSessionDetailKey.

Changes:
- navigation.js: Add code to populate multiCliPlan sessions in liteTaskDataStore
- notifications.js: Add code to populate multiCliPlan sessions when data refreshes

Now when task detail page loads, liteTaskDataStore contains the correct key
'multi-cli-${sessionId}' matching currentSessionDetailKey, allowing task
click handlers to find session data and open detail drawer.

Verified: Task clicks now properly open detail panel for all 7 tasks.
This commit is contained in:
catlog22
2026-01-22 15:41:01 +08:00
parent f0954b3247
commit ea04663035
22 changed files with 921 additions and 83 deletions

View File

@@ -223,6 +223,10 @@ interface IssueOptions {
data?: string; // JSON data for create
fromQueue?: boolean | string; // Sync statuses from queue (true=active, string=specific queue ID)
queue?: string; // Target queue ID for multi-queue operations
// GitHub pull options
state?: string; // Issue state: open, closed, all
limit?: number; // Maximum number of issues to pull
labels?: string; // Filter by labels (comma-separated)
}
const ISSUES_DIR = '.workflow/issues';
@@ -1003,6 +1007,113 @@ async function createAction(options: IssueOptions): Promise<void> {
}
}
/**
* pull - Pull issues from GitHub
* Usage: ccw issue pull [--state open|closed|all] [--limit N] [--labels label1,label2]
*/
async function pullAction(options: IssueOptions): Promise<void> {
try {
// Check if gh CLI is available
try {
execSync('gh --version', { stdio: 'ignore', timeout: EXEC_TIMEOUTS.GIT_QUICK });
} catch {
console.error(chalk.red('GitHub CLI (gh) is not installed or not in PATH'));
console.error(chalk.gray('Install from: https://cli.github.com/'));
process.exit(1);
}
// Build gh command with options
const state = options.state || 'open';
const limit = options.limit || 100;
let ghCommand = `gh issue list --state ${state} --limit ${limit} --json number,title,body,labels,url,state`;
if (options.labels) {
ghCommand += ` --label "${options.labels}"`;
}
console.log(chalk.cyan(`Fetching issues from GitHub (state: ${state}, limit: ${limit})...`));
// Fetch issues from GitHub
const ghOutput = execSync(ghCommand, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: EXEC_TIMEOUTS.PROCESS_SPAWN,
}).trim();
if (!ghOutput) {
console.log(chalk.yellow('No issues found on GitHub'));
return;
}
const ghIssues = JSON.parse(ghOutput);
const existingIssues = readIssues();
let imported = 0;
let skipped = 0;
let updated = 0;
for (const ghIssue of ghIssues) {
const issueId = `GH-${ghIssue.number}`;
const existingIssue = existingIssues.find(i => i.id === issueId);
// Prepare issue data
const issueData: Partial<Issue> = {
id: issueId,
title: ghIssue.title,
status: ghIssue.state === 'OPEN' ? 'registered' : 'completed',
priority: 3, // Default priority
context: ghIssue.body?.substring(0, 500) || ghIssue.title,
source: 'github',
source_url: ghIssue.url,
tags: ghIssue.labels?.map((l: any) => l.name) || [],
};
if (existingIssue) {
// Update existing issue if state changed
if (existingIssue.source_url === ghIssue.url) {
// Check if status needs updating
const newStatus = ghIssue.state === 'OPEN' ? 'registered' : 'completed';
if (existingIssue.status !== newStatus || existingIssue.title !== ghIssue.title) {
existingIssue.title = ghIssue.title;
existingIssue.status = newStatus;
existingIssue.updated_at = new Date().toISOString();
updated++;
} else {
skipped++;
}
} else {
skipped++;
}
} else {
// Create new issue
try {
createIssue(issueData);
imported++;
} catch (err) {
console.error(chalk.red(`Failed to import issue #${ghIssue.number}: ${(err as Error).message}`));
}
}
}
// Save updates if any
if (updated > 0) {
writeIssues(existingIssues);
}
console.log(chalk.green(`\n✓ GitHub sync complete:`));
console.log(chalk.gray(` - Imported: ${imported} new issues`));
console.log(chalk.gray(` - Updated: ${updated} existing issues`));
console.log(chalk.gray(` - Skipped: ${skipped} unchanged issues`));
if (options.json) {
console.log(JSON.stringify({ imported, updated, skipped, total: ghIssues.length }));
}
} catch (err) {
console.error(chalk.red(`Failed to pull issues from GitHub: ${(err as Error).message}`));
process.exit(1);
}
}
/**
* solution - Create or read solutions
* Create: ccw issue solution <issue-id> --data '{"tasks":[...]}'
@@ -2715,6 +2826,9 @@ export async function issueCommand(
case 'create':
await createAction(options);
break;
case 'pull':
await pullAction(options);
break;
case 'solution':
await solutionAction(argsArray[0], options);
break;
@@ -2769,6 +2883,8 @@ export async function issueCommand(
console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
console.log(chalk.bold('Core Commands:'));
console.log(chalk.gray(' create --data \'{"title":"..."}\' Create issue (auto-generates ID)'));
console.log(chalk.gray(' pull [--state open|closed|all] Pull issues from GitHub'));
console.log(chalk.gray(' [--limit N] [--labels label1,label2]'));
console.log(chalk.gray(' init <issue-id> Initialize new issue (manual ID)'));
console.log(chalk.gray(' list [issue-id] List issues or tasks'));
console.log(chalk.gray(' history List completed issues (from history)'));
@@ -2809,6 +2925,9 @@ export async function issueCommand(
console.log(chalk.gray(' --priority <n> Queue priority (lower = higher)'));
console.log(chalk.gray(' --json JSON output'));
console.log(chalk.gray(' --force Force operation'));
console.log(chalk.gray(' --state <state> GitHub issue state (open/closed/all)'));
console.log(chalk.gray(' --limit <n> Max issues to pull from GitHub'));
console.log(chalk.gray(' --labels <labels> Filter by GitHub labels (comma-separated)'));
console.log();
console.log(chalk.bold('Storage:'));
console.log(chalk.gray(' .workflow/issues/issues.jsonl Active issues'));