From 4a5f7ce7f791ffdca6d0b5f52d7cb1f580d86b57 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Tue, 17 Feb 2026 23:43:53 +0800 Subject: [PATCH] feat: update CLI roadmap planning agent to generate roadmap.md instead of execution-plan.json and issues.jsonl; enhance QueuePanel with orchestrator tab and status management; improve issue listing with summary output --- .claude/agents/cli-roadmap-plan-agent.md | 110 +--- .codex/skills/workflow-req-plan/SKILL.md | 12 +- .../terminal-dashboard/QueuePanel.tsx | 468 +++++++++++++----- .../terminal-panel/TerminalMainArea.tsx | 4 +- .../src/locales/en/terminal-dashboard.json | 7 +- .../src/locales/zh/terminal-dashboard.json | 7 +- ccw/frontend/src/router.tsx | 4 +- ccw/src/commands/issue.ts | 46 +- 8 files changed, 422 insertions(+), 236 deletions(-) diff --git a/.claude/agents/cli-roadmap-plan-agent.md b/.claude/agents/cli-roadmap-plan-agent.md index eadb33e7..f52d5373 100644 --- a/.claude/agents/cli-roadmap-plan-agent.md +++ b/.claude/agents/cli-roadmap-plan-agent.md @@ -10,12 +10,11 @@ description: | - Convergence criteria generation (criteria + verification + definition_of_done) - CLI-assisted quality validation of decomposition - Issue creation via ccw issue create (standard issues-jsonl-schema) - - Execution plan generation with wave groupings + issue dependencies - Optional codebase context integration color: green --- -You are a specialized roadmap planning agent that decomposes requirements into self-contained records with convergence criteria, creates issues via `ccw issue create`, and generates execution-plan.json for team-planex consumption. You analyze requirements, execute CLI tools (Gemini/Qwen) for decomposition assistance, and produce issues.jsonl + execution-plan.json + roadmap.md. +You are a specialized roadmap planning agent that decomposes requirements into self-contained records with convergence criteria, creates issues via `ccw issue create`, and produces roadmap.md (issues stored in .workflow/issues/issues.jsonl via ccw issue create). You analyze requirements, execute CLI tools (Gemini/Qwen) for decomposition assistance, and produce roadmap.md. **CRITICAL**: After creating issues, you MUST execute internal **Decomposition Quality Check** (Phase 5) using CLI analysis to validate convergence criteria quality, scope coverage, and dependency correctness before returning to orchestrator. @@ -23,8 +22,6 @@ You are a specialized roadmap planning agent that decomposes requirements into s | Artifact | Description | |----------|-------------| -| `issues.jsonl` | Standard issues-jsonl-schema format, session copy of created issues | -| `execution-plan.json` | Wave grouping + issue dependencies (team-planex bridge) | | `roadmap.md` | Human-readable roadmap with issue ID references | ## Input Context @@ -142,9 +139,7 @@ Phase 3: Record Enhancement & Validation Phase 4: Issue Creation & Output Generation ← ⭐ Core change ├─ 4a: Internal records → issue data mapping ├─ 4b: ccw issue create for each item (get formal ISS-xxx IDs) -├─ 4c: Generate execution-plan.json (waves + dependencies) -├─ 4d: Generate issues.jsonl session copy -└─ 4e: Generate roadmap.md with issue ID references +└─ 4c: Generate roadmap.md with issue ID references Phase 5: Decomposition Quality Check (MANDATORY) ├─ Execute CLI quality check using Gemini (Qwen fallback) @@ -681,84 +676,7 @@ for (const record of records) { } ``` -#### 4c: Generate execution-plan.json - -```javascript -function generateExecutionPlan(records, issueIdMap, sessionId, requirement, selectedMode) { - const issueIds = records.map(r => issueIdMap[r.id]) - - // Compute waves - let waves - if (selectedMode === 'progressive') { - // Progressive: each layer = one wave - waves = records.map((r, i) => ({ - wave: i + 1, - label: r.name, - issue_ids: [issueIdMap[r.id]], - depends_on_waves: r.depends_on.length > 0 - ? [...new Set(r.depends_on.map(d => records.findIndex(x => x.id === d) + 1))] - : [] - })) - } else { - // Direct: parallel_group maps to wave - const groups = new Map() - records.forEach(r => { - const g = r.parallel_group - if (!groups.has(g)) groups.set(g, []) - groups.get(g).push(r) - }) - - waves = [...groups.entries()] - .sort(([a], [b]) => a - b) - .map(([groupNum, groupRecords]) => ({ - wave: groupNum, - label: `Group ${groupNum}`, - issue_ids: groupRecords.map(r => issueIdMap[r.id]), - depends_on_waves: groupNum > 1 - ? [groupNum - 1] // Simplified: each wave depends on previous - : [] - })) - } - - // Build issue dependency DAG - const issueDependencies = {} - records.forEach(r => { - const deps = r.depends_on.map(d => issueIdMap[d]).filter(Boolean) - if (deps.length > 0) { - issueDependencies[issueIdMap[r.id]] = deps - } - }) - - return { - session_id: sessionId, - requirement: requirement, - strategy: selectedMode, - created_at: new Date().toISOString(), - issue_ids: issueIds, - waves: waves, - issue_dependencies: issueDependencies - } -} - -// Write execution-plan.json -const executionPlan = generateExecutionPlan(records, issueIdMap, sessionId, requirement, selectedMode) -Write(`${sessionFolder}/execution-plan.json`, JSON.stringify(executionPlan, null, 2)) -``` - -#### 4d: Generate issues.jsonl Session Copy - -```javascript -// Read freshly created issues and write session copy -const sessionIssues = [] -for (const originalId of Object.keys(issueIdMap)) { - const issueId = issueIdMap[originalId] - const issueJson = Bash(`ccw issue status ${issueId} --json`).trim() - sessionIssues.push(issueJson) -} -Write(`${sessionFolder}/issues.jsonl`, sessionIssues.join('\n') + '\n') -``` - -#### 4e: Roadmap Markdown Generation (with Issue ID References) +#### 4c: Roadmap Markdown Generation (with Issue ID References) ```javascript // Generate roadmap.md for progressive mode @@ -817,7 +735,7 @@ ${layers.flatMap(l => l.risks.map(r => `- **${l.id}** (${issueIdMap[l.id]}): ${r ### 使用 team-planex 执行全部波次 \`\`\` -Skill(skill="team-planex", args="--plan ${input.session.folder}/execution-plan.json") +Skill(skill="team-planex", args="${Object.values(issueIdMap).join(' ')}") \`\`\` ### 按波次逐步执行 @@ -826,8 +744,7 @@ ${layers.map(l => `# Wave ${getWaveNum(l)}: ${l.name}\nSkill(skill="team-planex" \`\`\` 路线图文件: \`${input.session.folder}/\` -- issues.jsonl (标准 issue 格式) -- execution-plan.json (波次编排) +- roadmap.md (路线图) ` } @@ -886,7 +803,7 @@ ${t.convergence.criteria.map(c => `- ${c}`).join('\n')} ### 使用 team-planex 执行全部波次 \`\`\` -Skill(skill="team-planex", args="--plan ${input.session.folder}/execution-plan.json") +Skill(skill="team-planex", args="${Object.values(issueIdMap).join(' ')}") \`\`\` ### 按波次逐步执行 @@ -897,8 +814,7 @@ ${[...groups.entries()].sort(([a], [b]) => a - b).map(([g, ts]) => \`\`\` 路线图文件: \`${input.session.folder}/\` -- issues.jsonl (标准 issue 格式) -- execution-plan.json (波次编排) +- roadmap.md (路线图) ` } ``` @@ -989,9 +905,6 @@ ${requirement} ISSUES CREATED (${selected_mode} mode): ${issuesJsonlContent} -EXECUTION PLAN: -${JSON.stringify(executionPlan, null, 2)} - TASK: • Requirement Coverage: Does the decomposition address ALL aspects of the requirement? • Convergence Quality: Are criteria testable? Is verification executable? Is DoD business-readable? @@ -1030,7 +943,7 @@ CONSTRAINTS: Read-only validation, do not modify files | Missing scope items | Add to appropriate issue context | | Effort imbalance | Suggest split (report to orchestrator) | -After fixes, update issues via `ccw issue update` and regenerate `issues.jsonl` + `roadmap.md`. +After fixes, update issues via `ccw issue update` and regenerate `roadmap.md`. ## Error Handling @@ -1073,11 +986,9 @@ for (const record of records) { - Ensure verification is executable (commands or explicit steps) - Ensure definition_of_done uses business language - Create issues via `ccw issue create` (get formal ISS-xxx IDs) -- Generate execution-plan.json with correct wave groupings -- Generate issues.jsonl session copy - Generate roadmap.md with issue ID references - Run Phase 5 quality check before returning -- Write all three output files: issues.jsonl, execution-plan.json, roadmap.md +- Write roadmap.md output file **Bash Tool**: - Use `run_in_background=false` for all Bash/CLI calls @@ -1087,5 +998,4 @@ for (const record of records) { - Create circular dependencies - Skip convergence validation - Skip Phase 5 quality check -- Return without writing all three output files -- Generate roadmap.jsonl (deprecated, replaced by issues.jsonl + execution-plan.json) +- Return without writing roadmap.md diff --git a/.codex/skills/workflow-req-plan/SKILL.md b/.codex/skills/workflow-req-plan/SKILL.md index 76080dfe..6a4479f0 100644 --- a/.codex/skills/workflow-req-plan/SKILL.md +++ b/.codex/skills/workflow-req-plan/SKILL.md @@ -1,6 +1,6 @@ --- name: workflow-req-plan -description: Requirement-level progressive roadmap planning with issue creation. Decomposes requirements into convergent layers or task sequences, creates issues via ccw issue create, and generates execution-plan.json for team-planex consumption. +description: Requirement-level progressive roadmap planning with issue creation. Decomposes requirements into convergent layers or task sequences, creates issues via ccw issue create, and generates roadmap.md for human review. Issues stored in .workflow/issues/issues.jsonl (single source of truth). argument-hint: "[-y|--yes] [-c|--continue] [-m|--mode progressive|direct|auto] \"requirement description\"" allowed-tools: spawn_agent, wait, send_input, close_agent, AskUserQuestion, Read, Write, Edit, Bash, Glob, Grep --- @@ -31,11 +31,11 @@ $workflow-req-plan -y "Implement caching layer" **Context Source**: cli-explore-agent (optional) + requirement analysis **Output Directory**: `.workflow/.req-plan/{session-id}/` -**Core Innovation**: Requirement decomposition → issue creation → execution-plan.json for team-planex consumption. Each issue is standard issues-jsonl-schema format, bridging req-plan to team-planex execution pipeline. +**Core Innovation**: Requirement decomposition → issue creation via `ccw issue create`. Issues stored in `.workflow/issues/issues.jsonl` (single source of truth); wave and dependency info embedded in issue tags and `extended_context.notes`. team-planex consumes issues directly by ID or tag query. ## Overview -Requirement-level layered roadmap planning. Decomposes a requirement into **convergent layers or task sequences**, creates issues via `ccw issue create`, and generates execution-plan.json for team-planex consumption. +Requirement-level layered roadmap planning. Decomposes a requirement into **convergent layers or task sequences**, creates issues via `ccw issue create`. Issues are the single source of truth in `.workflow/issues/issues.jsonl`; wave and dependency info is embedded in issue tags and `extended_context.notes`. **Dual Modes**: - **Progressive**: Layered MVP→iterations, suitable for high-uncertainty requirements (validate first, then refine) @@ -81,8 +81,6 @@ Phase 3: Decomposition & Issue Creation (Inlined Agent) ├─ Step 3.3: Issue Creation & Output Generation │ ├─ Internal records → issue data mapping │ ├─ ccw issue create for each item (get ISS-xxx IDs) - │ ├─ Generate execution-plan.json (waves + dependencies) - │ ├─ Generate issues.jsonl session copy │ └─ Generate roadmap.md with issue ID references └─ Step 3.4: Decomposition Quality Check (MANDATORY) ├─ Execute CLI quality check (Gemini, Qwen fallback) @@ -99,8 +97,6 @@ Phase 4: Validation & team-planex Handoff ``` .workflow/.req-plan/RPLAN-{slug}-{YYYY-MM-DD}/ ├── roadmap.md # Human-readable roadmap with issue ID references -├── issues.jsonl # Standard issues-jsonl-schema format (session copy) -├── execution-plan.json # Wave grouping + issue dependencies (team-planex bridge) ├── strategy-assessment.json # Strategy assessment result └── exploration-codebase.json # Codebase context (optional) ``` @@ -110,8 +106,6 @@ Phase 4: Validation & team-planex Handoff | `strategy-assessment.json` | 1 | Uncertainty analysis + mode recommendation + extracted goal/constraints/stakeholders | | `roadmap.md` (skeleton) | 1 | Initial skeleton with placeholders, finalized in Phase 3 | | `exploration-codebase.json` | 2 | Codebase context: relevant modules, patterns, integration points (only when codebase exists) | -| `issues.jsonl` | 3 | Standard issues-jsonl-schema records, one per line (session copy of created issues) | -| `execution-plan.json` | 3 | Wave grouping with issue dependencies for team-planex consumption | | `roadmap.md` (final) | 3 | Human-readable roadmap with issue ID references, convergence details, team-planex execution guide | ## Subagent API Reference diff --git a/ccw/frontend/src/components/terminal-dashboard/QueuePanel.tsx b/ccw/frontend/src/components/terminal-dashboard/QueuePanel.tsx index 50203418..c74fd8e7 100644 --- a/ccw/frontend/src/components/terminal-dashboard/QueuePanel.tsx +++ b/ccw/frontend/src/components/terminal-dashboard/QueuePanel.tsx @@ -1,13 +1,12 @@ // ======================================== // QueuePanel Component // ======================================== -// Queue list panel for the terminal dashboard middle column. -// Consumes existing useIssueQueue() React Query hook for queue data -// and bridges queueExecutionStore for execution status per item. -// Integrates with issueQueueIntegrationStore for association chain -// highlighting and selection state. +// Queue list panel for the terminal dashboard with tab switching. +// Tab 1 (Queue): Issue queue items from useIssueQueue() hook. +// Tab 2 (Orchestrator): Active orchestration plans from orchestratorStore. +// Integrates with issueQueueIntegrationStore for association chain. -import { useMemo, useCallback } from 'react'; +import { useState, useMemo, useCallback, memo } from 'react'; import { useIntl } from 'react-intl'; import { ListChecks, @@ -20,8 +19,18 @@ import { Zap, Ban, Terminal, + Workflow, + Circle, + CheckCircle2, + SkipForward, + Pause, + Play, + Square, + RotateCcw, + AlertCircle, } from 'lucide-react'; import { Badge } from '@/components/ui/Badge'; +import { Button } from '@/components/ui/Button'; import { cn } from '@/lib/utils'; import { useIssueQueue } from '@/hooks/useIssues'; import { @@ -32,9 +41,20 @@ import { useQueueExecutionStore, selectByQueueItem, } from '@/stores/queueExecutionStore'; +import { + useOrchestratorStore, + selectActivePlans, + selectActivePlanCount, + type OrchestrationRunState, +} from '@/stores/orchestratorStore'; +import type { StepStatus, OrchestrationStatus } from '@/types/orchestrator'; import type { QueueItem } from '@/lib/api'; -// ========== Status Config ========== +// ========== Tab Type ========== + +type QueueTab = 'queue' | 'orchestrator'; + +// ========== Queue Tab: Status Config ========== type QueueItemStatus = QueueItem['status']; @@ -51,7 +71,7 @@ const STATUS_CONFIG: Record e.status === 'running') ?? executions[0]; @@ -129,58 +148,14 @@ function QueueItemRow({ ); } -// ========== Empty State ========== +// ========== Queue Tab: Content ========== -function QueueEmptyState({ compact = false }: { compact?: boolean }) { - const { formatMessage } = useIntl(); - - if (compact) { - return ( -
- - {formatMessage({ id: 'terminalDashboard.queuePanel.noItems' })} - {formatMessage({ id: 'terminalDashboard.queuePanel.noItemsDesc' })} -
- ); - } - - return ( -
-
- -

{formatMessage({ id: 'terminalDashboard.queuePanel.noItems' })}

-

- {formatMessage({ id: 'terminalDashboard.queuePanel.noItemsDesc' })} -

-
-
- ); -} - -// ========== Error State ========== - -function QueueErrorState({ error }: { error: Error }) { - const { formatMessage } = useIntl(); - return ( -
-
- -

