✨ feat(vscode): 提交代码按钮实时显示进度并预检 git 身份
- spawnClaude 新增 onProgress 回调,给了回调就改走 stream-json 逐行输出,按行解析出工具调用 / 回复文本 - 新增 claudeProgress 把每行 NDJSON 压成可读进度短语 - 提交前先读 git user.name / user.email,缺失直接提示,不再让 claude 白跑 - 运行期间用带转圈的 toast 滚动显示进度,完成后带上 claude 的结果摘要 Claude-Session: https://claude.ai/code/session_01HTMgwaE3woqM5yvsHiuSAa
This commit is contained in:
1 parent
443a8e472a
commit
73a0c46f9f
6 files changed
+322
-25
No files matched your search
@@ -1,16 +1,18 @@
|
||||
/**
|
||||
* Typed wrapper around the `claude` CLI in `-p` (one-shot) mode with
|
||||
* `--output-format json`. We rely on `claude` being available on PATH;
|
||||
* callers should surface the install-hint error message to the user when
|
||||
* ENOENT is raised.
|
||||
* Typed wrapper around the `claude` CLI in `-p` (one-shot) mode. We rely on
|
||||
* `claude` being available on PATH; callers should surface the install-hint
|
||||
* error message to the user when ENOENT is raised.
|
||||
*
|
||||
* Two execution paths share the same output-parsing logic:
|
||||
*
|
||||
* - **Text-only** (no images): plain `claude -p "<prompt>" --output-format json`
|
||||
* via `spawn` with stdin ignored so the CLI does not wait for piped input.
|
||||
* When the caller passes `onProgress`, this path switches to
|
||||
* `--output-format stream-json --verbose` so tool calls can be reported
|
||||
* live instead of only when the process exits.
|
||||
*
|
||||
* - **With images**: switch to `claude -p --input-format stream-json
|
||||
* --output-format json` via `spawn`, then feed a single NDJSON line on
|
||||
* --output-format stream-json` via `spawn`, then feed a single NDJSON line on
|
||||
* stdin containing the user message with text + image content blocks
|
||||
* (Anthropic Messages API shape). The official docs state single-message
|
||||
* input does NOT support image attachments — stream-json is the only
|
||||
@@ -21,6 +23,7 @@
|
||||
import { spawn } from 'node:child_process'
|
||||
import * as path from 'node:path'
|
||||
import { resolveClaudeBin } from './claudeBin'
|
||||
import { summarizeClaudeStreamLine } from './claudeProgress'
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 300_000
|
||||
const MAX_BUFFER_BYTES = 10 * 1024 * 1024
|
||||
@@ -177,6 +180,27 @@ function buildClaudeExitDiagnostic(stderr: string, stdout: string): string {
|
||||
return extractStdoutDiagnostic(stdout) ?? '(无 stderr/stdout)'
|
||||
}
|
||||
|
||||
export type ClaudeProgressListener = (label: string) => void
|
||||
|
||||
/**
|
||||
* 按行切 stdout:stream-json 每行一个对象,但 data 事件的 chunk 边界和行边界
|
||||
* 无关,一行常被拆进两个 chunk,所以末尾那段不完整的残行要留到下次拼上。
|
||||
*/
|
||||
function createProgressFeeder(onProgress?: ClaudeProgressListener): (chunk: string) => void {
|
||||
if (!onProgress)
|
||||
return () => {}
|
||||
let pending = ''
|
||||
return (chunk) => {
|
||||
pending += chunk
|
||||
const lines = pending.split('\n')
|
||||
pending = lines.pop() ?? ''
|
||||
for (const line of lines) {
|
||||
for (const label of summarizeClaudeStreamLine(line))
|
||||
onProgress(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildStreamJsonLine(prompt: string, images: ClaudeImage[]): string {
|
||||
const content: Array<Record<string, unknown>> = [
|
||||
{ type: 'text', text: prompt },
|
||||
@@ -246,14 +270,16 @@ export async function spawnClaude(opts: {
|
||||
bare?: boolean
|
||||
/** 续会话:传入则 `--resume <id>`,让本次 `-p` 接在该会话之后(复用上下文,省 token)。 */
|
||||
resumeSessionId?: string
|
||||
/** 实时进度:每识别出一个工具调用 / 一句回复就回调一次可读标签。 */
|
||||
onProgress?: ClaudeProgressListener
|
||||
}): Promise<ClaudeResult> {
|
||||
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
||||
const hasImages = !!opts.images && opts.images.length > 0
|
||||
|
||||
if (!hasImages) {
|
||||
return spawnClaudeText(opts.prompt, opts.cwd, timeoutMs, opts.profilePath, opts.bare, opts.resumeSessionId)
|
||||
return spawnClaudeText(opts.prompt, opts.cwd, timeoutMs, opts.profilePath, opts.bare, opts.resumeSessionId, opts.onProgress)
|
||||
}
|
||||
return spawnClaudeStreamed(opts.prompt, opts.cwd, timeoutMs, opts.images!, opts.profilePath, opts.bare, opts.resumeSessionId)
|
||||
return spawnClaudeStreamed(opts.prompt, opts.cwd, timeoutMs, opts.images!, opts.profilePath, opts.bare, opts.resumeSessionId, opts.onProgress)
|
||||
}
|
||||
|
||||
function spawnClaudeText(
|
||||
@@ -263,7 +289,9 @@ function spawnClaudeText(
|
||||
profilePath?: string,
|
||||
bare?: boolean,
|
||||
resumeSessionId?: string,
|
||||
onProgress?: ClaudeProgressListener,
|
||||
): Promise<ClaudeResult> {
|
||||
// `--output-format json` 要到进程结束才有输出;要报进度就得换成逐行吐的 stream-json
|
||||
const args = [
|
||||
...(bare ? ['--bare'] : []),
|
||||
'--dangerously-skip-permissions',
|
||||
@@ -272,7 +300,7 @@ function spawnClaudeText(
|
||||
'-p',
|
||||
prompt,
|
||||
'--output-format',
|
||||
'json',
|
||||
...(onProgress ? ['stream-json', '--verbose'] : ['json']),
|
||||
]
|
||||
|
||||
const bin = resolveClaudeBin()
|
||||
@@ -284,6 +312,7 @@ function spawnClaudeText(
|
||||
let stderr = ''
|
||||
let stdoutBytes = 0
|
||||
let settled = false
|
||||
const feedProgress = createProgressFeeder(onProgress)
|
||||
|
||||
const child = spawn(bin, args, {
|
||||
cwd,
|
||||
@@ -332,7 +361,9 @@ function spawnClaudeText(
|
||||
))
|
||||
return
|
||||
}
|
||||
stdout += chunk.toString('utf8')
|
||||
const text = chunk.toString('utf8')
|
||||
stdout += text
|
||||
feedProgress(text)
|
||||
})
|
||||
|
||||
child.stderr.on('data', (chunk: Buffer) => {
|
||||
@@ -377,6 +408,7 @@ function spawnClaudeStreamed(
|
||||
profilePath?: string,
|
||||
bare?: boolean,
|
||||
resumeSessionId?: string,
|
||||
onProgress?: ClaudeProgressListener,
|
||||
): Promise<ClaudeResult> {
|
||||
// Local Claude Code v2.1.143 enforces that --input-format=stream-json must
|
||||
// be paired with --output-format=stream-json (the friendlier `--output-format
|
||||
@@ -405,6 +437,7 @@ function spawnClaudeStreamed(
|
||||
let stderr = ''
|
||||
let stdoutBytes = 0
|
||||
let settled = false
|
||||
const feedProgress = createProgressFeeder(onProgress)
|
||||
|
||||
const child = spawn(bin, args, {
|
||||
cwd,
|
||||
@@ -453,7 +486,9 @@ function spawnClaudeStreamed(
|
||||
))
|
||||
return
|
||||
}
|
||||
stdout += chunk.toString('utf8')
|
||||
const text = chunk.toString('utf8')
|
||||
stdout += text
|
||||
feedProgress(text)
|
||||
})
|
||||
|
||||
child.stderr.on('data', (chunk: Buffer) => {
|
||||
|
||||
Reference in new issue
Block a user