mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-06 16:31:12 +08:00
feat: 更新A2UIButton组件以支持A2UI快速操作和可用性检查;增强InjectionControlTab和QueueExecutionListView组件的错误处理和用户提示
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: workflow-multi-cli-plan
|
||||||
|
description: Multi-CLI collaborative planning with ACE context gathering, iterative cross-verification, and execution handoff
|
||||||
|
allowed-tools: Skill, Agent, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Workflow-Multi-CLI-Plan
|
||||||
|
|
||||||
|
Multi-CLI collaborative planning with ACE context gathering and iterative cross-verification.
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
Execute Phase 1 directly: [phases/01-multi-cli-plan.md](phases/01-multi-cli-plan.md)
|
||||||
|
|||||||
@@ -459,9 +459,12 @@ executionContext = {
|
|||||||
|
|
||||||
**Step 4: Hand off to Execution**:
|
**Step 4: Hand off to Execution**:
|
||||||
```javascript
|
```javascript
|
||||||
// Direct phase handoff: Read and execute Phase 2 (lite-execute) with in-memory context
|
// Skill handoff: Invoke workflow-lite-execute with in-memory context
|
||||||
Read("phases/02-lite-execute.md")
|
Skill({
|
||||||
// Execute Phase 2 with executionContext (Mode 1: In-Memory Plan)
|
skill: "workflow-lite-execute",
|
||||||
|
args: "--in-memory"
|
||||||
|
})
|
||||||
|
// executionContext is passed via global variable to workflow-lite-execute (Mode 1: In-Memory Plan)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output File Structure
|
## Output File Structure
|
||||||
@@ -614,4 +617,4 @@ cat .workflow/.multi-cli-plan/{session-id}/context-package.json
|
|||||||
|
|
||||||
## Next Phase
|
## Next Phase
|
||||||
|
|
||||||
Return to orchestrator, then auto-continue to [Phase 2: Lite Execute](02-lite-execute.md) via Skill handoff.
|
Return to orchestrator, then auto-continue via `Skill({ skill: "workflow-lite-execute", args: "--in-memory" })` with executionContext.
|
||||||
|
|||||||
@@ -1,779 +0,0 @@
|
|||||||
# Phase 2: Lite Execute
|
|
||||||
|
|
||||||
> **📌 COMPACT SENTINEL [Phase 2: Lite-Execute]**
|
|
||||||
> This phase contains 6 execution steps (Step 1 — 6).
|
|
||||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
|
||||||
> Recovery: `Read("phases/02-lite-execute.md")`
|
|
||||||
|
|
||||||
Complete execution engine supporting multiple input modes: in-memory plan, prompt description, or file content.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Execute implementation tasks from in-memory plan, prompt description, or file content
|
|
||||||
- Support batch execution with grouped tasks and code review
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Flexible task execution command supporting three input modes: in-memory plan (from lite-plan), direct prompt description, or file content. Handles execution orchestration, progress tracking, and optional code review.
|
|
||||||
|
|
||||||
**Core capabilities:**
|
|
||||||
- Multi-mode input (in-memory plan, prompt description, or file path)
|
|
||||||
- Execution orchestration (Agent or Codex) with full context
|
|
||||||
- Live progress tracking via TodoWrite at execution call level
|
|
||||||
- Optional code review with selected tool (Gemini, Agent, or custom)
|
|
||||||
- Context continuity across multiple executions
|
|
||||||
- Intelligent format detection (Enhanced Task JSON vs plain text)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
> **Note**: This is an internal phase, not a standalone command. It is called by Phase 1 (multi-cli-plan) after plan approval, or by lite-plan after Phase 4 approval.
|
|
||||||
|
|
||||||
## Input Modes
|
|
||||||
|
|
||||||
### Mode 1: In-Memory Plan
|
|
||||||
|
|
||||||
**Trigger**: Called by multi-cli-plan Phase 5 after plan approval, or by lite-plan after Phase 4 approval
|
|
||||||
|
|
||||||
**Input Source**: `executionContext` global variable set by lite-plan
|
|
||||||
|
|
||||||
**Content**: Complete execution context (see Data Structures section)
|
|
||||||
|
|
||||||
**Behavior**:
|
|
||||||
- Skip execution method selection (already set by lite-plan)
|
|
||||||
- Directly proceed to execution with full context
|
|
||||||
- All planning artifacts available (exploration, clarifications, plan)
|
|
||||||
|
|
||||||
### Mode 2: Prompt Description
|
|
||||||
|
|
||||||
**Trigger**: User calls with task description string
|
|
||||||
|
|
||||||
**Input**: Simple task description (e.g., "Add unit tests for auth module")
|
|
||||||
|
|
||||||
**Behavior**:
|
|
||||||
- Store prompt as `originalUserInput`
|
|
||||||
- Create simple execution plan from prompt
|
|
||||||
- AskUserQuestion: Select execution method (Agent/Codex/Auto)
|
|
||||||
- AskUserQuestion: Select code review tool (Skip/Gemini/Agent/Other)
|
|
||||||
- Proceed to execution with `originalUserInput` included
|
|
||||||
|
|
||||||
**User Interaction**:
|
|
||||||
```javascript
|
|
||||||
// Reference workflowPreferences (set by SKILL.md via AskUserQuestion)
|
|
||||||
const autoYes = workflowPreferences.autoYes
|
|
||||||
|
|
||||||
let userSelection
|
|
||||||
|
|
||||||
if (autoYes) {
|
|
||||||
// Auto mode: Use defaults
|
|
||||||
console.log(`[Auto] Auto-confirming execution:`)
|
|
||||||
console.log(` - Execution method: Auto`)
|
|
||||||
console.log(` - Code review: Skip`)
|
|
||||||
|
|
||||||
userSelection = {
|
|
||||||
execution_method: "Auto",
|
|
||||||
code_review_tool: "Skip"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Interactive mode: Ask user
|
|
||||||
userSelection = AskUserQuestion({
|
|
||||||
questions: [
|
|
||||||
{
|
|
||||||
question: "Select execution method:",
|
|
||||||
header: "Execution",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "Agent", description: "@code-developer agent" },
|
|
||||||
{ label: "Codex", description: "codex CLI tool" },
|
|
||||||
{ label: "Auto", description: "Auto-select based on complexity" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: "Enable code review after execution?",
|
|
||||||
header: "Code Review",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "Skip", description: "No review" },
|
|
||||||
{ label: "Gemini Review", description: "Gemini CLI tool" },
|
|
||||||
{ label: "Codex Review", description: "Git-aware review (prompt OR --uncommitted)" },
|
|
||||||
{ label: "Agent Review", description: "Current agent review" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mode 3: File Content
|
|
||||||
|
|
||||||
**Trigger**: User calls with file path
|
|
||||||
|
|
||||||
**Input**: Path to file containing task description or plan.json
|
|
||||||
|
|
||||||
**Step 1: Read and Detect Format**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
fileContent = Read(filePath)
|
|
||||||
|
|
||||||
// Attempt JSON parsing
|
|
||||||
try {
|
|
||||||
jsonData = JSON.parse(fileContent)
|
|
||||||
|
|
||||||
// Check if plan.json from lite-plan session (two-layer format: task_ids[])
|
|
||||||
if (jsonData.summary && jsonData.approach && jsonData.task_ids) {
|
|
||||||
planObject = jsonData
|
|
||||||
originalUserInput = jsonData.summary
|
|
||||||
isPlanJson = true
|
|
||||||
|
|
||||||
// Load tasks from .task/*.json files
|
|
||||||
const planDir = filePath.replace(/[/\\][^/\\]+$/, '') // parent directory
|
|
||||||
planObject._loadedTasks = loadTaskFiles(planDir, jsonData.task_ids)
|
|
||||||
} else {
|
|
||||||
// Valid JSON but not plan.json - treat as plain text
|
|
||||||
originalUserInput = fileContent
|
|
||||||
isPlanJson = false
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Not valid JSON - treat as plain text prompt
|
|
||||||
originalUserInput = fileContent
|
|
||||||
isPlanJson = false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: Create Execution Plan**
|
|
||||||
|
|
||||||
If `isPlanJson === true`:
|
|
||||||
- Use `planObject` directly
|
|
||||||
- User selects execution method and code review
|
|
||||||
|
|
||||||
If `isPlanJson === false`:
|
|
||||||
- Treat file content as prompt (same behavior as Mode 2)
|
|
||||||
- Create simple execution plan from content
|
|
||||||
|
|
||||||
**Step 3: User Interaction**
|
|
||||||
|
|
||||||
- AskUserQuestion: Select execution method (Agent/Codex/Auto)
|
|
||||||
- AskUserQuestion: Select code review tool
|
|
||||||
- Proceed to execution with full context
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Load task files from .task/ directory (two-layer format)
|
|
||||||
function loadTaskFiles(planDir, taskIds) {
|
|
||||||
return taskIds.map(id => {
|
|
||||||
const taskPath = `${planDir}/.task/${id}.json`
|
|
||||||
return JSON.parse(Read(taskPath))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tasks array from loaded .task/*.json files
|
|
||||||
function getTasks(planObject) {
|
|
||||||
return planObject._loadedTasks || []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execution Process
|
|
||||||
|
|
||||||
```
|
|
||||||
Input Parsing:
|
|
||||||
└─ Decision (mode detection):
|
|
||||||
├─ --in-memory flag → Mode 1: Load executionContext → Skip user selection
|
|
||||||
├─ Ends with .md/.json/.txt → Mode 3: Read file → Detect format
|
|
||||||
│ ├─ Valid plan.json → Use planObject → User selects method + review
|
|
||||||
│ └─ Not plan.json → Treat as prompt → User selects method + review
|
|
||||||
└─ Other → Mode 2: Prompt description → User selects method + review
|
|
||||||
|
|
||||||
Execution:
|
|
||||||
├─ Step 1: Initialize result tracking (previousExecutionResults = [])
|
|
||||||
├─ Step 2: Task grouping & batch creation
|
|
||||||
│ ├─ Extract explicit depends_on (no file/keyword inference)
|
|
||||||
│ ├─ Group: independent tasks → per-executor parallel batches (one CLI per batch)
|
|
||||||
│ ├─ Group: dependent tasks → sequential phases (respect dependencies)
|
|
||||||
│ └─ Create TodoWrite list for batches
|
|
||||||
├─ Step 3: Launch execution
|
|
||||||
│ ├─ Phase 1: Independent tasks (⚡ per-executor batches, multi-CLI concurrent)
|
|
||||||
│ └─ Phase 2+: Dependent tasks by dependency order
|
|
||||||
├─ Step 4: Track progress (TodoWrite updates per batch)
|
|
||||||
└─ Step 5: Code review (if codeReviewTool ≠ "Skip")
|
|
||||||
|
|
||||||
Output:
|
|
||||||
└─ Execution complete with results in previousExecutionResults[]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Detailed Execution Steps
|
|
||||||
|
|
||||||
### Step 1: Initialize Execution Tracking
|
|
||||||
|
|
||||||
**Operations**:
|
|
||||||
- Initialize result tracking for multi-execution scenarios
|
|
||||||
- Set up `previousExecutionResults` array for context continuity
|
|
||||||
- **In-Memory Mode**: Echo execution strategy from lite-plan for transparency
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Initialize result tracking
|
|
||||||
previousExecutionResults = []
|
|
||||||
|
|
||||||
// In-Memory Mode: Echo execution strategy (transparency before execution)
|
|
||||||
if (executionContext) {
|
|
||||||
console.log(`
|
|
||||||
📋 Execution Strategy (from lite-plan):
|
|
||||||
Method: ${executionContext.executionMethod}
|
|
||||||
Review: ${executionContext.codeReviewTool}
|
|
||||||
Tasks: ${getTasks(executionContext.planObject).length}
|
|
||||||
Complexity: ${executionContext.planObject.complexity}
|
|
||||||
${executionContext.executorAssignments ? ` Assignments: ${JSON.stringify(executionContext.executorAssignments)}` : ''}
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Task Grouping & Batch Creation
|
|
||||||
|
|
||||||
**Dependency Analysis & Grouping Algorithm**:
|
|
||||||
```javascript
|
|
||||||
// Use explicit depends_on from plan.json (no inference from file/keywords)
|
|
||||||
function extractDependencies(tasks) {
|
|
||||||
const taskIdToIndex = {}
|
|
||||||
tasks.forEach((t, i) => { taskIdToIndex[t.id] = i })
|
|
||||||
|
|
||||||
return tasks.map((task, i) => {
|
|
||||||
// Only use explicit depends_on from plan.json
|
|
||||||
const deps = (task.depends_on || [])
|
|
||||||
.map(depId => taskIdToIndex[depId])
|
|
||||||
.filter(idx => idx !== undefined && idx < i)
|
|
||||||
return { ...task, taskIndex: i, dependencies: deps }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executor Resolution (used by task grouping below)
|
|
||||||
// 获取任务的 executor(优先使用 executorAssignments,fallback 到全局 executionMethod)
|
|
||||||
function getTaskExecutor(task) {
|
|
||||||
const assignments = executionContext?.executorAssignments || {}
|
|
||||||
if (assignments[task.id]) {
|
|
||||||
return assignments[task.id].executor // 'gemini' | 'codex' | 'agent'
|
|
||||||
}
|
|
||||||
// Fallback: 全局 executionMethod 映射
|
|
||||||
const method = executionContext?.executionMethod || 'Auto'
|
|
||||||
if (method === 'Agent') return 'agent'
|
|
||||||
if (method === 'Codex') return 'codex'
|
|
||||||
// Auto: 根据复杂度
|
|
||||||
return planObject.complexity === 'Low' ? 'agent' : 'codex'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按 executor 分组任务(核心分组组件)
|
|
||||||
function groupTasksByExecutor(tasks) {
|
|
||||||
const groups = { gemini: [], codex: [], agent: [] }
|
|
||||||
tasks.forEach(task => {
|
|
||||||
const executor = getTaskExecutor(task)
|
|
||||||
groups[executor].push(task)
|
|
||||||
})
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group into batches: per-executor parallel batches (one CLI per batch)
|
|
||||||
function createExecutionCalls(tasks, executionMethod) {
|
|
||||||
const tasksWithDeps = extractDependencies(tasks)
|
|
||||||
const processed = new Set()
|
|
||||||
const calls = []
|
|
||||||
|
|
||||||
// Phase 1: Independent tasks → per-executor batches (multi-CLI concurrent)
|
|
||||||
const independentTasks = tasksWithDeps.filter(t => t.dependencies.length === 0)
|
|
||||||
if (independentTasks.length > 0) {
|
|
||||||
const executorGroups = groupTasksByExecutor(independentTasks)
|
|
||||||
let parallelIndex = 1
|
|
||||||
|
|
||||||
for (const [executor, tasks] of Object.entries(executorGroups)) {
|
|
||||||
if (tasks.length === 0) continue
|
|
||||||
tasks.forEach(t => processed.add(t.taskIndex))
|
|
||||||
calls.push({
|
|
||||||
method: executionMethod,
|
|
||||||
executor: executor, // 明确指定 executor
|
|
||||||
executionType: "parallel",
|
|
||||||
groupId: `P${parallelIndex++}`,
|
|
||||||
taskSummary: tasks.map(t => t.title).join(' | '),
|
|
||||||
tasks: tasks
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2: Dependent tasks → sequential/parallel batches (respect dependencies)
|
|
||||||
let sequentialIndex = 1
|
|
||||||
let remaining = tasksWithDeps.filter(t => !processed.has(t.taskIndex))
|
|
||||||
|
|
||||||
while (remaining.length > 0) {
|
|
||||||
// Find tasks whose dependencies are all satisfied
|
|
||||||
const ready = remaining.filter(t =>
|
|
||||||
t.dependencies.every(d => processed.has(d))
|
|
||||||
)
|
|
||||||
|
|
||||||
if (ready.length === 0) {
|
|
||||||
console.warn('Circular dependency detected, forcing remaining tasks')
|
|
||||||
ready.push(...remaining)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ready.length > 1) {
|
|
||||||
// Multiple ready tasks → per-executor batches (parallel within this phase)
|
|
||||||
const executorGroups = groupTasksByExecutor(ready)
|
|
||||||
for (const [executor, tasks] of Object.entries(executorGroups)) {
|
|
||||||
if (tasks.length === 0) continue
|
|
||||||
tasks.forEach(t => processed.add(t.taskIndex))
|
|
||||||
calls.push({
|
|
||||||
method: executionMethod,
|
|
||||||
executor: executor,
|
|
||||||
executionType: "parallel",
|
|
||||||
groupId: `P${calls.length + 1}`,
|
|
||||||
taskSummary: tasks.map(t => t.title).join(' | '),
|
|
||||||
tasks: tasks
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Single ready task → sequential batch
|
|
||||||
ready.forEach(t => processed.add(t.taskIndex))
|
|
||||||
calls.push({
|
|
||||||
method: executionMethod,
|
|
||||||
executor: getTaskExecutor(ready[0]),
|
|
||||||
executionType: "sequential",
|
|
||||||
groupId: `S${sequentialIndex++}`,
|
|
||||||
taskSummary: ready[0].title,
|
|
||||||
tasks: ready
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining = remaining.filter(t => !processed.has(t.taskIndex))
|
|
||||||
}
|
|
||||||
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
executionCalls = createExecutionCalls(getTasks(planObject), executionMethod).map(c => ({ ...c, id: `[${c.groupId}]` }))
|
|
||||||
|
|
||||||
TodoWrite({
|
|
||||||
todos: executionCalls.map(c => ({
|
|
||||||
content: `${c.executionType === "parallel" ? "⚡" : "→"} ${c.id} [${c.executor}] (${c.tasks.length} tasks)`,
|
|
||||||
status: "pending",
|
|
||||||
activeForm: `Executing ${c.id} [${c.executor}]`
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Launch Execution
|
|
||||||
|
|
||||||
**Executor Resolution**: `getTaskExecutor()` and `groupTasksByExecutor()` defined in Step 2 (Task Grouping).
|
|
||||||
|
|
||||||
**Batch Execution Routing** (根据 batch.executor 字段路由):
|
|
||||||
```javascript
|
|
||||||
// executeBatch 根据 batch 自身的 executor 字段决定调用哪个 CLI
|
|
||||||
function executeBatch(batch) {
|
|
||||||
const executor = batch.executor || getTaskExecutor(batch.tasks[0])
|
|
||||||
const sessionId = executionContext?.session?.id || 'standalone'
|
|
||||||
const fixedId = `${sessionId}-${batch.groupId}`
|
|
||||||
|
|
||||||
if (executor === 'agent') {
|
|
||||||
// Agent execution (synchronous)
|
|
||||||
return Agent({
|
|
||||||
subagent_type: "code-developer",
|
|
||||||
run_in_background: false,
|
|
||||||
description: batch.taskSummary,
|
|
||||||
prompt: buildExecutionPrompt(batch)
|
|
||||||
})
|
|
||||||
} else if (executor === 'codex') {
|
|
||||||
// Codex CLI (background)
|
|
||||||
return Bash(`ccw cli -p "${buildExecutionPrompt(batch)}" --tool codex --mode write --id ${fixedId}`, { run_in_background: true })
|
|
||||||
} else if (executor === 'gemini') {
|
|
||||||
// Gemini CLI (background)
|
|
||||||
return Bash(`ccw cli -p "${buildExecutionPrompt(batch)}" --tool gemini --mode write --id ${fixedId}`, { run_in_background: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**并行执行原则**:
|
|
||||||
- 每个 batch 对应一个独立的 CLI 实例或 Agent 调用
|
|
||||||
- 并行 = 多个 Bash(run_in_background=true) 或多个 Task() 同时发出
|
|
||||||
- 绝不将多个独立任务合并到同一个 CLI prompt 中
|
|
||||||
- Agent 任务不可后台执行(run_in_background=false),但多个 Agent 任务可通过单条消息中的多个 Task() 调用并发
|
|
||||||
|
|
||||||
**Execution Flow**: Parallel batches concurrently → Sequential batches in order
|
|
||||||
```javascript
|
|
||||||
const parallel = executionCalls.filter(c => c.executionType === "parallel")
|
|
||||||
const sequential = executionCalls.filter(c => c.executionType === "sequential")
|
|
||||||
|
|
||||||
// Phase 1: Launch all parallel batches (single message with multiple tool calls)
|
|
||||||
if (parallel.length > 0) {
|
|
||||||
TodoWrite({ todos: executionCalls.map(c => ({ status: c.executionType === "parallel" ? "in_progress" : "pending" })) })
|
|
||||||
parallelResults = await Promise.all(parallel.map(c => executeBatch(c)))
|
|
||||||
previousExecutionResults.push(...parallelResults)
|
|
||||||
TodoWrite({ todos: executionCalls.map(c => ({ status: parallel.includes(c) ? "completed" : "pending" })) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2: Execute sequential batches one by one
|
|
||||||
for (const call of sequential) {
|
|
||||||
TodoWrite({ todos: executionCalls.map(c => ({ status: c === call ? "in_progress" : "..." })) })
|
|
||||||
result = await executeBatch(call)
|
|
||||||
previousExecutionResults.push(result)
|
|
||||||
TodoWrite({ todos: executionCalls.map(c => ({ status: "completed" or "pending" })) })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unified Task Prompt Builder
|
|
||||||
|
|
||||||
**Task Formatting Principle**: Each task is a self-contained checklist. The executor only needs to know what THIS task requires. Same template for Agent and CLI.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function buildExecutionPrompt(batch) {
|
|
||||||
// Task template (6 parts: Files → Why → How → Reference → Risks → Done)
|
|
||||||
const formatTask = (t) => `
|
|
||||||
## ${t.title}
|
|
||||||
|
|
||||||
**Scope**: \`${t.scope}\` | **Action**: ${t.action}
|
|
||||||
|
|
||||||
### Files
|
|
||||||
${(t.files || []).map(f => `- **${f.path}** → \`${f.target || ''}\`: ${f.change || (f.changes || []).join(', ') || ''}`).join('\n')}
|
|
||||||
|
|
||||||
${t.rationale ? `
|
|
||||||
### Why this approach (Medium/High)
|
|
||||||
${t.rationale.chosen_approach}
|
|
||||||
${t.rationale.decision_factors?.length > 0 ? `\nKey factors: ${t.rationale.decision_factors.join(', ')}` : ''}
|
|
||||||
${t.rationale.tradeoffs ? `\nTradeoffs: ${t.rationale.tradeoffs}` : ''}
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
### How to do it
|
|
||||||
${t.description}
|
|
||||||
|
|
||||||
${t.implementation.map(step => `- ${step}`).join('\n')}
|
|
||||||
|
|
||||||
${t.code_skeleton ? `
|
|
||||||
### Code skeleton (High)
|
|
||||||
${t.code_skeleton.interfaces?.length > 0 ? `**Interfaces**: ${t.code_skeleton.interfaces.map(i => `\`${i.name}\` - ${i.purpose}`).join(', ')}` : ''}
|
|
||||||
${t.code_skeleton.key_functions?.length > 0 ? `\n**Functions**: ${t.code_skeleton.key_functions.map(f => `\`${f.signature}\` - ${f.purpose}`).join(', ')}` : ''}
|
|
||||||
${t.code_skeleton.classes?.length > 0 ? `\n**Classes**: ${t.code_skeleton.classes.map(c => `\`${c.name}\` - ${c.purpose}`).join(', ')}` : ''}
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
### Reference
|
|
||||||
- Pattern: ${t.reference?.pattern || 'N/A'}
|
|
||||||
- Files: ${t.reference?.files?.join(', ') || 'N/A'}
|
|
||||||
${t.reference?.examples ? `- Notes: ${t.reference.examples}` : ''}
|
|
||||||
|
|
||||||
${t.risks?.length > 0 ? `
|
|
||||||
### Risk mitigations (High)
|
|
||||||
${t.risks.map(r => `- ${r.description} → **${r.mitigation}**`).join('\n')}
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
### Done when
|
|
||||||
${(t.convergence?.criteria || []).map(c => `- [ ] ${c}`).join('\n')}
|
|
||||||
${(t.test?.success_metrics || []).length > 0 ? `\n**Success metrics**: ${t.test.success_metrics.join(', ')}` : ''}`
|
|
||||||
|
|
||||||
// Build prompt
|
|
||||||
const sections = []
|
|
||||||
|
|
||||||
if (originalUserInput) sections.push(`## Goal\n${originalUserInput}`)
|
|
||||||
|
|
||||||
sections.push(`## Tasks\n${batch.tasks.map(formatTask).join('\n\n---\n')}`)
|
|
||||||
|
|
||||||
// Context (reference only)
|
|
||||||
const context = []
|
|
||||||
if (previousExecutionResults.length > 0) {
|
|
||||||
context.push(`### Previous Work\n${previousExecutionResults.map(r => `- ${r.tasksSummary}: ${r.status}`).join('\n')}`)
|
|
||||||
}
|
|
||||||
if (clarificationContext) {
|
|
||||||
context.push(`### Clarifications\n${Object.entries(clarificationContext).map(([q, a]) => `- ${q}: ${a}`).join('\n')}`)
|
|
||||||
}
|
|
||||||
if (executionContext?.planObject?.data_flow?.diagram) {
|
|
||||||
context.push(`### Data Flow\n${executionContext.planObject.data_flow.diagram}`)
|
|
||||||
}
|
|
||||||
if (executionContext?.session?.artifacts?.plan) {
|
|
||||||
context.push(`### Artifacts\nPlan: ${executionContext.session.artifacts.plan}`)
|
|
||||||
}
|
|
||||||
// Project guidelines (user-defined constraints from /workflow:session:solidify)
|
|
||||||
context.push(`### Project Guidelines\n@.workflow/specs/*.md`)
|
|
||||||
if (context.length > 0) sections.push(`## Context\n${context.join('\n\n')}`)
|
|
||||||
|
|
||||||
sections.push(`Complete each task according to its "Done when" checklist.`)
|
|
||||||
|
|
||||||
return sections.join('\n\n')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option A: Agent Execution**
|
|
||||||
|
|
||||||
When to use:
|
|
||||||
- `getTaskExecutor(task) === "agent"`
|
|
||||||
- 或 `executionMethod = "Agent"` (全局 fallback)
|
|
||||||
- 或 `executionMethod = "Auto" AND complexity = "Low"` (全局 fallback)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Task(
|
|
||||||
subagent_type="code-developer",
|
|
||||||
run_in_background=false,
|
|
||||||
description=batch.taskSummary,
|
|
||||||
prompt=buildExecutionPrompt(batch)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result Collection**: After completion, collect result following `executionResult` structure (see Data Structures section)
|
|
||||||
|
|
||||||
**Option B: CLI Execution (Codex)**
|
|
||||||
|
|
||||||
When to use:
|
|
||||||
- `getTaskExecutor(task) === "codex"`
|
|
||||||
- 或 `executionMethod = "Codex"` (全局 fallback)
|
|
||||||
- 或 `executionMethod = "Auto" AND complexity = "Medium/High"` (全局 fallback)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "${buildExecutionPrompt(batch)}" --tool codex --mode write
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution with fixed IDs** (predictable ID pattern):
|
|
||||||
```javascript
|
|
||||||
// Launch CLI in background, wait for task hook callback
|
|
||||||
// Generate fixed execution ID: ${sessionId}-${groupId}
|
|
||||||
const sessionId = executionContext?.session?.id || 'standalone'
|
|
||||||
const fixedExecutionId = `${sessionId}-${batch.groupId}` // e.g., "implement-auth-2025-12-13-P1"
|
|
||||||
|
|
||||||
// Check if resuming from previous failed execution
|
|
||||||
const previousCliId = batch.resumeFromCliId || null
|
|
||||||
|
|
||||||
// Build command with fixed ID (and optional resume for continuation)
|
|
||||||
const cli_command = previousCliId
|
|
||||||
? `ccw cli -p "${buildExecutionPrompt(batch)}" --tool codex --mode write --id ${fixedExecutionId} --resume ${previousCliId}`
|
|
||||||
: `ccw cli -p "${buildExecutionPrompt(batch)}" --tool codex --mode write --id ${fixedExecutionId}`
|
|
||||||
|
|
||||||
// Execute in background, stop output and wait for task hook callback
|
|
||||||
Bash(
|
|
||||||
command=cli_command,
|
|
||||||
run_in_background=true
|
|
||||||
)
|
|
||||||
// STOP HERE - CLI executes in background, task hook will notify on completion
|
|
||||||
```
|
|
||||||
|
|
||||||
**Resume on Failure** (with fixed ID):
|
|
||||||
```javascript
|
|
||||||
// If execution failed or timed out, offer resume option
|
|
||||||
if (bash_result.status === 'failed' || bash_result.status === 'timeout') {
|
|
||||||
console.log(`
|
|
||||||
⚠️ Execution incomplete. Resume available:
|
|
||||||
Fixed ID: ${fixedExecutionId}
|
|
||||||
Lookup: ccw cli detail ${fixedExecutionId}
|
|
||||||
Resume: ccw cli -p "Continue tasks" --resume ${fixedExecutionId} --tool codex --mode write --id ${fixedExecutionId}-retry
|
|
||||||
`)
|
|
||||||
|
|
||||||
// Store for potential retry in same session
|
|
||||||
batch.resumeFromCliId = fixedExecutionId
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result Collection**: After completion, analyze output and collect result following `executionResult` structure (include `cliExecutionId` for resume capability)
|
|
||||||
|
|
||||||
**Option C: CLI Execution (Gemini)**
|
|
||||||
|
|
||||||
When to use: `getTaskExecutor(task) === "gemini"` (分析类任务)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 使用统一的 buildExecutionPrompt,切换 tool 和 mode
|
|
||||||
ccw cli -p "${buildExecutionPrompt(batch)}" --tool gemini --mode analysis --id ${sessionId}-${batch.groupId}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Progress Tracking
|
|
||||||
|
|
||||||
Progress tracked at batch level (not individual task level). Icons: ⚡ (parallel, concurrent), → (sequential, one-by-one)
|
|
||||||
|
|
||||||
### Step 5: Code Review (Optional)
|
|
||||||
|
|
||||||
**Skip Condition**: Only run if `codeReviewTool ≠ "Skip"`
|
|
||||||
|
|
||||||
**Review Focus**: Verify implementation against plan convergence criteria and test requirements
|
|
||||||
- Read plan.json + .task/*.json for task convergence criteria and test checklist
|
|
||||||
- Check each convergence criterion is fulfilled
|
|
||||||
- Verify success metrics from test field (Medium/High complexity)
|
|
||||||
- Run unit/integration tests specified in test field
|
|
||||||
- Validate code quality and identify issues
|
|
||||||
- Ensure alignment with planned approach and risk mitigations
|
|
||||||
|
|
||||||
**Operations**:
|
|
||||||
- Agent Review: Current agent performs direct review
|
|
||||||
- Gemini Review: Execute gemini CLI with review prompt
|
|
||||||
- Codex Review: Two options - (A) with prompt for complex reviews, (B) `--uncommitted` flag only for quick reviews
|
|
||||||
- Custom tool: Execute specified CLI tool (qwen, etc.)
|
|
||||||
|
|
||||||
**Unified Review Template** (All tools use same standard):
|
|
||||||
|
|
||||||
**Review Criteria**:
|
|
||||||
- **Convergence Criteria**: Verify each criterion from task convergence.criteria
|
|
||||||
- **Test Checklist** (Medium/High): Check unit, integration, success_metrics from task test
|
|
||||||
- **Code Quality**: Analyze quality, identify issues, suggest improvements
|
|
||||||
- **Plan Alignment**: Validate implementation matches planned approach and risk mitigations
|
|
||||||
|
|
||||||
**Shared Prompt Template** (used by all CLI tools):
|
|
||||||
```
|
|
||||||
PURPOSE: Code review for implemented changes against plan convergence criteria and test requirements
|
|
||||||
TASK: • Verify plan convergence criteria fulfillment • Check test requirements (unit, integration, success_metrics) • Analyze code quality • Identify issues • Suggest improvements • Validate plan adherence and risk mitigations
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/* @{plan.json} @{.task/*.json} [@{exploration.json}] | Memory: Review lite-execute changes against plan requirements including test checklist
|
|
||||||
EXPECTED: Quality assessment with:
|
|
||||||
- Convergence criteria verification (all tasks from .task/*.json)
|
|
||||||
- Test checklist validation (Medium/High: unit, integration, success_metrics)
|
|
||||||
- Issue identification
|
|
||||||
- Recommendations
|
|
||||||
Explicitly check each convergence criterion and test item from .task/*.json files.
|
|
||||||
CONSTRAINTS: Focus on plan convergence criteria, test requirements, and plan adherence | analysis=READ-ONLY
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tool-Specific Execution** (Apply shared prompt template above):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Method 1: Agent Review (current agent)
|
|
||||||
# - Read plan.json: ${executionContext.session.artifacts.plan}
|
|
||||||
# - Apply unified review criteria (see Shared Prompt Template)
|
|
||||||
# - Report findings directly
|
|
||||||
|
|
||||||
# Method 2: Gemini Review (recommended)
|
|
||||||
ccw cli -p "[Shared Prompt Template with artifacts]" --tool gemini --mode analysis
|
|
||||||
# CONTEXT includes: @**/* @${plan.json} [@${exploration.json}]
|
|
||||||
|
|
||||||
# Method 3: Qwen Review (alternative)
|
|
||||||
ccw cli -p "[Shared Prompt Template with artifacts]" --tool qwen --mode analysis
|
|
||||||
# Same prompt as Gemini, different execution engine
|
|
||||||
|
|
||||||
# Method 4: Codex Review (git-aware) - Two mutually exclusive options:
|
|
||||||
|
|
||||||
# Option A: With custom prompt (reviews uncommitted by default)
|
|
||||||
ccw cli -p "[Shared Prompt Template with artifacts]" --tool codex --mode review
|
|
||||||
# Use for complex reviews with specific focus areas
|
|
||||||
|
|
||||||
# Option B: Target flag only (no prompt allowed)
|
|
||||||
ccw cli --tool codex --mode review --uncommitted
|
|
||||||
# Quick review of uncommitted changes without custom instructions
|
|
||||||
|
|
||||||
# ⚠️ IMPORTANT: -p prompt and target flags (--uncommitted/--base/--commit) are MUTUALLY EXCLUSIVE
|
|
||||||
```
|
|
||||||
|
|
||||||
**Multi-Round Review with Fixed IDs**:
|
|
||||||
```javascript
|
|
||||||
// Generate fixed review ID
|
|
||||||
const reviewId = `${sessionId}-review`
|
|
||||||
|
|
||||||
// First review pass with fixed ID
|
|
||||||
const reviewResult = Bash(`ccw cli -p "[Review prompt]" --tool gemini --mode analysis --id ${reviewId}`)
|
|
||||||
|
|
||||||
// If issues found, continue review dialog with fixed ID chain
|
|
||||||
if (hasUnresolvedIssues(reviewResult)) {
|
|
||||||
// Resume with follow-up questions
|
|
||||||
Bash(`ccw cli -p "Clarify the security concerns you mentioned" --resume ${reviewId} --tool gemini --mode analysis --id ${reviewId}-followup`)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation Note**: Replace `[Shared Prompt Template with artifacts]` placeholder with actual template content, substituting:
|
|
||||||
- `@{plan.json}` → `@${executionContext.session.artifacts.plan}`
|
|
||||||
- `[@{exploration.json}]` → exploration files from artifacts (if exists)
|
|
||||||
|
|
||||||
### Step 6: Auto-Sync Project State
|
|
||||||
|
|
||||||
**Trigger**: After all executions complete (regardless of code review)
|
|
||||||
|
|
||||||
**Operation**: Execute `/workflow:session:sync -y "{summary}"` to update both `specs/*.md` and `project-tech.json` in one shot.
|
|
||||||
|
|
||||||
Summary 取值优先级:`originalUserInput` → `planObject.summary` → git log 自动推断。
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
**Input Modes**: In-memory (lite-plan), prompt (standalone), file (JSON/text)
|
|
||||||
**Task Grouping**: Based on explicit depends_on only; independent tasks split by executor, each batch runs as separate CLI instance
|
|
||||||
**Execution**: Independent task batches launch concurrently via single Claude message with multiple tool calls (one tool call per batch)
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Cause | Resolution |
|
|
||||||
|-------|-------|------------|
|
|
||||||
| Missing executionContext | --in-memory without context | Error: "No execution context found. Only available when called by lite-plan." |
|
|
||||||
| File not found | File path doesn't exist | Error: "File not found: {path}. Check file path." |
|
|
||||||
| Empty file | File exists but no content | Error: "File is empty: {path}. Provide task description." |
|
|
||||||
| Invalid Enhanced Task JSON | JSON missing required fields | Warning: "Missing required fields. Treating as plain text." |
|
|
||||||
| Malformed JSON | JSON parsing fails | Treat as plain text (expected for non-JSON files) |
|
|
||||||
| Execution failure | Agent/Codex crashes | Display error, use fixed ID `${sessionId}-${groupId}` for resume: `ccw cli -p "Continue" --resume <fixed-id> --id <fixed-id>-retry` |
|
|
||||||
| Execution timeout | CLI exceeded timeout | Use fixed ID for resume with extended timeout |
|
|
||||||
| Codex unavailable | Codex not installed | Show installation instructions, offer Agent execution |
|
|
||||||
| Fixed ID not found | Custom ID lookup failed | Check `ccw cli history`, verify date directories |
|
|
||||||
|
|
||||||
## Data Structures
|
|
||||||
|
|
||||||
### executionContext (Input - Mode 1)
|
|
||||||
|
|
||||||
Passed from lite-plan via global variable:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
planObject: {
|
|
||||||
summary: string,
|
|
||||||
approach: string,
|
|
||||||
task_ids: string[], // Task IDs referencing .task/*.json files
|
|
||||||
task_count: number, // Number of tasks
|
|
||||||
_loadedTasks: [...], // Populated at runtime from .task/*.json files
|
|
||||||
estimated_time: string,
|
|
||||||
recommended_execution: string,
|
|
||||||
complexity: string
|
|
||||||
},
|
|
||||||
// Task file paths (populated for two-layer format)
|
|
||||||
taskFiles: [{id: string, path: string}] | null,
|
|
||||||
explorationsContext: {...} | null, // Multi-angle explorations
|
|
||||||
explorationAngles: string[], // List of exploration angles
|
|
||||||
explorationManifest: {...} | null, // Exploration manifest
|
|
||||||
clarificationContext: {...} | null,
|
|
||||||
executionMethod: "Agent" | "Codex" | "Auto", // 全局默认
|
|
||||||
codeReviewTool: "Skip" | "Gemini Review" | "Agent Review" | string,
|
|
||||||
originalUserInput: string,
|
|
||||||
|
|
||||||
// 任务级 executor 分配(优先于 executionMethod)
|
|
||||||
executorAssignments: {
|
|
||||||
[taskId]: { executor: "gemini" | "codex" | "agent", reason: string }
|
|
||||||
},
|
|
||||||
|
|
||||||
// Session artifacts location (saved by lite-plan)
|
|
||||||
session: {
|
|
||||||
id: string, // Session identifier: {taskSlug}-{shortTimestamp}
|
|
||||||
folder: string, // Session folder path: .workflow/.lite-plan/{session-id}
|
|
||||||
artifacts: {
|
|
||||||
explorations: [{angle, path}], // exploration-{angle}.json paths
|
|
||||||
explorations_manifest: string, // explorations-manifest.json path
|
|
||||||
plan: string // plan.json path (always present)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Artifact Usage**:
|
|
||||||
- Artifact files contain detailed planning context
|
|
||||||
- Pass artifact paths to CLI tools and agents for enhanced context
|
|
||||||
- See execution options below for usage examples
|
|
||||||
|
|
||||||
### executionResult (Output)
|
|
||||||
|
|
||||||
Collected after each execution call completes:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
executionId: string, // e.g., "[Agent-1]", "[Codex-1]"
|
|
||||||
status: "completed" | "partial" | "failed",
|
|
||||||
tasksSummary: string, // Brief description of tasks handled
|
|
||||||
completionSummary: string, // What was completed
|
|
||||||
keyOutputs: string, // Files created/modified, key changes
|
|
||||||
notes: string, // Important context for next execution
|
|
||||||
fixedCliId: string | null // Fixed CLI execution ID (e.g., "implement-auth-2025-12-13-P1")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Appended to `previousExecutionResults` array for context continuity in multi-execution scenarios.
|
|
||||||
|
|
||||||
## Post-Completion Expansion
|
|
||||||
|
|
||||||
**Auto-sync**: 执行 `/workflow:session:sync -y "{summary}"` 更新 specs/*.md + project-tech(Step 6 已触发,此处不重复)。
|
|
||||||
|
|
||||||
完成后询问用户是否扩展为issue(test/enhance/refactor/doc),选中项调用 `/issue:new "{summary} - {dimension}"`
|
|
||||||
|
|
||||||
**Fixed ID Pattern**: `${sessionId}-${groupId}` enables predictable lookup without auto-generated timestamps.
|
|
||||||
|
|
||||||
**Resume Usage**: If `status` is "partial" or "failed", use `fixedCliId` to resume:
|
|
||||||
```bash
|
|
||||||
# Lookup previous execution
|
|
||||||
ccw cli detail ${fixedCliId}
|
|
||||||
|
|
||||||
# Resume with new fixed ID for retry
|
|
||||||
ccw cli -p "Continue from where we left off" --resume ${fixedCliId} --tool codex --mode write --id ${fixedCliId}-retry
|
|
||||||
```
|
|
||||||
@@ -8,15 +8,20 @@ import { MessageSquare } from 'lucide-react';
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { useDialogStyleContext } from '@/contexts/DialogStyleContext';
|
import { useDialogStyleContext } from '@/contexts/DialogStyleContext';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useNotificationStore } from '@/stores';
|
||||||
|
|
||||||
interface A2UIButtonProps {
|
interface A2UIButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function A2UIButton({ className, compact = false }: A2UIButtonProps) {
|
export function A2UIButton({ className, compact = false }: A2UIButtonProps) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { preferences } = useDialogStyleContext();
|
const { preferences } = useDialogStyleContext();
|
||||||
|
const a2uiSurfaces = useNotificationStore((state) => state.a2uiSurfaces);
|
||||||
|
const isA2UIAvailable = Object.keys(a2uiSurfaces).length > 0;
|
||||||
|
|
||||||
// Don't render if hidden in preferences
|
// Don't render if hidden in preferences
|
||||||
if (!preferences.showA2UIButtonInToolbar) {
|
if (!preferences.showA2UIButtonInToolbar) {
|
||||||
@@ -24,21 +29,36 @@ export function A2UIButton({ className, compact = false }: A2UIButtonProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
// Trigger A2UI quick action - this would typically open a dialog
|
// This would typically open the most relevant A2UI dialog
|
||||||
// For now, we'll just log the action
|
// For now, we'll just log the action
|
||||||
console.log('[A2UIButton] Quick action triggered');
|
if (isA2UIAvailable) {
|
||||||
|
console.log('[A2UIButton] Quick action triggered');
|
||||||
|
// Example: find the first popup surface and handle it
|
||||||
|
const firstPopupId = Object.keys(a2uiSurfaces).find(id => a2uiSurfaces[id].displayMode === 'popup');
|
||||||
|
if(firstPopupId) {
|
||||||
|
// In a real implementation, you might open a dialog here
|
||||||
|
// using the surface data.
|
||||||
|
console.log(`[A2UIButton] Would open dialog for surface: ${firstPopupId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buttonTitle = isA2UIAvailable
|
||||||
|
? formatMessage({ id: 'navigation.toolbar.a2ui.quickAction', defaultMessage: 'A2UI Quick Action' })
|
||||||
|
: formatMessage({ id: 'navigation.toolbar.a2ui.unavailable', defaultMessage: 'No A2UI action available' });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size={compact ? 'icon' : 'sm'}
|
size={compact ? 'icon' : 'sm'}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
disabled={!isA2UIAvailable}
|
||||||
className={cn(
|
className={cn(
|
||||||
'gap-2 bg-primary text-primary-foreground hover:bg-primary/90',
|
'gap-2 bg-primary text-primary-foreground hover:bg-primary/90',
|
||||||
|
!isA2UIAvailable && 'bg-muted-foreground/30 hover:bg-muted-foreground/30',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
title={formatMessage({ id: 'navigation.toolbar.a2ui.quickAction', defaultMessage: 'A2UI Quick Action' })}
|
title={buttonTitle}
|
||||||
>
|
>
|
||||||
<MessageSquare className="h-4 w-4" />
|
<MessageSquare className="h-4 w-4" />
|
||||||
{!compact && (
|
{!compact && (
|
||||||
|
|||||||
@@ -922,12 +922,14 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
|
|||||||
step={500}
|
step={500}
|
||||||
value={formData.maxLength}
|
value={formData.maxLength}
|
||||||
onChange={(e) => handleFieldChange('maxLength', Number(e.target.value))}
|
onChange={(e) => handleFieldChange('maxLength', Number(e.target.value))}
|
||||||
|
className={cn(maxLengthError && 'border-destructive')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||||
(~{Math.ceil(formData.maxLength / 80)} {formatMessage({ id: 'specs.injection.lines', defaultMessage: 'lines' })})
|
(~{Math.ceil(formData.maxLength / 80)} {formatMessage({ id: 'specs.injection.lines', defaultMessage: 'lines' })})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{maxLengthError && <p className="text-xs text-destructive mt-1">{maxLengthError}</p>}
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'specs.injection.maxLengthHelp',
|
id: 'specs.injection.maxLengthHelp',
|
||||||
@@ -981,12 +983,14 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
|
|||||||
step={500}
|
step={500}
|
||||||
value={formData.warnThreshold}
|
value={formData.warnThreshold}
|
||||||
onChange={(e) => handleFieldChange('warnThreshold', Number(e.target.value))}
|
onChange={(e) => handleFieldChange('warnThreshold', Number(e.target.value))}
|
||||||
|
className={cn(warnThresholdError && 'border-destructive')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||||
(~{Math.ceil(formData.warnThreshold / 80)} {formatMessage({ id: 'specs.injection.lines', defaultMessage: 'lines' })})
|
(~{Math.ceil(formData.warnThreshold / 80)} {formatMessage({ id: 'specs.injection.lines', defaultMessage: 'lines' })})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{warnThresholdError && <p className="text-xs text-destructive mt-1">{warnThresholdError}</p>}
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'specs.injection.warnThresholdHelp',
|
id: 'specs.injection.warnThresholdHelp',
|
||||||
@@ -1024,7 +1028,7 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
|
|||||||
>
|
>
|
||||||
{formatMessage({ id: 'common.actions.reset', defaultMessage: 'Reset' })}
|
{formatMessage({ id: 'common.actions.reset', defaultMessage: 'Reset' })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSave} disabled={!hasChanges || isSaving}>
|
<Button onClick={handleSave} disabled={!hasChanges || isSaving || !!maxLengthError || !!warnThresholdError}>
|
||||||
{isSaving ? (
|
{isSaving ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { ClipboardList } from 'lucide-react';
|
import { Inbox } from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/Badge';
|
import { Badge } from '@/components/ui/Badge';
|
||||||
|
import { Button } from '@/components/ui/Button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useQueueExecutionStore } from '@/stores/queueExecutionStore';
|
import { useQueueExecutionStore } from '@/stores/queueExecutionStore';
|
||||||
import { useTerminalPanelStore } from '@/stores/terminalPanelStore';
|
import { useTerminalPanelStore } from '@/stores/terminalPanelStore';
|
||||||
@@ -65,17 +66,31 @@ function QueueExecutionItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ========== Empty State ==========
|
// ========== Empty State ==========
|
||||||
|
|
||||||
function QueueEmptyState() {
|
function QueueEmptyState() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleCreateQueue = () => {
|
||||||
|
navigate(ROUTES.ORCHESTRATOR);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex items-center justify-center text-muted-foreground">
|
<div className="flex-1 flex items-center justify-center text-muted-foreground">
|
||||||
<div className="text-center">
|
<div className="text-center p-4">
|
||||||
<ClipboardList className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
<Inbox className="h-12 w-12 mx-auto mb-4 opacity-40" />
|
||||||
<p className="text-sm">{formatMessage({ id: 'home.terminalPanel.queueView.emptyTitle' })}</p>
|
<p className="text-sm font-medium text-foreground">
|
||||||
<p className="text-xs mt-1">{formatMessage({ id: 'home.terminalPanel.queueView.emptyDesc' })}</p>
|
{formatMessage({ id: 'home.terminalPanel.queueView.emptyTitleV2', defaultMessage: 'Execution queue is empty' })}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs mt-1 mb-4">
|
||||||
|
{formatMessage({ id: 'home.terminalPanel.queueView.emptyDescV2', defaultMessage: 'Create an execution queue to run tasks in sequence.' })}
|
||||||
|
</p>
|
||||||
|
<Button size="sm" onClick={handleCreateQueue}>
|
||||||
|
{formatMessage({ id: 'home.terminalPanel.queueView.createQueue', defaultMessage: 'Create Queue' })}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user