Skip to content

Feature: Proactive Dynamic Output Truncation (tool-output-truncator) | Feature: 预防性动态输出截断 (tool-output-truncator) #101

Open

Description

@ranxianglei

需求

ACP 目前只有事后压缩能力(compress/decompress/prune),缺少预防性截断——在工具输出进入上下文之前就做动态截断。

背景

OMO (omo-stable) 有一个 tool-output-truncator hook,在 tool.execute.after 时对特定工具的输出做动态截断:

  • 查询当前 session 的最后一条 assistant 消息的 token 用量(input + cache.read)
  • 计算剩余上下文空间
  • 截断上限 = min(剩余空间 × 0.5, 50K tokens) — 上下文越满,截断越激进
  • 保留前 3 行 header + 尽量多的内容行
  • 对 grep/glob/lsp/ast_grep/interactive_bash/skill_mcp/webfetch 等工具生效

这是 ACP 缺失的能力。ACP 的 compress 是事后压缩(生成摘要),无法防止单次超大输出在进入上下文前就占满空间。

建议实现

将 OMO 的 dynamic-truncator 逻辑迁入 ACP,作为 tool.execute.after hook:

  1. 在工具输出进入上下文前,检查当前上下文使用率
  2. 动态计算截断上限(基于剩余空间)
  3. 截断保留 header + 尽量多的内容
  4. 追加截断标记

源码参考:omo-stable/src/shared/dynamic-truncator.ts(193 行)+ omo-stable/src/hooks/tool-output-truncator.ts(61 行)

收益

  • 填补 ACP 预防性截断的空白
  • OMO 可以完全移除上下文管理组件,只保留编排能力
  • 统一上下文管理到 ACP 一处

