PR #412 (streaming polish output) 合并时 pr-agent 二/三轮反馈两条流式 SSE 的边缘 case,按 "do not iterate" 协议没在 PR 内修复,单独跟踪。
1. UTF-8 chunk parsing 跨 frame 切分
`response.chunk().await` 返回的 byte 切片是按 HTTP frame 切的,可能在多字节 UTF-8 字符中间断开。当前 `polish.rs::chat_completion_messages_streaming`(流式润色)和 `chat_completion_history_streaming`(QA 流式)都直接 `std::str::from_utf8(&chunk)?`,命中切分时整条流误判为 "non-utf8 SSE chunk" 失败。
中文 / 日文 / emoji 输出概率 ~1/2700 per frame(8KB chunk × 3 bytes/char)。罕见但真实。
修法:bytes buffer + 增量 UTF-8 解码(`encoding_rs::Decoder::decode_to_string` 或手写)。
影响范围:同时 fix 两条路径(流式润色 + QA 流式)。
2. typer 部分 delta 丢失
`coordinator/dictation.rs::run_streaming_polish` 的 typer 任务里:
```rust
match crate::unicode_keystroke::type_unicode_chunk(&delta) {
Ok(()) => typed_text.push_str(&delta),
Err(e) => first_failure = Some(e.to_string()),
}
```
`type_unicode_chunk` 内部逐 codepoint 发送,如果在第 N 字成功、第 N+1 字失败,前 N 字已经物理落到屏幕但不会进 `typed_text`。导致 history / clipboard 比屏幕短,第一字 case 还可能触发 "zero chars typed → fallback to one-shot" 让用户屏幕上看到重复内容。
修法:`type_unicode_chunk` 返回实际成功字数(`Result<usize, TypeError>` 而非 `Result<(), TypeError>`),typer 按返回值 truncate delta 后 push。
罕见:每个 SSE delta 通常 1-5 字,partial-delta 失败只丢前缀部分,差值 ≤ delta size。
参考
PR #412 pr-agent 评论:
- 第 3 轮:UTF-8 parsing
- 第 4 轮:Partial chunk loss
PR #412 (streaming polish output) 合并时 pr-agent 二/三轮反馈两条流式 SSE 的边缘 case,按 "do not iterate" 协议没在 PR 内修复,单独跟踪。
1. UTF-8 chunk parsing 跨 frame 切分
`response.chunk().await` 返回的 byte 切片是按 HTTP frame 切的,可能在多字节 UTF-8 字符中间断开。当前 `polish.rs::chat_completion_messages_streaming`(流式润色)和 `chat_completion_history_streaming`(QA 流式)都直接 `std::str::from_utf8(&chunk)?`,命中切分时整条流误判为 "non-utf8 SSE chunk" 失败。
中文 / 日文 / emoji 输出概率 ~1/2700 per frame(8KB chunk × 3 bytes/char)。罕见但真实。
修法:bytes buffer + 增量 UTF-8 解码(`encoding_rs::Decoder::decode_to_string` 或手写)。
影响范围:同时 fix 两条路径(流式润色 + QA 流式)。
2. typer 部分 delta 丢失
`coordinator/dictation.rs::run_streaming_polish` 的 typer 任务里:
```rust
match crate::unicode_keystroke::type_unicode_chunk(&delta) {
Ok(()) => typed_text.push_str(&delta),
Err(e) => first_failure = Some(e.to_string()),
}
```
`type_unicode_chunk` 内部逐 codepoint 发送,如果在第 N 字成功、第 N+1 字失败,前 N 字已经物理落到屏幕但不会进 `typed_text`。导致 history / clipboard 比屏幕短,第一字 case 还可能触发 "zero chars typed → fallback to one-shot" 让用户屏幕上看到重复内容。
修法:`type_unicode_chunk` 返回实际成功字数(`Result<usize, TypeError>` 而非 `Result<(), TypeError>`),typer 按返回值 truncate delta 后 push。
罕见:每个 SSE delta 通常 1-5 字,partial-delta 失败只丢前缀部分,差值 ≤ delta size。
参考
PR #412 pr-agent 评论: