feat: AI 命令执行实时流式推送到工作空间终端

- execWorkspaceCommand 新增 onOutput 回调,stdout/stderr 实时推送
- ipc.ts workspace:execTool 实时转发输出 + 发送 toolDone 完成通知
- 渲染进程 appendToolStreamOutput 流式显示,handleToolDone 显示退出状态
- 去掉 tool-handlers.ts 中的一次性完整输出推送
- 完整流程:用户确认 → 实时输出 → 完成通知 → AI 回复结果
This commit is contained in:
thzxx
2026-04-08 13:06:57 +08:00
parent d150b53116
commit d5e5f5f58a
6 changed files with 70 additions and 51 deletions
+39 -30
View File
@@ -182,10 +182,15 @@ export async function initWorkspacePanel(): Promise<void> {
handleProcessExit(data.id, data.code);
});
// 监听 AI Tool Calling 命令输出,同步到终端面板
// 监听 AI Tool Calling 实时输出,流式推送到终端面板
bridge.workspace.onToolOutput((data) => {
logDebug('工作空间收到工具输出', `command: ${data.command.slice(0, 50)} | exitCode: ${data.exitCode}`);
appendToolOutput(data.command, data.stdout, data.stderr, data.exitCode);
appendToolStreamOutput(data.command, data.type, data.data);
});
// 监听 AI Tool Calling 命令执行完毕
bridge.workspace.onToolDone((data) => {
logDebug('工作空间工具命令完成', `command: ${data.command.slice(0, 50)} | exitCode: ${data.exitCode}`);
handleToolDone(data.command, data.exitCode);
});
// 绑定 UI 事件
@@ -263,46 +268,50 @@ function getActiveSession(): TerminalSession | undefined {
return terminalSessions.find(s => s.id === activeTerminalId);
}
/** AI Tool Calling 命令输出 → 追加到活跃终端 */
function appendToolOutput(command: string, stdout: string, stderr: string, exitCode: number | null): void {
/** AI Tool Calling 实时输出 → 流式追加到活跃终端 */
function appendToolStreamOutput(command: string, type: 'stdout' | 'stderr', data: string): void {
const session = getActiveSession();
if (!session) return;
// 记录当前 AI 命令
currentAiCommand = command;
session.running = true;
updateHint();
// 显示执行的命令
session.lines.push({ type: 'system', text: `$ ${command} (AI)` });
// 显示 stdout
if (stdout) {
for (const line of stdout.split('\n')) {
if (line) session.lines.push({ type: 'stdout', text: line });
}
// 首次输出时显示命令
if (!currentAiCommand) {
currentAiCommand = command;
session.running = true;
session.lines.push({ type: 'system', text: `$ ${command} (AI)` });
updateHint();
updateStopBtnState();
}
// 显示 stderr
if (stderr) {
for (const line of stderr.split('\n')) {
if (line) session.lines.push({ type: 'stderr', text: line });
// 流式追加输出(按行分割,保留最后一行用于后续追加)
const lines = data.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (i < lines.length - 1 || line) {
const cleanLine = line.replace(/\r/g, '');
if (cleanLine) session.lines.push({ type, text: cleanLine });
}
}
// 显示退出状态
const exitMsg = exitCode === 0 ? '✓ AI 命令执行成功' : `✗ AI 命令退出 (code: ${exitCode})`;
session.lines.push({ type: 'system', text: exitMsg });
// 命令结束
session.running = false;
currentAiCommand = null;
// 限制最大行数
if (session.lines.length > 3000) {
session.lines = session.lines.slice(-2000);
}
renderTerminal();
}
/** AI Tool Calling 命令执行完毕 */
function handleToolDone(command: string, exitCode: number | null): void {
const session = getActiveSession();
if (!session) return;
// 显示退出状态
const exitMsg = exitCode === 0 ? '✓ AI 命令执行成功' : `✗ AI 命令退出 (code: ${exitCode})`;
session.lines.push({ type: 'system', text: exitMsg });
session.running = false;
currentAiCommand = null;
renderTerminal();
updateStopBtnState();
updateHint();