Activity

  1. ranxianglei commented on Sep 12, 2026

    @ranxianglei
    OwnerAuthor

    确认

    #101(预防性输出截断)— v1.17.0 的 truncate-tools + enforceContextBudget 已覆盖大半;剩余 tool.execute.after 钩子部分维持 open  
    

    是否属实

  2. ranxianglei commented on Sep 12, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    核实完毕(基于 origin/master,v1.17.0 = release commit df690fc):方向属实,但「已覆盖大半」言过其实。

    属实的部分:

    1. v1.17.0 确实带上了这两个机制:enforceContextBudget(新文件 lib/messages/enforce-budget.ts,PR fix: context budget guard + no-window warning (#347) #350/[Bug] Silent context-overflow death on resume when model reports limit.context=0 (custom providers): uncalibrated thresholds → 400 → empty exit-0 loop #347)+ truncateLargeToolOutputs(lib/messages/truncate-tools.ts,2026-07-28 bug-hunt 迭代创建,v1.17.0 的 fix: context-limit safety net for spawn+resume mode (#346) #349 更新了其阈值解析)。
    2. 全代码库确认没有注册任何 tool.execute.after 钩子(唯一的 execute 钩子是 command.execute.before),也没有任何相关 PR/issue 在推进本需求。Feature: Proactive Dynamic Output Truncation (tool-output-truncator) | Feature: 预防性动态输出截断 (tool-output-truncator) #101 维持 OPEN 是正确的。

    不准确的部分——「覆盖大半」高估了:

    这两个机制都是事后/反应式的,运行在 experimental.chat.messages.transform 管道里,作用对象是已经在上下文里的旧工具输出(截断或清空它们来腾空间);而 #101 的核心提案是预防式的——在 tool.execute.after 时把新输出在进入上下文之前就按剩余空间动态截断(cap = min(剩余×0.5, 50K)、保留 header 行、针对 grep/glob/lsp/bash/webfetch 等特定工具)。逐项对照:

    #101 要求 v1.17.0 现状
    工具输出进入上下文前截断 ❌ 均在 transform 阶段处理已入上下文的输出
    上限 = min(剩余空间×0.5, 50K),动态 ❌ 固定 reserve(completionReserveTokens=32768)/ GC 阈值×0.9
    保留前 3 行 header + 内容行 ❌ prefix+suffix 各 2000 字符(truncate-tools)/ truncate-then-clear(budget guard)
    针对特定工具列表生效 ❌ 对所有非受保护 completed 工具生效
    tool.execute.after 钩子 ❌ 未实现

    关键一点:单次超大输出在自己的那一轮仍会完整进入上下文——enforce-budget.ts 明确保护最近 3 条消息(PROTECT_RECENT_MESSAGES=3),它清的是旧输出而不是这条新的大输出。所以 issue 标题里的「预防性」这个核心缺口一个字节都没补上。

    结论:issue 应保持 open(这点说法正确),但措辞建议修正为——「v1.17.0 已提供反应式安全网层(truncate-tools + enforceContextBudget,防溢出/400 死循环);#101 提案的预防式机制(tool.execute.after 入上下文前动态截断)尚未开始实现,这才是 issue 的主体」。两者是互补关系,不是完成度关系。

  3. ranxianglei commented on Sep 14, 2026

    @ranxianglei
    OwnerAuthor

    现在是否可以做类似于omo的输出截断?超过一定大小截断即可。防止上下文膨胀

  4. ranxianglei commented on Sep 14, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    可以做,技术可行性已验证:@opencode-ai/plugin SDK 确实暴露了 "tool.execute.after" 钩子(签名 (input: {tool, sessionID, callID, args}, output: {title, output, metadata}) => Promise<void>),其中 output.output 是可变的字符串——正是 OMO 用的那个切入点,在输出持久化进会话前修改它即可。ACP 的 index.ts 已有统一的 guard() 钩子接线模式(#337 bili-proxy self-disable 也会自动覆盖新钩子),接入成本很低。

    按你说的「超过一定大小截断即可」,我的建议方案(简化静态版,不做 OMO 的动态 min(剩余×0.5, 50K)):

    设计

    • 新增纯函数模块(如 lib/messages/truncate-on-execute.ts)+ index.ts 注册一个 tool.execute.after 钩子。核心逻辑无副作用、可单测。
    • 阈值:单条工具输出 > maxTokens(默认 20000 tokens,约 80K 字符;正常有用的输出基本 <5K tokens,这个默认值只拦真正的巨型输出)→ 截断为前 2000 字符 + 后 2000 字符 + 截断标记,与现有 truncate-tools.ts 的 [truncated for context space...] 标记同族(头部保留 header/命令回显,尾部保留错误/结论)。
    • 范围:所有非受保护工具生效——遵守现有 commands.protectedTools + compress.protectedTools 语义,skill/compress/task/todowrite/write/edit 等绝不动(Bug 39 parity)。主会话和子代理会话都生效(这是保护机制,不是压缩管理)。
    • 配置(新增一个小区块,保持少旋钮):
    {
        "toolOutputTruncation": {
            "enabled": true,
            "maxTokens": 20000
        }
    }
    • 与现有机制的关系:互补不冲突。本钩子防「新巨型输出入上下文」(预防层);truncateLargeToolOutputs / enforceContextBudget 继续清「已积累的旧输出」(反应层)。标记幂等,反应层看到已截断内容会跳过。

    边界说明

    • 截断是不可逆的(不像 compress/decompress 可以恢复),所以默认阈值刻意宽松;误伤时可通过 enabled: false 或调大 maxTokens 关闭/放宽。
    • OMO 的动态上限版本(随剩余空间收紧)留作后续增强,本版不做。

    确认的话我就开工:分支 2026-09-14_tool-output-truncator + devlog(REQ/WORKLOG)+ 实现 + 测试(代码与测试各过双 agent review)+ PR 引用 #101。如果这个简化版符合你的预期,PR 合并后 #101 即可关闭;若你还想要动态版,issue 维持 open 另开后续需求。

  5. changed the title [-]Feature: 预防性动态输出截断 (tool-output-truncator)[/-] [+]Feature: Preventive Dynamic Output Truncation (tool-output-truncator) | Feature: 预防性动态输出截断 (tool-output-truncator)[/+] on Oct 16, 2026
  6. changed the title [-]Feature: Preventive Dynamic Output Truncation (tool-output-truncator) | Feature: 预防性动态输出截断 (tool-output-truncator)[/-] [+]Feature: Proactive Dynamic Output Truncation (tool-output-truncator) | Feature: 预防性动态输出截断 (tool-output-truncator)[/+] on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions