diff --git a/CHANGELOG.md b/CHANGELOG.md index f4216d2cca..fcc43d0a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Only write entries that are worth mentioning to users. ## Unreleased +- Shell: Fix `Rejected by user` misleading message when an approval modal times out — after the 300s safety timeout, the tool call now rejects with `Rejected: approval timed out`, so users returning to their session after stepping away can tell the rejection was a timeout rather than a manual rejection. Pass `--yolo`/`-y` to auto-approve tool calls if you regularly leave sessions unattended - Kosong: Fix parallel tool results being split into multiple user messages in Anthropic provider — consecutive tool-result-only user messages are now merged into a single message, complying with the Anthropic Messages API spec that all `tool_use` blocks in an assistant turn must be answered within one user message; this fixes 400 errors on strict Anthropic-compatible backends (e.g. DeepSeek `/anthropic` endpoint) and prevents the official backend from silently teaching the model to avoid parallel tool calls ## 1.37.0 (2026-04-20) diff --git a/docs/en/release-notes/changelog.md b/docs/en/release-notes/changelog.md index f3589e7714..989323aee1 100644 --- a/docs/en/release-notes/changelog.md +++ b/docs/en/release-notes/changelog.md @@ -4,6 +4,7 @@ This page documents the changes in each Kimi Code CLI release. ## Unreleased +- Shell: Fix `Rejected by user` misleading message when an approval modal times out — after the 300s safety timeout, the tool call now rejects with `Rejected: approval timed out`, so users returning to their session after stepping away can tell the rejection was a timeout rather than a manual rejection. Pass `--yolo`/`-y` to auto-approve tool calls if you regularly leave sessions unattended - Kosong: Fix parallel tool results being split into multiple user messages in Anthropic provider — consecutive tool-result-only user messages are now merged into a single message, complying with the Anthropic Messages API spec that all `tool_use` blocks in an assistant turn must be answered within one user message; this fixes 400 errors on strict Anthropic-compatible backends (e.g. DeepSeek `/anthropic` endpoint) and prevents the official backend from silently teaching the model to avoid parallel tool calls ## 1.37.0 (2026-04-20) diff --git a/docs/zh/release-notes/changelog.md b/docs/zh/release-notes/changelog.md index effba7f914..948e0a3442 100644 --- a/docs/zh/release-notes/changelog.md +++ b/docs/zh/release-notes/changelog.md @@ -4,6 +4,7 @@ ## 未发布 +- Shell:修复 approval 弹窗超时后被误报为 `Rejected by user` 的问题——300 秒安全超时后,工具调用会以 `Rejected: approval timed out` 拒绝,让离开电脑一段时间后回来的用户能分辨出这是超时而非自己的手动拒绝。经常长时间离开的话可以加 `--yolo`/`-y` 自动批准工具调用 - Kosong:修复 Anthropic 供应商将并行工具结果拆分到多个 user message 的问题——现在会将仅包含工具结果的连续 user message 合并为单条消息,以符合 Anthropic Messages API 规范(assistant 一轮中的所有 `tool_use` 必须在同一条 user message 内回答);修复了严格兼容后端(如 DeepSeek `/anthropic` 接口)返回 400 错误的问题,并避免官方后端静默地引导模型放弃并行工具调用 ## 1.37.0 (2026-04-20) diff --git a/src/kimi_cli/soul/approval.py b/src/kimi_cli/soul/approval.py index bb8bf81ba0..78f5246304 100644 --- a/src/kimi_cli/soul/approval.py +++ b/src/kimi_cli/soul/approval.py @@ -177,7 +177,8 @@ async def request( tool_name=tool_call.function.name, approval_mode="cancelled", ) - return ApprovalResult(approved=False) + record = self._runtime.get_request(request_id) + return ApprovalResult(approved=False, feedback=record.feedback if record else "") from kimi_cli.telemetry import track match response: diff --git a/tests/core/test_approval_runtime.py b/tests/core/test_approval_runtime.py index 7944bd64f2..e71d321944 100644 --- a/tests/core/test_approval_runtime.py +++ b/tests/core/test_approval_runtime.py @@ -231,3 +231,49 @@ async def test_approval_runtime_wait_for_response_times_out() -> None: record = runtime.get_request(request.id) assert record is not None assert record.status == "cancelled" + assert record.feedback == "approval timed out" + + +@pytest.mark.asyncio +async def test_approval_request_timeout_carries_feedback_to_result() -> None: + """Timeout feedback must survive round-trip through ``Approval.request``. + + Regression test: when the 300s ``wait_for_response`` safety timeout fires + (e.g. the user stepped away from their session), ``_cancel_request`` sets + ``record.feedback = "approval timed out"`` before raising + ``ApprovalCancelledError``. ``Approval.request`` must read that feedback + back into the returned ``ApprovalResult`` — otherwise the resulting + ``ToolRejectedError`` falls back to the generic "Rejected by user" brief, + hiding the timeout cause from the user. + """ + from kimi_cli.soul.approval import Approval, ApprovalState + from kimi_cli.soul.toolset import current_tool_call + from kimi_cli.wire.types import ToolCall + + runtime = ApprovalRuntime() + approval = Approval(state=ApprovalState(), runtime=runtime) + + token = current_tool_call.set( + ToolCall(id="test", function=ToolCall.FunctionBody(name="Shell", arguments=None)) + ) + try: + request_task = asyncio.create_task( + approval.request(sender="Shell", action="shell_exec", description="ls") + ) + while not runtime.list_pending(): + await asyncio.sleep(0) + pending = runtime.list_pending()[0] + # Drive the timeout path directly instead of waiting 300s: this is + # the same internal call ``wait_for_response`` makes when its own + # timeout expires (runtime.py uses ``feedback="approval timed out"``). + runtime._cancel_request(pending.id, feedback="approval timed out") + result = await request_task + finally: + current_tool_call.reset(token) + + assert result.approved is False + assert result.feedback == "approval timed out" + # The user-visible rejection surface reflects the real reason rather + # than the generic "Rejected by user" fallback. + err = result.rejection_error() + assert err.brief == "Rejected: approval timed out"