- spawnClaude 新增 onProgress 回调,给了回调就改走 stream-json 逐行输出,按行解析出工具调用 / 回复文本 - 新增 claudeProgress 把每行 NDJSON 压成可读进度短语 - 提交前先读 git user.name / user.email,缺失直接提示,不再让 claude 白跑 - 运行期间用带转圈的 toast 滚动显示进度,完成后带上 claude 的结果摘要 Claude-Session: https://claude.ai/code/session_01HTMgwaE3woqM5yvsHiuSAa
84 lines
3.5 KiB
TypeScript
84 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { summarizeClaudeStreamLine } from './claudeProgress'
|
|
|
|
function assistantLine(content: unknown[]): string {
|
|
return JSON.stringify({ type: 'assistant', message: { role: 'assistant', content } })
|
|
}
|
|
|
|
describe('summarizeClaudeStreamLine', () => {
|
|
it('工具 Bash 有 description 时优先用 description', () => {
|
|
const line = assistantLine([
|
|
{ type: 'tool_use', name: 'Bash', input: { command: 'pnpm test', description: '跑全量测试' } },
|
|
])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual(['执行命令:跑全量测试'])
|
|
})
|
|
|
|
it('工具 Bash 没 description 时回退到 command,超长截断', () => {
|
|
const command = 'x'.repeat(80)
|
|
const line = assistantLine([{ type: 'tool_use', name: 'Bash', input: { command } }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual([`执行命令:${'x'.repeat(60)}…`])
|
|
})
|
|
|
|
it('工具 Read 只显示文件名', () => {
|
|
const line = assistantLine([{ type: 'tool_use', name: 'Read', input: { file_path: '/abs/dir/x.py' } }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual(['读取 x.py'])
|
|
})
|
|
|
|
it('工具 Edit / Write / MultiEdit 显示为修改', () => {
|
|
for (const name of ['Edit', 'Write', 'MultiEdit']) {
|
|
const line = assistantLine([{ type: 'tool_use', name, input: { file_path: '/a/b/c.ts' } }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual(['修改 c.ts'])
|
|
}
|
|
})
|
|
|
|
it('工具 Grep / Glob 显示搜索模式', () => {
|
|
const line = assistantLine([{ type: 'tool_use', name: 'Grep', input: { pattern: 'foo.*bar' } }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual(['搜索 foo.*bar'])
|
|
})
|
|
|
|
it('未知工具显示调用 + 工具名', () => {
|
|
const line = assistantLine([{ type: 'tool_use', name: 'TodoWrite', input: {} }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual(['调用 TodoWrite'])
|
|
})
|
|
|
|
it('text 块取第一行并截断', () => {
|
|
const line = assistantLine([{ type: 'text', text: `${'好'.repeat(70)}\n第二行` }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual([`${'好'.repeat(60)}…`])
|
|
})
|
|
|
|
it('空白 text 块忽略', () => {
|
|
const line = assistantLine([{ type: 'text', text: ' \n\n' }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual([])
|
|
})
|
|
|
|
it('thinking 块忽略', () => {
|
|
const line = assistantLine([{ type: 'thinking', thinking: '让我想想' }])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual([])
|
|
})
|
|
|
|
it('system / user / result 行忽略', () => {
|
|
expect(summarizeClaudeStreamLine(JSON.stringify({ type: 'system', subtype: 'init' }))).toEqual([])
|
|
expect(summarizeClaudeStreamLine(JSON.stringify({
|
|
type: 'user',
|
|
message: { content: [{ type: 'tool_result', content: 'ok' }] },
|
|
}))).toEqual([])
|
|
expect(summarizeClaudeStreamLine(JSON.stringify({ type: 'result', result: 'done', session_id: 's' }))).toEqual([])
|
|
})
|
|
|
|
it('坏 JSON 返回空数组', () => {
|
|
expect(summarizeClaudeStreamLine('{not json')).toEqual([])
|
|
expect(summarizeClaudeStreamLine('')).toEqual([])
|
|
expect(summarizeClaudeStreamLine('null')).toEqual([])
|
|
})
|
|
|
|
it('多个 block 按顺序输出', () => {
|
|
const line = assistantLine([
|
|
{ type: 'thinking', thinking: '...' },
|
|
{ type: 'text', text: '先看看代码' },
|
|
{ type: 'tool_use', name: 'Read', input: { file_path: '/p/a.ts' } },
|
|
{ type: 'tool_use', name: 'Bash', input: { command: 'git status' } },
|
|
])
|
|
expect(summarizeClaudeStreamLine(line)).toEqual(['先看看代码', '读取 a.ts', '执行命令:git status'])
|
|
})
|
|
})
|