{formatMessage({ id: 'terminalDashboard.queuePanel.error' })}

-

{error.message}

-
-
- ); -} - -// ========== Main Component ========== - -export function QueuePanel({ embedded = false }: { embedded?: boolean }) { +function QueueTabContent({ embedded = false }: { embedded?: boolean }) { const { formatMessage } = useIntl(); const queueQuery = useIssueQueue(); const associationChain = useIssueQueueIntegrationStore(selectAssociationChain); const buildAssociationChain = useIssueQueueIntegrationStore((s) => s.buildAssociationChain); - // Flatten all queue items from grouped_items const allItems = useMemo(() => { if (!queueQuery.data) return []; const grouped = queueQuery.data.grouped_items ?? {}; @@ -188,18 +163,10 @@ export function QueuePanel({ embedded = false }: { embedded?: boolean }) { for (const group of Object.values(grouped)) { items.push(...group); } - // Sort by execution_order items.sort((a, b) => a.execution_order - b.execution_order); return items; }, [queueQuery.data]); - // Count active items (pending + ready + executing) - const activeCount = useMemo(() => { - return allItems.filter( - (item) => item.status === 'pending' || item.status === 'ready' || item.status === 'executing' - ).length; - }, [allItems]); - const handleSelect = useCallback( (queueItemId: string) => { buildAssociationChain(queueItemId, 'queue'); @@ -207,74 +174,341 @@ export function QueuePanel({ embedded = false }: { embedded?: boolean }) { [buildAssociationChain] ); - // Loading state if (queueQuery.isLoading) { return ( -
- {!embedded && ( -
-

- - {formatMessage({ id: 'terminalDashboard.queuePanel.title' })} -

-
- )} -
- +
+ +
+ ); + } + + if (queueQuery.error) { + return ( +
+
+ +

{formatMessage({ id: 'terminalDashboard.queuePanel.error' })}

+

{queueQuery.error.message}

); } - // Error state - if (queueQuery.error) { + if (allItems.length === 0) { return ( -
- {!embedded && ( -
-

- - {formatMessage({ id: 'terminalDashboard.queuePanel.title' })} -

-
- )} - +
+
+ +

