Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

fix(compact): 压缩后重注入按字节下标切片 → UTF-8 边界 panic

Đã đóng Phù hợp với người mới
#159 5 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
72/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
rust
Lĩnh vực
ai-infra-agents

Hướng nghiên cứu

Bắt đầu với hai phép cắt chuỗi theo chỉ số byte trong src/compact/reinject.rs: FileReinjector quanh dòng 79-85 và SkillReinjector quanh dòng 288-293. Thêm hàm hỗ trợ truncate_on_char_boundary(s, max) lùi lại bằng is_char_boundary, sau đó thêm bốn kiểm thử được liệt kê trong đặc tả, bao gồm một chuỗi CJK vượt ngân sách. Hoàn thành khi cả hai reinjector đều không panic với nội dung nhiều byte và dấu hiệu cắt ngắn hiện có vẫn xuất hiện.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

needs-human

优先级 P1 · 依赖:无 · 本单属 #186「上下文与压缩契约」簇(tracking 单,见该单的落地顺序与簇级验收)

基线 HEAD = 14bac15a。

Summary

  • src/compact/reinject.rs:79-85(FileReinjector):
let max_chars = self.per_file_budget * 4;
let truncated = if content.len() > max_chars {
    format!("{}...\n[... truncated for compaction; use Read on the path for full text]", &content[..max_chars])

content 是 String,&content[..max_chars] 是字节下标切片;max_chars = 5_000 * 4 = 20_000(DEFAULT_PER_FILE_BUDGET=5_000,src/preset.rs:69/565)。

  • 技能注入同型:reinject.rs:288-293 的 &body[..max_chars]。
  • 全目录无保护:grep -rn "char_boundary" --include=*.rs src/compact 零命中。
  • 内容来自 Read 工具每次完整读入的 ReadFileState(src/tools/execution/fs.rs:356);调用点在 src/runtime.rs:900 与 :915(跨轮压缩内),panic 发生在 AgentRuntime::run 内、turn 已落盘之后,运行时路径没有 catch_unwind。
  • Rust 语义保证:非字符边界的字节切片必 panic(byte index 20000 is not a char boundary)。

影响

默认 standard 预设(开启 reinject_recent_files)下,agent 读过任意一个 >20KB 且含非 ASCII 的文件(本仓中文文档即是:一个汉字 3 字节,20_000 落在字符中间的概率约 2/3),此后一次跨轮压缩即 panic,终结该 turn 的任务(HTTP 请求任务 / CLI 进程)。技能形态同理(SKILL.md 正文 >20KB 且含中文)。

建议

  1. 两处都改为按字符边界截断:content.floor_char_boundary(max_chars)(或自行用 is_char_boundary 回退),再切片。
  2. 顺带把 max_chars 更名/改成与语义一致(当前是按「预算 token × 4」当字节用)。
  3. 验收:新增测试——构造一个多字节字符跨在 20_000 字节上的字符串,调用两个 reinjector,断言不 panic 且输出是合法 UTF-8 前缀。

边界

不覆盖压缩摘要/前缀注入侧的其他问题(见相邻单)。

实施规格(补充 · 2026-10-10)

目标:两处按字节下标的切片改为按字符边界,消除跨轮压缩路径上的确定性 panic。

改动清单

文件 改动
src/compact/reinject.rs:79-85(FileReinjector) &content[..max_chars] → 边界安全截断
src/compact/reinject.rs:288-293(SkillReinjector) &body[..max_chars] 同上
同文件顶部 新增 pub(crate) fn truncate_on_char_boundary(s: &str, max_bytes: usize) -> &str

设计

/// Truncate `s` to at most `max_bytes` bytes without splitting a char.
/// MSRV 1.75:`str::floor_char_boundary` 仍是 nightly,故手写。
pub(crate) fn truncate_on_char_boundary(s: &str, max: usize) -> &str {
    if max >= s.len() { return s; }
    let mut end = max;
    while end > 0 && !s.is_char_boundary(end) { end -= 1; }
    &s[..end]
}

调用点保持既有格式串不变(省略号 + [... truncated for compaction; use Read on the path for full text])。

测试

用例 断言
truncate_stops_at_char_boundary "中".repeat(10) 截 5 字节 → 返回 "中"(3 字节),不 panic
truncate_returns_full_string_when_short max >= len 时原样返回
file_reinjector_survives_cjk_over_budget 7000 个汉字(21000 字节)经 FileReinjector::reinject → 不 panic,输出含截断标记
skill_reinjector_survives_cjk_over_budget 同型,走 SkillReinjector

陷阱

  • max_chars 这个名字本身就是误导(它按"预算 token × 4"算,实际当字节用)。建议同 PR 更名 max_bytes,并保留一句注释说明"预算 token ≈ 字节 / 4"。
  • 截断后的 att_len_chars(reinject.rs 内用于预算累加)现阶段按 truncated.len() / 4 + 1 计算,保持原样即可,不要在本单改动预算语义。

不做:不改 per_file_budget / token_budget 的默认值;不改注入内容的排版。

Ngôn ngữ chính
Rust
Star
4
Fork
0
Merge trung bình
5 giờ 32 phút
Pull request đã merge (30 ngày)
7

Chuẩn bị môi trường

  • Có Dockerfile hoặc tệp Docker Compose
  • Không có mẫu pull request
  • Không có hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của jeffkit/recursive

Tất cả issue của jeffkit/recursive

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.