fix(acp): improve prompt timeout handling with cancel support and configurable duration#1709
fix(acp): improve prompt timeout handling with cancel support and configurable duration#1709
Conversation
…figurable duration - Use ACP session/cancel instead of disconnect on timeout to keep connection alive - Add cancelPrompt() to AcpAgent for lightweight prompt cancellation - Rename AcpAgent.stop() to kill() to clarify semantics; remove dead code - AcpAgentManager.stop() now cancels prompt; kill() destroys process - Re-read timeout config before each prompt so changes take effect immediately - Add prompt timeout setting in system preferences (30s-3600s) - Add i18n keys for all 6 locales - Add 14 unit tests for timeout and cancel logic
Code Review:fix(acp): improve prompt timeout handling with cancel support and configurable duration (#1709)变更概述本 PR 将超时后的
方案评估结论: 整体方向正确——用 问题清单🟠 HIGH — 手动取消时向用户展示 "Request cancelled" 错误 + 双 finish 信号文件: 问题代码: // AcpAgent.cancelPrompt() — 同步路径
cancelPrompt(): void {
this.connection.cancelPrompt(); // 同步 reject 所有 pending session/prompt
// ...
this.onSignalEvent({ type: 'finish', ... }); // ① 第一次 finish
}
// AcpAgent.sendMessage() catch — 异步微任务路径
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
// "Request cancelled" 不匹配任何分类条件,落入 UNKNOWN
this.emitErrorMessage(errorMsg); // ② 用户看到 "Request cancelled" 错误气泡
this.onSignalEvent({ type: 'finish', ... }); // ③ 第二次 finish问题说明: 注:这一问题在测试中被掩盖,因为测试完全 mock 了 修复建议:在 } catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
// User-initiated cancellation — finish already emitted by cancelPrompt()
if (errorMsg === 'Request cancelled') {
return { success: false, error: createAcpError(AcpErrorType.UNKNOWN, errorMsg, false) };
}
this.emitErrorMessage(errorMsg);
// ...
}🔵 LOW — 未使用的 import
|
| # | 严重级别 | 文件 | 问题 |
|---|---|---|---|
| 1 | 🟠 HIGH | src/process/agent/acp/index.ts:691 |
手动取消时展示 "Request cancelled" 错误 + double finish |
| 2 | 🔵 LOW | src/process/agent/acp/AcpConnection.ts:28 |
未使用的 import os from 'os' |
| 3 | 🔵 LOW | tests/unit/acpTimeout.test.ts |
14 处 no-explicit-any lint 违规 |
结论
"Request cancelled" 错误消息会在用户主动点击停止时显示为对话中的错误气泡,属于明确的 UX 回归;两次 finish 信号也可能在下游造成状态问题。该问题修复简单,建议处理后合并。两处 LOW 问题不阻塞合并。
本报告由本地 pr-review skill 生成,包含完整项目上下文,无截断限制。
Resolve pending prompt with null instead of rejecting, so the sendMessage catch block is not triggered and no error message is shown to the user when they manually stop generation.
|
已修复 (58620b5)。
|
Remove finish signal emission (covered by PR iOfficeAI#1709) and keep only the cronBusyGuard/status cleanup on sendMessage failure, which iOfficeAI#1709 does not address. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Remove finish signal emission (covered by PR iOfficeAI#1709) and keep only the cronBusyGuard/status cleanup on sendMessage failure, which iOfficeAI#1709 does not address. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Remove finish signal emission (covered by PR iOfficeAI#1709) and keep only the cronBusyGuard/status cleanup on sendMessage failure, which iOfficeAI#1709 does not address. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Remove finish signal emission (covered by PR iOfficeAI#1709) and keep only the cronBusyGuard/status cleanup on sendMessage failure, which iOfficeAI#1709 does not address. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Summary
disconnect()on timeout with ACPsession/cancelto keep connection alivecancelPrompt()for lightweight cancel,kill()for full teardownChanges
Core (AcpConnection)
cancelPrompt(): sendssession/cancelnotification and clears pending prompt requestshandlePromptTimeout(): unified timeout handler that calls cancel instead of disconnectsetPromptTimeout(): configurable timeout with 30s minimumAcpAgent
cancelPrompt(): cancels prompt, rejects pending permissions, emits finish signalstop()→kill()to match actual behavior (full process teardown)applyPromptTimeoutFromConfig(): re-reads config before each prompt for immediate effectkill()wrapper andstop.streamhandler in workerAcpAgentManager
stop()now callsagent.cancelPrompt()(wasdisconnect)kill()callsagent.kill()(clear naming chain)Settings UI
Related Issue
Closes #1708
Test Plan