{formatMessage({ id: 'terminalDashboard.queuePanel.noItems' })}

+

+ {formatMessage({ id: 'terminalDashboard.queuePanel.noItemsDesc' })} +

+
); } return ( -
- {/* Header with flow indicator (hidden when embedded) */} - {!embedded && ( -
-

- - - {formatMessage({ id: 'terminalDashboard.queuePanel.title' })} -

- {activeCount > 0 && ( - - {activeCount} - - )} -
- )} +
+ {allItems.map((item) => ( + handleSelect(item.item_id)} + /> + ))} +
+ ); +} - {/* Queue Item List */} - {allItems.length === 0 ? ( - - ) : ( -
- {allItems.map((item) => ( - handleSelect(item.item_id)} - /> - ))} -
+// ========== Orchestrator Tab: Status Badge ========== + +const orchestratorStatusClass: Record = { + pending: 'bg-muted text-muted-foreground border-border', + running: 'bg-primary/10 text-primary border-primary/50', + paused: 'bg-amber-500/10 text-amber-500 border-amber-500/50', + completed: 'bg-green-500/10 text-green-500 border-green-500/50', + failed: 'bg-destructive/10 text-destructive border-destructive/50', + cancelled: 'bg-muted text-muted-foreground border-border', +}; + +function OrchestratorStatusBadge({ status }: { status: OrchestrationStatus }) { + const { formatMessage } = useIntl(); + return ( + + {formatMessage({ id: `orchestrator.status.${status}` })} + + ); +} + +// ========== Orchestrator Tab: Step Icon ========== + +function StepIcon({ status }: { status: StepStatus }) { + switch (status) { + case 'running': + return ; + case 'completed': + return ; + case 'failed': + return ; + case 'skipped': + return ; + case 'paused': + return ; + case 'cancelled': + return ; + default: + return ; + } +} + +// ========== Orchestrator Tab: Plan Controls ========== + +function PlanControls({ planId, status, failedStepId }: { + planId: string; + status: OrchestrationStatus; + failedStepId: string | null; +}) { + const pauseOrchestration = useOrchestratorStore((s) => s.pauseOrchestration); + const resumeOrchestration = useOrchestratorStore((s) => s.resumeOrchestration); + const stopOrchestration = useOrchestratorStore((s) => s.stopOrchestration); + const retryStep = useOrchestratorStore((s) => s.retryStep); + const skipStep = useOrchestratorStore((s) => s.skipStep); + + if (status === 'completed' || status === 'cancelled') return null; + + const isPausedOnError = status === 'paused' && failedStepId !== null; + const isPausedByUser = status === 'paused' && failedStepId === null; + + return ( +
+ {status === 'running' && ( + <> + + + + )} + {isPausedByUser && ( + <> + + + + )} + {isPausedOnError && failedStepId && ( + <> + + + + + )} +
+ ); +} + +// ========== Orchestrator Tab: Plan Card ========== + +const PlanCard = memo(function PlanCard({ runState }: { runState: OrchestrationRunState }) { + const { plan, status, stepStatuses, currentStepIndex } = runState; + + const { completedCount, totalCount, progress } = useMemo(() => { + const statuses = Object.values(stepStatuses); + const total = statuses.length; + const completed = statuses.filter((s) => s.status === 'completed' || s.status === 'skipped').length; + return { completedCount: completed, totalCount: total, progress: total > 0 ? (completed / total) * 100 : 0 }; + }, [stepStatuses]); + + const failedStepId = useMemo(() => { + for (const [stepId, stepState] of Object.entries(stepStatuses)) { + if (stepState.status === 'failed') return stepId; + } + return null; + }, [stepStatuses]); + + return ( +
+
+

