From 5c04f4f8b46cf30f96d5ac07048e9be6f292d549 Mon Sep 17 00:00:00 2001 From: ypshidifu Date: Sat, 26 Sep 2026 15:09:44 +0800 Subject: [PATCH] =?UTF-8?q?compaction-cn=20v4:=E5=8E=8B=E7=BC=A9=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=87=BA=E7=BD=91=E5=89=8D=E5=8F=8C=E9=87=8D=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E4=B8=AD=E6=96=87=E6=8C=87=E4=BB=A4,=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=87=AA=E5=8A=A8=E5=8E=8B=E7=BC=A9=E4=BB=8D=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E8=8B=B1=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因(A/B实测,agnes-2.5-flash):模型只认messages最末尾的指令,system段无论放头尾都被无视;手动压缩原本出中文只是跟随对话语言,与注入无关,自动压缩上下文全英文才暴露问题。 http.request钩子在压缩模板指纹(## Objective + Do not continue the task)命中后: 1) 把极短英文语言规则内联写进模板消息本身(紧跟Do not continue the task); 2) 再在messages最末尾追加一条独立中文指令。 v3只做第2条时4次里1次仍输出英文,v4双保险后Win/WSL手动与自动四象限实测全部中文。 附带诊断日志:body顶层keys、逐条message结构、命中请求全文落盘。 --- compaction-cn.ts | 217 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 198 insertions(+), 19 deletions(-) diff --git a/compaction-cn.ts b/compaction-cn.ts index 4ae6324..12fcfaf 100644 --- a/compaction-cn.ts +++ b/compaction-cn.ts @@ -8,27 +8,54 @@ // hooks 对象会被当 cleanup 调用 → TypeError: Ae is not a function 整批崩)。 // 于是压缩摘要默认产出纯英文,后续任务容易被前面的英文摘要带偏。 // -// 干预点(2026-09-25 实测定稿): -// ctx.session.hook("compaction", cb) —— 对应 SessionCompaction.prepare 阶段, -// payload = [sessionID, model, system, messages, options, agent, tools],含 system 与 messages。 -// 时序:compaction hook(prep) → 追加压缩模板 Zt.user(z$(...)) → model.request(kind="compaction") -// ⇒ 模板在钩子【之后】才拼进 messages,钩子改不到模板本身; -// model.request 虽在模板之后但 payload 无 messages,只能识别不能改写。 -// ⇒ 唯一可行干预:向 g.system 追加中文输出指令(system 会带进最终压缩请求)。 +// ── OC 2.0.15 压缩链路(逆向 opencode.exe 实锤,勿重复考古)──────────────── +// auto = SessionCompaction.compact(k) reason:"auto" (runStep 里 required:F 判定后调用) +// manual = SessionCompaction.compactManual(T) reason:"manual" +// 两者都进 G(execute) → g(N,msg) → N.prepare(args) +// → SessionModelRequest.prepare → trigger("session","compaction", payload) +// (trigger:顺序执行注册回调、返回【同一个 payload 对象】⇒ 对 system/messages 的原地 push 必然生效) +// → T.system / T.messages → e9({system:T.system, messages:NB(T6(T.messages))}) → 构造 request +// G 在 prepare 返回【之后】才拼压缩模板: +// be = pn.update(we.request, {messages:[...we.request.messages, Zt.user(z$(...))]}) +// ⇒ 钩子里追加的消息一定排在模板【前面】。 +// +// ── 2026-09-26 根因(A/B 实测,模型 agnes-2.5-flash)────────────────────── +// 注入生效但位置不对。同一模型四组对照(对话全英文、模板一致): +// A. 中文规则放 system 末尾 → cn=0 纯英文(= 插件 v1/v2 的做法) +// B. 中文指令放【模板之后】 → cn=157 中文 ✅ +// C. 中文规则放 system 最前面 → cn=0 纯英文 +// D. 模板之后追加、用英文措辞 → cn=123 中文 ✅ +// ⇒ 模型只认「最后一条消息」里的指令;system 段无论放哪都压不住。 +// ⇒ 手动压缩出中文,是因为对话本身就是中文(模型跟随对话语言),与注入无关; +// 自动压缩时上下文几乎全英文,才暴露出「指令位置不对」。 +// +// ── 因此干预点分两层(v4)──────────────────────────────────────────────── +// ① session.hook("compaction"):向 payload.system 追加完整中文规则(覆盖读 system 的模型) +// ② session.hook("http.request"):【关键】压缩请求出网前解析 JSON body, +// 检测到压缩模板(## Objective + Do not continue the task)后做两件事: +// ②-a 把极短英文语言规则【内联写进模板消息本身】(紧跟 Do not continue the task) +// ②-b 再在 messages 【最末尾】(即模板之后)追加一条独立指令 +// 2026-09-26 实测只做 ②-b 时 4 次里有 1 次模型仍输出英文,故 v4 加 ②-a 双保险。 +// 非压缩请求 / 非 JSON / 已含标记 → 原样放行,绝不改写。 +// 参考:session.hook("model.request") 实测 payload keys 只有 +// ["sessionID","agent","model","kind","baseURL","headers"],无 system/messages, +// 只能做诊断日志,改不了内容(保留日志便于日后核对)。 // // 关键约束(不能违反): // 输出校验 ww = (e) => e.split("\n").some(a => U$.includes(a.trim())) -// 其中 U$ = 模板按行过滤出的英文标题(## Objective / ## Requirements / ## Decisions / +// U$ = 模板按行过滤出的英文标题(## Objective / ## Requirements / ## Decisions / // ## Work State / ### Completed / ### Active / ### Blocked / ## Next Move / // ## Relevant Files / ## Important Context)。 -// ⇒ 摘要回复必须含【整行英文标题】,翻译标题 → 校验失败 → 压缩失败。 -// ⇒ 故只能要求「内容用中文、标题原样英文」,绝不能翻译标题。 +// ⇒ 摘要必须含【整行英文标题】,翻译标题 → 校验失败 → 压缩失败。 +// ⇒ 只能要求「内容中文、标题原样英文」,绝不能翻译标题。 +// compaction 钩子里若追加消息,content 必须是数组: +// prepare 链路里 T6 会在 Zt.make 之前对 content 调 .map(),给字符串会 TypeError。 // // 与 ai-mcp 同款的 V2 插件契约(勿破坏): // - named export 必须是逗号表达式安全形态(返回 hooks 对象会崩) // - default 导出 { id, setup },setup 必须返回 void、绝不抛 // -// 部署(三份同步 + node --check): +// 部署(三份同步 + node --experimental-strip-types 校验): // 源码真源 : D:\桌面\源码\oc_session\compaction-cn\compaction-cn.ts // 本机生效 : C:\Users\ypshidifu\.config\opencode\plugins\compaction-cn.ts // WSL 生效 : /root/.config/opencode/plugins/compaction-cn.ts @@ -43,10 +70,12 @@ const LOG_FILE = join(LOG_DIR, "compaction-cn.log") // 标记串:幂等判重用(多 location / 多次 reload 可能重复注册钩子) const MARK = "" +// system 段注入(对部分模型有效,对只认最后一条消息的模型无效,保留但不作为唯一手段) const CN_RULE = `${MARK} 以下为本次会话压缩(compaction)摘要的输出语言要求,优先级高于模板中的其他说明: 1. 除第 2 条外,摘要的全部内容必须使用简体中文撰写,包括各小节的条目正文、说明与结论。 + 即使被压缩的对话原文是英文,摘要正文也必须用简体中文。 2. 所有小节标题必须原样保留英文、不得翻译或改动大小写,包括: ## Objective / ## Requirements / ## Decisions / ## Work State / ### Completed / ### Active / ### Blocked / @@ -54,9 +83,39 @@ const CN_RULE = `${MARK} 这些标题行是压缩结果的格式校验依据,翻译或改写会导致本次压缩失败。 3. 文件路径、代码、命令、URL、报错原文、标识符以及用户原话引用保持原样,不要翻译。 4. 严格遵守压缩模板中的全部格式规则(单行条目、不超过 15 个相关文件、不要提及压缩过程等)。 -5. 若本次为更新模式(已有 checkpoint 要求重写),各节内容同样按上述语言要求用简体中文重写。 +5. 若本次为更新模式(已有 checkpoint 要求重写),各节内容同样按上述语言要求用简体中文重写, + 不要沿用旧 checkpoint 的英文表述。 +6. 禁止输出