mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
fix(cli): 增强 CLI 输出处理,添加解析输出和过滤功能
This commit is contained in:
@@ -385,8 +385,12 @@ Before completing any task, verify:
|
|||||||
- Make assumptions - verify with existing code
|
- Make assumptions - verify with existing code
|
||||||
- Create unnecessary complexity
|
- Create unnecessary complexity
|
||||||
|
|
||||||
**Bash Tool**:
|
**Bash Tool (CLI Execution in Agent)**:
|
||||||
- Use `run_in_background=false` for all Bash/CLI calls to ensure foreground execution
|
- Use `run_in_background=false` for all Bash/CLI calls - agent cannot receive task hook callbacks
|
||||||
|
- Set timeout ≥60 minutes for CLI commands (hooks don't propagate to subagents):
|
||||||
|
```javascript
|
||||||
|
Bash(command="ccw cli -p '...' --tool codex --mode write", timeout=3600000) // 60 min
|
||||||
|
```
|
||||||
|
|
||||||
**ALWAYS:**
|
**ALWAYS:**
|
||||||
- **Search Tool Priority**: ACE (`mcp__ace-tool__search_context`) → CCW (`mcp__ccw-tools__smart_search`) / Built-in (`Grep`, `Glob`, `Read`)
|
- **Search Tool Priority**: ACE (`mcp__ace-tool__search_context`) → CCW (`mcp__ccw-tools__smart_search`) / Built-in (`Grep`, `Glob`, `Read`)
|
||||||
|
|||||||
@@ -172,10 +172,23 @@ Output:
|
|||||||
**Operations**:
|
**Operations**:
|
||||||
- Initialize result tracking for multi-execution scenarios
|
- Initialize result tracking for multi-execution scenarios
|
||||||
- Set up `previousExecutionResults` array for context continuity
|
- Set up `previousExecutionResults` array for context continuity
|
||||||
|
- **In-Memory Mode**: Echo execution strategy from lite-plan for transparency
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Initialize result tracking
|
// Initialize result tracking
|
||||||
previousExecutionResults = []
|
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: ${executionContext.planObject.tasks.length}
|
||||||
|
Complexity: ${executionContext.planObject.complexity}
|
||||||
|
${executionContext.executorAssignments ? ` Assignments: ${JSON.stringify(executionContext.executorAssignments)}` : ''}
|
||||||
|
`)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Task Grouping & Batch Creation
|
### Step 2: Task Grouping & Batch Creation
|
||||||
@@ -393,16 +406,8 @@ ccw cli -p "${buildExecutionPrompt(batch)}" --tool codex --mode write
|
|||||||
|
|
||||||
**Execution with fixed IDs** (predictable ID pattern):
|
**Execution with fixed IDs** (predictable ID pattern):
|
||||||
```javascript
|
```javascript
|
||||||
// Launch CLI in foreground (NOT background)
|
// Launch CLI in background, wait for task hook callback
|
||||||
// Timeout based on complexity: Low=40min, Medium=60min, High=100min
|
|
||||||
const timeoutByComplexity = {
|
|
||||||
"Low": 2400000, // 40 minutes
|
|
||||||
"Medium": 3600000, // 60 minutes
|
|
||||||
"High": 6000000 // 100 minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate fixed execution ID: ${sessionId}-${groupId}
|
// Generate fixed execution ID: ${sessionId}-${groupId}
|
||||||
// This enables predictable ID lookup without relying on resume context chains
|
|
||||||
const sessionId = executionContext?.session?.id || 'standalone'
|
const sessionId = executionContext?.session?.id || 'standalone'
|
||||||
const fixedExecutionId = `${sessionId}-${batch.groupId}` // e.g., "implement-auth-2025-12-13-P1"
|
const fixedExecutionId = `${sessionId}-${batch.groupId}` // e.g., "implement-auth-2025-12-13-P1"
|
||||||
|
|
||||||
@@ -414,16 +419,12 @@ 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} --resume ${previousCliId}`
|
||||||
: `ccw cli -p "${buildExecutionPrompt(batch)}" --tool codex --mode write --id ${fixedExecutionId}`
|
: `ccw cli -p "${buildExecutionPrompt(batch)}" --tool codex --mode write --id ${fixedExecutionId}`
|
||||||
|
|
||||||
bash_result = Bash(
|
// Execute in background, stop output and wait for task hook callback
|
||||||
|
Bash(
|
||||||
command=cli_command,
|
command=cli_command,
|
||||||
timeout=timeoutByComplexity[planObject.complexity] || 3600000
|
run_in_background=true
|
||||||
)
|
)
|
||||||
|
// STOP HERE - CLI executes in background, task hook will notify on completion
|
||||||
// Execution ID is now predictable: ${fixedExecutionId}
|
|
||||||
// Can also extract from output: "ID: implement-auth-2025-12-13-P1"
|
|
||||||
const cliExecutionId = fixedExecutionId
|
|
||||||
|
|
||||||
// Update TodoWrite when execution completes
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Resume on Failure** (with fixed ID):
|
**Resume on Failure** (with fixed ID):
|
||||||
|
|||||||
@@ -386,8 +386,10 @@ async function outputAction(conversationId: string | undefined, options: OutputV
|
|||||||
|
|
||||||
if (options.final) {
|
if (options.final) {
|
||||||
// Final result only with usage hint
|
// Final result only with usage hint
|
||||||
if (result.stdout) {
|
// Prefer parsedOutput (filtered, intermediate content removed) over raw stdout
|
||||||
console.log(result.stdout.content);
|
const outputContent = result.parsedOutput?.content || result.stdout?.content;
|
||||||
|
if (outputContent) {
|
||||||
|
console.log(outputContent);
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
console.log(chalk.gray('─'.repeat(60)));
|
console.log(chalk.gray('─'.repeat(60)));
|
||||||
|
|||||||
@@ -981,6 +981,13 @@ async function executeCliTool(
|
|||||||
|
|
||||||
// Create new turn - cache full output when not streaming (default)
|
// Create new turn - cache full output when not streaming (default)
|
||||||
const shouldCache = !parsed.data.stream;
|
const shouldCache = !parsed.data.stream;
|
||||||
|
|
||||||
|
// Compute parsed output (filtered, intermediate content removed) for final display
|
||||||
|
const computedParsedOutput = flattenOutputUnits(allOutputUnits, {
|
||||||
|
excludeTypes: ['stderr', 'progress', 'metadata', 'system', 'tool_call', 'thought', 'code', 'file_diff', 'streaming_content'],
|
||||||
|
stripCommandJsonBlocks: true // Strip embedded command execution JSON from agent_message
|
||||||
|
});
|
||||||
|
|
||||||
const newTurnOutput = {
|
const newTurnOutput = {
|
||||||
stdout: stdout.substring(0, 10240), // Truncate preview to 10KB
|
stdout: stdout.substring(0, 10240), // Truncate preview to 10KB
|
||||||
stderr: stderr.substring(0, 2048), // Truncate preview to 2KB
|
stderr: stderr.substring(0, 2048), // Truncate preview to 2KB
|
||||||
@@ -988,6 +995,7 @@ async function executeCliTool(
|
|||||||
cached: shouldCache,
|
cached: shouldCache,
|
||||||
stdout_full: shouldCache ? stdout : undefined,
|
stdout_full: shouldCache ? stdout : undefined,
|
||||||
stderr_full: shouldCache ? stderr : undefined,
|
stderr_full: shouldCache ? stderr : undefined,
|
||||||
|
parsed_output: computedParsedOutput || undefined, // Filtered output for final display
|
||||||
structured: allOutputUnits // Save structured IR units
|
structured: allOutputUnits // Save structured IR units
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1148,9 +1156,7 @@ async function executeCliTool(
|
|||||||
exit_code: code,
|
exit_code: code,
|
||||||
duration_ms: duration,
|
duration_ms: duration,
|
||||||
output: newTurnOutput,
|
output: newTurnOutput,
|
||||||
parsedOutput: flattenOutputUnits(allOutputUnits, {
|
parsedOutput: computedParsedOutput // Use already-computed filtered output
|
||||||
excludeTypes: ['stderr', 'progress', 'metadata', 'system', 'tool_call', 'thought']
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface ConversationTurn {
|
|||||||
cached?: boolean;
|
cached?: boolean;
|
||||||
stdout_full?: string;
|
stdout_full?: string;
|
||||||
stderr_full?: string;
|
stderr_full?: string;
|
||||||
|
parsed_output?: string; // Filtered output (intermediate content removed)
|
||||||
structured?: CliOutputUnit[]; // Structured IR sequence for advanced parsing
|
structured?: CliOutputUnit[]; // Structured IR sequence for advanced parsing
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -326,6 +327,7 @@ export class CliHistoryStore {
|
|||||||
const hasCached = turnsInfo.some(col => col.name === 'cached');
|
const hasCached = turnsInfo.some(col => col.name === 'cached');
|
||||||
const hasStdoutFull = turnsInfo.some(col => col.name === 'stdout_full');
|
const hasStdoutFull = turnsInfo.some(col => col.name === 'stdout_full');
|
||||||
const hasStderrFull = turnsInfo.some(col => col.name === 'stderr_full');
|
const hasStderrFull = turnsInfo.some(col => col.name === 'stderr_full');
|
||||||
|
const hasParsedOutput = turnsInfo.some(col => col.name === 'parsed_output');
|
||||||
|
|
||||||
if (!hasCached) {
|
if (!hasCached) {
|
||||||
console.log('[CLI History] Migrating database: adding cached column to turns table...');
|
console.log('[CLI History] Migrating database: adding cached column to turns table...');
|
||||||
@@ -342,6 +344,11 @@ export class CliHistoryStore {
|
|||||||
this.db.exec('ALTER TABLE turns ADD COLUMN stderr_full TEXT;');
|
this.db.exec('ALTER TABLE turns ADD COLUMN stderr_full TEXT;');
|
||||||
console.log('[CLI History] Migration complete: stderr_full column added');
|
console.log('[CLI History] Migration complete: stderr_full column added');
|
||||||
}
|
}
|
||||||
|
if (!hasParsedOutput) {
|
||||||
|
console.log('[CLI History] Migrating database: adding parsed_output column to turns table...');
|
||||||
|
this.db.exec('ALTER TABLE turns ADD COLUMN parsed_output TEXT;');
|
||||||
|
console.log('[CLI History] Migration complete: parsed_output column added');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[CLI History] Migration error:', (err as Error).message);
|
console.error('[CLI History] Migration error:', (err as Error).message);
|
||||||
// Don't throw - allow the store to continue working with existing schema
|
// Don't throw - allow the store to continue working with existing schema
|
||||||
@@ -448,8 +455,8 @@ export class CliHistoryStore {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const upsertTurn = this.db.prepare(`
|
const upsertTurn = this.db.prepare(`
|
||||||
INSERT INTO turns (conversation_id, turn_number, timestamp, prompt, duration_ms, status, exit_code, stdout, stderr, truncated, cached, stdout_full, stderr_full)
|
INSERT INTO turns (conversation_id, turn_number, timestamp, prompt, duration_ms, status, exit_code, stdout, stderr, truncated, cached, stdout_full, stderr_full, parsed_output)
|
||||||
VALUES (@conversation_id, @turn_number, @timestamp, @prompt, @duration_ms, @status, @exit_code, @stdout, @stderr, @truncated, @cached, @stdout_full, @stderr_full)
|
VALUES (@conversation_id, @turn_number, @timestamp, @prompt, @duration_ms, @status, @exit_code, @stdout, @stderr, @truncated, @cached, @stdout_full, @stderr_full, @parsed_output)
|
||||||
ON CONFLICT(conversation_id, turn_number) DO UPDATE SET
|
ON CONFLICT(conversation_id, turn_number) DO UPDATE SET
|
||||||
timestamp = @timestamp,
|
timestamp = @timestamp,
|
||||||
prompt = @prompt,
|
prompt = @prompt,
|
||||||
@@ -461,7 +468,8 @@ export class CliHistoryStore {
|
|||||||
truncated = @truncated,
|
truncated = @truncated,
|
||||||
cached = @cached,
|
cached = @cached,
|
||||||
stdout_full = @stdout_full,
|
stdout_full = @stdout_full,
|
||||||
stderr_full = @stderr_full
|
stderr_full = @stderr_full,
|
||||||
|
parsed_output = @parsed_output
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const transaction = this.db.transaction(() => {
|
const transaction = this.db.transaction(() => {
|
||||||
@@ -496,7 +504,8 @@ export class CliHistoryStore {
|
|||||||
truncated: turn.output.truncated ? 1 : 0,
|
truncated: turn.output.truncated ? 1 : 0,
|
||||||
cached: turn.output.cached ? 1 : 0,
|
cached: turn.output.cached ? 1 : 0,
|
||||||
stdout_full: turn.output.stdout_full || null,
|
stdout_full: turn.output.stdout_full || null,
|
||||||
stderr_full: turn.output.stderr_full || null
|
stderr_full: turn.output.stderr_full || null,
|
||||||
|
parsed_output: turn.output.parsed_output || null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -543,7 +552,8 @@ export class CliHistoryStore {
|
|||||||
truncated: !!t.truncated,
|
truncated: !!t.truncated,
|
||||||
cached: !!t.cached,
|
cached: !!t.cached,
|
||||||
stdout_full: t.stdout_full || undefined,
|
stdout_full: t.stdout_full || undefined,
|
||||||
stderr_full: t.stderr_full || undefined
|
stderr_full: t.stderr_full || undefined,
|
||||||
|
parsed_output: t.parsed_output || undefined
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
@@ -588,6 +598,7 @@ export class CliHistoryStore {
|
|||||||
turnNumber: number;
|
turnNumber: number;
|
||||||
stdout?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
stdout?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
||||||
stderr?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
stderr?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
||||||
|
parsedOutput?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
||||||
cached: boolean;
|
cached: boolean;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -614,6 +625,7 @@ export class CliHistoryStore {
|
|||||||
turnNumber: number;
|
turnNumber: number;
|
||||||
stdout?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
stdout?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
||||||
stderr?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
stderr?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
||||||
|
parsedOutput?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
||||||
cached: boolean;
|
cached: boolean;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -652,6 +664,19 @@ export class CliHistoryStore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add parsed output if available (filtered output for final display)
|
||||||
|
if (turn.parsed_output) {
|
||||||
|
const parsedContent = turn.parsed_output;
|
||||||
|
const totalBytes = parsedContent.length;
|
||||||
|
const content = parsedContent.substring(offset, offset + limit);
|
||||||
|
result.parsedOutput = {
|
||||||
|
content,
|
||||||
|
totalBytes,
|
||||||
|
offset,
|
||||||
|
hasMore: offset + limit < totalBytes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1117,13 +1117,15 @@ export function flattenOutputUnits(
|
|||||||
excludeTypes?: CliOutputUnitType[];
|
excludeTypes?: CliOutputUnitType[];
|
||||||
includeTimestamps?: boolean;
|
includeTimestamps?: boolean;
|
||||||
separator?: string;
|
separator?: string;
|
||||||
|
stripCommandJsonBlocks?: boolean; // Strip embedded command execution JSON code blocks from stdout
|
||||||
}
|
}
|
||||||
): string {
|
): string {
|
||||||
const {
|
const {
|
||||||
includeTypes,
|
includeTypes,
|
||||||
excludeTypes,
|
excludeTypes,
|
||||||
includeTimestamps = false,
|
includeTimestamps = false,
|
||||||
separator = '\n'
|
separator = '\n',
|
||||||
|
stripCommandJsonBlocks = false
|
||||||
} = options || {};
|
} = options || {};
|
||||||
|
|
||||||
// Special handling for streaming_content: concatenate all into a single stdout unit
|
// Special handling for streaming_content: concatenate all into a single stdout unit
|
||||||
@@ -1163,7 +1165,20 @@ export function flattenOutputUnits(
|
|||||||
|
|
||||||
// Extract text content based on type
|
// Extract text content based on type
|
||||||
if (typeof unit.content === 'string') {
|
if (typeof unit.content === 'string') {
|
||||||
text += unit.content;
|
let content = unit.content;
|
||||||
|
|
||||||
|
// Strip command execution JSON code blocks if requested (codex agent_message often includes these)
|
||||||
|
if (stripCommandJsonBlocks && unit.type === 'stdout') {
|
||||||
|
// Pattern 1: Backtick-wrapped JSON blocks
|
||||||
|
// Format: ```...{"command":"...","output":"...","exitCode":N,"status":"..."...}...```
|
||||||
|
// Uses [\s\S]*? to match any characters (including newlines) non-greedily
|
||||||
|
content = content.replace(/```[\s\S]*?\{"command":[\s\S]*?,"status":"[^"]*"\}[\s\S]*?```/g, '').trim();
|
||||||
|
// Pattern 2: Raw JSON command execution (no backticks)
|
||||||
|
// Matches complete JSON object with command/output/exitCode/status fields
|
||||||
|
content = content.replace(/\{"command":[\s\S]*?,"status":"[^"]*"\}/g, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
text += content;
|
||||||
} else if (typeof unit.content === 'object' && unit.content !== null) {
|
} else if (typeof unit.content === 'object' && unit.content !== null) {
|
||||||
// Handle structured content with type-specific formatting
|
// Handle structured content with type-specific formatting
|
||||||
switch (unit.type) {
|
switch (unit.type) {
|
||||||
|
|||||||
Reference in New Issue
Block a user