{plan.name}

+ + + {completedCount}/{totalCount} + +
+ +
+
+
+ +
+ {plan.steps.map((step, index) => { + const stepState = stepStatuses[step.id]; + if (!stepState) return null; + const isCurrent = index === currentStepIndex && status === 'running'; + return ( +
+ + + {step.name} + + {stepState.retryCount > 0 && ( + ×{stepState.retryCount} + )} +
+ ); + })} +
+ + {failedStepId && stepStatuses[failedStepId]?.error && ( +
+ + + {stepStatuses[failedStepId].error} + +
+ )} + + +
+ ); +}); + +// ========== Orchestrator Tab: Content ========== + +function OrchestratorTabContent() { + const { formatMessage } = useIntl(); + const activePlans = useOrchestratorStore(selectActivePlans); + const planEntries = useMemo(() => Object.entries(activePlans), [activePlans]); + + if (planEntries.length === 0) { + return ( +
+
+ +

+ {formatMessage({ id: 'terminalDashboard.orchestratorPanel.noPlans', defaultMessage: 'No active orchestrations' })} +

+

+ {formatMessage({ id: 'terminalDashboard.orchestratorPanel.noPlansHint', defaultMessage: 'Run a flow from the Orchestrator to see progress here' })} +

+
+
+ ); + } + + return ( +
+ {planEntries.map(([planId, runState]) => ( + + ))} +
+ ); +} + +// ========== Main Component ========== + +export function QueuePanel({ embedded = false }: { embedded?: boolean }) { + const { formatMessage } = useIntl(); + const [activeTab, setActiveTab] = useState('queue'); + const orchestratorCount = useOrchestratorStore(selectActivePlanCount); + + const queueQuery = useIssueQueue(); + const queueActiveCount = useMemo(() => { + if (!queueQuery.data) return 0; + const grouped = queueQuery.data.grouped_items ?? {}; + let count = 0; + for (const items of Object.values(grouped)) { + count += items.filter( + (item) => item.status === 'pending' || item.status === 'ready' || item.status === 'executing' + ).length; + } + return count; + }, [queueQuery.data]); + + return ( +
+ {/* Tab bar */} + {!embedded && ( +
+ + +
+ )} + + {/* Tab content */} + {activeTab === 'queue' ? ( + + ) : ( + )}
); diff --git a/ccw/frontend/src/components/terminal-panel/TerminalMainArea.tsx b/ccw/frontend/src/components/terminal-panel/TerminalMainArea.tsx index 9eab5784..053161d9 100644 --- a/ccw/frontend/src/components/terminal-panel/TerminalMainArea.tsx +++ b/ccw/frontend/src/components/terminal-panel/TerminalMainArea.tsx @@ -19,7 +19,7 @@ import { Button } from '@/components/ui/Button'; import { useTerminalPanelStore } from '@/stores/terminalPanelStore'; import { useCliSessionStore, type CliSessionMeta } from '@/stores/cliSessionStore'; import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore'; -import { QueueExecutionListView } from './QueueExecutionListView'; +import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel'; import { fetchCliSessionBuffer, sendCliSessionText, @@ -273,7 +273,7 @@ export function TerminalMainArea({ onClose }: TerminalMainAreaProps) { {/* Content */} {panelView === 'queue' ? ( /* Queue View */ - + ) : activeTerminalId ? ( /* Terminal View */
diff --git a/ccw/frontend/src/locales/en/terminal-dashboard.json b/ccw/frontend/src/locales/en/terminal-dashboard.json index 68ffa56c..39708b0a 100644 --- a/ccw/frontend/src/locales/en/terminal-dashboard.json +++ b/ccw/frontend/src/locales/en/terminal-dashboard.json @@ -85,7 +85,12 @@ "modeYolo": "Yolo", "quickCreate": "Quick Create", "configure": "Configure...", - "fullscreen": "Fullscreen" + "fullscreen": "Fullscreen", + "orchestrator": "Orchestrator" + }, + "orchestratorPanel": { + "noPlans": "No active orchestrations", + "noPlansHint": "Run a flow from the Orchestrator to see progress here" }, "cliConfig": { "title": "Create CLI Session", diff --git a/ccw/frontend/src/locales/zh/terminal-dashboard.json b/ccw/frontend/src/locales/zh/terminal-dashboard.json index 2f4fad4f..cbeb31bd 100644 --- a/ccw/frontend/src/locales/zh/terminal-dashboard.json +++ b/ccw/frontend/src/locales/zh/terminal-dashboard.json @@ -85,7 +85,12 @@ "modeYolo": "Yolo", "quickCreate": "快速创建", "configure": "配置...", - "fullscreen": "全屏" + "fullscreen": "全屏", + "orchestrator": "编排器" + }, + "orchestratorPanel": { + "noPlans": "没有活跃的编排任务", + "noPlansHint": "从编排器运行流程后,进度将显示在这里" }, "cliConfig": { "title": "创建 CLI 会话", diff --git a/ccw/frontend/src/router.tsx b/ccw/frontend/src/router.tsx index 057f5cd7..d353345a 100644 --- a/ccw/frontend/src/router.tsx +++ b/ccw/frontend/src/router.tsx @@ -13,7 +13,6 @@ import { SessionDetailPage, HistoryPage, OrchestratorPage, - LoopMonitorPage, IssueHubPage, SkillsManagerPage, CommandsManagerPage, @@ -91,7 +90,7 @@ const routes: RouteObject[] = [ }, { path: 'loops', - element: , + element: , }, { path: 'cli-viewer', @@ -207,6 +206,7 @@ export const ROUTES = { PROJECT: '/project', HISTORY: '/history', ORCHESTRATOR: '/orchestrator', + /** @deprecated Redirects to /terminal-dashboard */ LOOPS: '/loops', CLI_VIEWER: '/cli-viewer', ISSUES: '/issues', diff --git a/ccw/src/commands/issue.ts b/ccw/src/commands/issue.ts index 3778a663..4247c56a 100644 --- a/ccw/src/commands/issue.ts +++ b/ccw/src/commands/issue.ts @@ -1463,9 +1463,17 @@ async function initAction(issueId: string | undefined, options: IssueOptions): P * list - List issues or tasks */ async function listAction(issueId: string | undefined, options: IssueOptions): Promise { + // Always compute summary from ALL issues (unfiltered) + const allIssues = readIssues(); + const statusCounts: Record = {}; + for (const i of allIssues) { + statusCounts[i.status] = (statusCounts[i.status] || 0) + 1; + } + const summary = { total: allIssues.length, by_status: statusCounts }; + if (!issueId) { // List all issues - let issues = readIssues(); + let issues = allIssues; // Filter by status if specified if (options.status) { @@ -1483,18 +1491,19 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P tags: i.tags || [], bound_solution_id: i.bound_solution_id })); - console.log(JSON.stringify(briefIssues, null, 2)); + console.log(JSON.stringify({ _summary: summary, issues: briefIssues }, null, 2)); return; } if (options.json) { - console.log(JSON.stringify(issues, null, 2)); + console.log(JSON.stringify({ _summary: summary, issues }, null, 2)); return; } if (issues.length === 0) { console.log(chalk.yellow('No issues found')); console.log(chalk.gray('Create one with: ccw issue init ')); + printIssueSummary(summary); return; } @@ -1523,6 +1532,8 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P (issue.title || '').substring(0, 30) ); } + + printIssueSummary(summary, options.status ? issues.length : undefined); return; } @@ -1537,7 +1548,7 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P const tasks = solution?.tasks || []; if (options.json) { - console.log(JSON.stringify({ issue, solution, tasks }, null, 2)); + console.log(JSON.stringify({ _summary: summary, issue, solution, tasks }, null, 2)); return; } @@ -1549,6 +1560,7 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P if (tasks.length === 0) { console.log(chalk.yellow('No tasks (bind a solution first)')); + printIssueSummary(summary); return; } @@ -1563,6 +1575,32 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P task.title.substring(0, 30) ); } + + printIssueSummary(summary); +} + +/** + * Print issue summary line (total + per-status counts) + */ +function printIssueSummary(summary: { total: number; by_status: Record }, filteredCount?: number): void { + const parts = Object.entries(summary.by_status) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([status, count]) => { + const color = { + 'registered': chalk.gray, + 'planning': chalk.blue, + 'planned': chalk.cyan, + 'queued': chalk.yellow, + 'executing': chalk.yellow, + 'completed': chalk.green, + 'failed': chalk.red, + 'paused': chalk.magenta + }[status] || chalk.white; + return color(`${status}: ${count}`); + }); + + const filterInfo = filteredCount !== undefined ? ` (showing ${filteredCount})` : ''; + console.log(chalk.gray(`\nTotal: ${summary.total}${filterInfo} | ${parts.join(', ')}`)); } /**