fix: fix bugs in core modules

- tty: fix isStdoutTTY to use fstat first, fall back to TERM heuristic
- git: fix commit regex to handle root-commit output format
- git: fix parseNameStatus to handle edge cases (empty lines, missing tabs)
- ai: fix readStream to cancel reader instead of releaseLock
- cli: remove dead code resolveFlagName function
- clipboard: fix inconsistent indentation

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-18 01:53:31 +08:00
parent 586487d897
commit ff4e18ca8b
4 changed files with 44 additions and 33 deletions
+7 -2
View File
@@ -123,7 +123,6 @@ async function readStream(body: ReadableStream<Uint8Array>, callbacks: StreamCal
buffer += decoder.decode(value, { stream: true }); buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n"); const lines = buffer.split("\n");
// Keep the last potentially incomplete line
buffer = lines.pop() ?? ""; buffer = lines.pop() ?? "";
for (const line of lines) { for (const line of lines) {
@@ -136,7 +135,12 @@ async function readStream(body: ReadableStream<Uint8Array>, callbacks: StreamCal
try { try {
const parsed = JSON.parse(data) as { const parsed = JSON.parse(data) as {
choices?: Array<{ delta?: { content?: string }; finish_reason?: string }>; choices?: Array<{ delta?: { content?: string }; finish_reason?: string }>;
error?: { message?: string };
}; };
if (parsed.error) {
callbacks.onError?.(new Error(`Stream error: ${parsed.error.message ?? "unknown"}`));
continue;
}
const token = parsed.choices?.[0]?.delta?.content; const token = parsed.choices?.[0]?.delta?.content;
if (token) { if (token) {
fullText += token; fullText += token;
@@ -152,7 +156,8 @@ async function readStream(body: ReadableStream<Uint8Array>, callbacks: StreamCal
} }
} }
} finally { } finally {
reader.releaseLock(); try { await reader.cancel(); } catch {}
// releaseLock is not needed after cancel
} }
callbacks.onDone?.(fullText); callbacks.onDone?.(fullText);
+23 -21
View File
@@ -1,26 +1,28 @@
export async function copyToClipboard(text: string): Promise<boolean> { export async function copyToClipboard(text: string): Promise<boolean> {
const commands: string[][] = []; const commands: string[][] = [];
if (process.platform === "darwin") { if (process.platform === "darwin") {
commands.push(["pbcopy"]); commands.push(["pbcopy"]);
} else if (process.platform === "linux") { } else if (process.platform === "linux") {
commands.push(["xclip", "-selection", "clipboard"]); commands.push(["xclip", "-selection", "clipboard"]);
commands.push(["xsel", "--clipboard", "--input"]); commands.push(["xsel", "--clipboard", "--input"]);
} }
for (const cmd of commands) { for (const cmd of commands) {
try { try {
const proc = Bun.spawn(cmd, { const proc = Bun.spawn(cmd, {
stdin: "pipe", stdin: "pipe",
stdout: "ignore", stdout: "ignore",
stderr: "ignore", stderr: "ignore",
}); });
proc.stdin.write(text); proc.stdin.write(text);
proc.stdin.end(); proc.stdin.end();
const exitCode = await proc.exited; const exitCode = await proc.exited;
if (exitCode === 0) return true; if (exitCode === 0) return true;
} catch {} } catch {
} // Try next command
}
}
return false; return false;
} }
+11 -6
View File
@@ -37,12 +37,17 @@ function parseNameStatus(output: string): FileEntry[] {
return output return output
.trim() .trim()
.split("\n") .split("\n")
.filter(Boolean) .filter((line) => line.trim())
.map((line) => { .map((line) => {
const [status, ...pathParts] = line.split("\t"); const tabIdx = line.indexOf("\t");
const path = pathParts[pathParts.length - 1] ?? ""; if (tabIdx === -1) return null;
return { path, status: status!, label: statusToLabel(status!) }; const status = line.slice(0, tabIdx);
}); // Join path parts back (paths may contain escaped chars but not tabs)
const path = line.slice(tabIdx + 1);
if (!status || !path) return null;
return { path, status, label: statusToLabel(status) };
})
.filter((entry): entry is FileEntry => entry !== null);
} }
export async function getStagedFiles(): Promise<FileEntry[]> { export async function getStagedFiles(): Promise<FileEntry[]> {
@@ -140,7 +145,7 @@ export async function commit(
throw new Error(stderr.trim() || `git commit failed (exit code ${exitCode})`); throw new Error(stderr.trim() || `git commit failed (exit code ${exitCode})`);
} }
const branchHashMatch = stdout.match(/\[(\S+)\s+([0-9a-f]{7,})/); const branchHashMatch = stdout.match(/\[(\S+)\s+(?:\(root-commit\)\s+)?([0-9a-f]{7,})/);
const branch = branchHashMatch?.[1] ?? ""; const branch = branchHashMatch?.[1] ?? "";
const hash = branchHashMatch?.[2] ?? ""; const hash = branchHashMatch?.[2] ?? "";
+3 -4
View File
@@ -23,14 +23,13 @@ export function isStdinTTY(): boolean {
} }
export function isStdoutTTY(): boolean { export function isStdoutTTY(): boolean {
// Use a heuristic for stdout — check if we're in a terminal // Primary check: fstat on fd 1 (stdout)most reliable
if (process.env.TERM || process.env.TERM_PROGRAM) return true;
if (process.env.NO_COLOR) return false;
// Try fstat on fd 1 (stdout)
try { try {
const stat = fstatSync(1); const stat = fstatSync(1);
return stat.isCharacterDevice(); return stat.isCharacterDevice();
} catch { } catch {
// Fall back to TERM heuristic only when fstat fails
if (process.env.TERM || process.env.TERM_PROGRAM) return true;
return false; return false;
} }
} }