Skip to content

feat(tokenless): add native dsh plugin - #2581

Open
kongche-jbw wants to merge 1 commit into
alibaba:mainfrom
kongche-jbw:feat/tokenless/dsh-native-plugin
Open

feat(tokenless): add native dsh plugin#2581
kongche-jbw wants to merge 1 commit into
alibaba:mainfrom
kongche-jbw:feat/tokenless/dsh-native-plugin

Conversation

@kongche-jbw

@kongche-jbw kongche-jbw commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Why

Tokenless needs a DeepSeek Harness integration that participates in DSH's
native Cordis lifecycle instead of installing a bridge hook or editing profile
configuration directly. This bundle provides response compression and error
attribution through DSH's typed tools/post-execute seam while keeping the
Tokenless CLI as the compression engine.

What changed

  • Add the @anolisa/dsh-tokenless native bundle with nested
    dsh.bundle.patch metadata, a relocatable Cordis patch, and a dependency-free
    ESM plugin entry.
  • Add the DSH adapter to the Tokenless ANOLISA component contract and legacy
    adapter manifest.
  • Include and verify the bundle in regular installs, raw archives, RPMs, and
    npm prebuilt packages.
  • Add native adapter tests covering compression, mixed-content preservation,
    cancellation, fail-open behavior, and plugin-owned error attribution.

Current functionality:

  • Compress successful tool results through tokenless compress-response.
  • Replace content only when the result is one text block containing a JSON
    object or array and the compressed JSON is strictly shorter.
  • Use shell/API-specific truncation defaults, with plugin config overrides for
    thresholds, skipped tools, timeout, buffer size, binary path, agent id, and
    stash behavior.
  • Pass DSH cancellation through exec.signal and bound the child process with
    a timeout and output limit.
  • Detect common dependency, permission, path, network, and package failures
    from host errors and canonical shell-tool values, then append a valid
    plugin-owned user context that tells the agent not to retry blindly.
  • Forward session and tool-call identifiers to Tokenless when DSH exposes them,
    preserving Tokenless statistics attribution.
  • Keep file/content retrieval tools lossless by default.

Known limitations:

  • Command rewriting is not implemented. DSH's current pre-tool decision type
    deliberately excludes tool-argument replacement, so Tokenless cannot safely
    rewrite commands through a native public seam yet.
  • Plain text, multiple content blocks, images, errored tool content, and invalid
    JSON are not compressed. These paths continue unchanged.
  • The native v1 path runs compress-response only and does not add the hook
    adapter's TOON second stage.
  • There is no pre-spawn minimum-size gate; eligible small JSON may invoke the
    CLI, but content is replaced only when the result is strictly smaller.
  • A missing, failing, timed-out, or non-reducing Tokenless CLI is fail-open;
    DSH continues with the original result.
  • The bundle requires Node.js 22 and DSH/DSH tool packages in the declared
    >=0.1.0-rc.2 <0.2.0 compatibility range.
  • ANOLISA-managed profile activation depends on companion PR feat(anolisa): add native dsh adapter #2580. Until that
    driver is available, the native package can still be registered through
    DSH's own plugin CLI.

Related issue

no-issue: add the Tokenless implementation for the shared DSH adapter contract

Companion interface PR: #2580

User / Agent impact

After installing a build that includes #2580, users can run:

anolisa install tokenless
anolisa adapter enable tokenless dsh --profile <profile>

The plugin then compresses eligible DSH tool responses without changing tool
calls or profile files itself.

Risk and compatibility

  • Public CLI, API, configuration, or documented behavior changed
  • Cross-component contract changed
  • Migration or rollback guidance is needed

Existing Tokenless adapters and non-DSH installations are unchanged. The new
adapter is inactive until explicitly enabled. Before rolling back after an
enable, remove the Tokenless package from each affected DSH profile or use the
companion ANOLISA driver's disable command.

Validation

  • cargo fmt --all -- --check
  • make -C src/tokenless lint
  • make -C src/tokenless test-adapters
  • make -C src/tokenless build-dsh-plugin
  • bash src/tokenless/tests/test-dsh-adapter.sh
  • umask 022 && bash src/tokenless/tests/test-package-raw.sh
  • umask 022 && bash src/tokenless/tests/test-package-npm-prebuilt.sh
  • python3 src/tokenless/packaging/raw/verify-release.py
  • git diff --check

Documentation and rollback

The adapter manifests and package metadata describe the native contract; no
standalone guide is added in this change. Disable the adapter registrations and
revert this PR's feature commit to remove the bundle and packaging integration.

@github-actions github-actions Bot added the component:tokenless src/tokenless/ label Aug 15, 2026
@kongche-jbw
kongche-jbw marked this pull request as ready for review August 15, 2026 02:28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d3fb7b370c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tokenless/adapters/tokenless/dsh/dist/index.js Outdated

@qoderai qoderai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本次审查范围内未发现需要修改的问题。


🤖 Generated by QoderView workflow run

@kongche-jbw
kongche-jbw force-pushed the feat/tokenless/dsh-native-plugin branch from d3fb7b3 to e5d6a33 Compare August 15, 2026 02:45

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

DSH 原生插件整体设计扎实:fail-open 语义一致、子进程边界完整(timeout/maxBuffer/signal)、raw/RPM/npm 四条打包链路验证齐全,CLI 参数与 tokenless-cli 契约逐项吻合。主要问题在环境错误归因:会把成功结果误判为失败并向 agent 注入误导性 "Skip retry" 指令——Codex review 已指出 raw-text 路径(dist/index.js:303),本次审查补充其未覆盖的第二个调用点和一处失效的 exit-code 防护,建议修复后合并。

审查结论

request changes

详细意见

🔴 必须修改(阻塞合并)

  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:321](补充 Codex review)Codex P1 指向 303 行的 raw-text 归因,但压缩路径上还有第二个调用点:classifyEnvironmentError(parsed) 只在 isError === false 的成功结果上运行(312 行 singleTextContent 门控),扫描成功 JSON 的 stderr/error 字段。例如 exit 0 但 stderr 含 "… timed out …" 警告的构建命令、或把 error 字段当作普通数据的 API 响应,都会被注入 [tokenless:env] … failed … Skip retry。仅按 Codex 建议限制 raw-text 路径而不处理此调用点,误报仍然存在。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:156-161](补充 Codex review)if (exitCode === 0 && text.length === 0) return undefined 是失效防护:text 为空时 163 行 if (!text) return undefined 已返回,exitCode === 0 分支从不生效;exit_code === 0 且 stderr 非空的成功结果仍会被模式匹配(/timeout/i 连代码里的 setTimeout 都会命中)。防护的意图应是 "exit 0 不视为环境失败"。注意共享分类器 common/hooks/hook_utils.py:312(classify_env_error)有同样的问题,本插件是其忠实移植——建议两处同步修复保持各 adapter 行为一致,或至少在 dsh 插件内先行收敛。
    • 修复建议:raw-text 归因仅在 result.isError === true 时进行;结构化 JSON 仅在 exit_code/exitCode 非零或存在显式错误字段时归因;skip/enabled 门控移到归因计算之前。

🟡 建议修改(不阻塞但推荐)

  • [src/tokenless/tests/test-dsh-adapter.sh] 测试缺口:PR 描述称覆盖 "fail-open behavior",实际只测了 abort;缺少 binary 缺失/退出码非 0 的用例,也缺少 "无压缩收益"(CLI 输出 ≥ 输入)的用例。建议补两个 case 并断言 next() 被调用、原内容原样通过。另:fake binary 完全忽略 argv,CLI flag 正确性目前只靠人工核对。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:20-57] DEFAULT_SKIP_TOOLS / DEFAULT_SHELL_TOOLS / DEFAULT_THRESHOLDS 是 common/hooks/tool_categories.json 的硬编码副本(已核对当前逐项一致),双份事实来源有漂移风险。建议加一个 parity 断言测试,或基于 import.meta.url../common/hooks/tool_categories.json 读取(仍保持零运行时依赖)。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:301-308] responseCompressionEnabled: false 只关闭压缩;归因在 303-304 行先于门控计算,disabled 时仍会注入 additionalContexts,且没有独立开关。建议把归因一并纳入 enabled 门控,或提供独立配置项并在 manifest/文档中说明——在上述误报修复前,用户至少需要止血手段。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js compressionArgs] 压缩默认开启 stash(不传 --no-stash),被截断内容以 <<tokenless:HASH>> 标记呈现、需 tokenless retrieve 恢复;本 PR 未给 DSH 侧接入 retrieve 指引。若 DSH 会话中的 agent 不懂得恢复,截断内容对模型即事实丢失。建议明确 DSH agent 的恢复路径文档,或在指引就绪前考虑默认 noStash
  • [src/tokenless/Makefile:152-161] build-dsh-plugin 只检查文件存在;dist/index.js 是提交的源码而非构建产物,语法/导入错误要到 test-adapters 才暴露,而打包链路(package-raw/RPM/npm)理论上可单独运行。建议在验证接缝加一条低成本的 import 检查(测试脚本已有现成的 import 模式可复用)。

🟢 值得肯定

  • fail-open 一致且可验证:CLI 缺失/失败/超时/无收益均回退原内容并经 next() 放行;execFile + argv 数组无 shell 注入面;timeout、maxBuffer、AbortSignal 三重边界齐全。
  • 内容替换非常保守:单 text block + JSON 对象/数组 + 严格更短 + 输出重新 JSON.parse 校验,宿主原内容始终权威。
  • CLI 参数(--agent-id/--session-id/--tool-use-id/--truncate-*/--max-depth/--no-stash)与 tokenless-cli CompressResponse 定义及 stdin/stdout 契约逐项核对一致。
  • 打包集成完整:Makefile build/install/test、raw package.sh + verify-release.py、RPM spec %build/%install/%files、npm packaging 与 prebuilt 测试全部同步更新且带存在性断言。
  • 工具清单与阈值同 tool_categories.json 完全对齐;测试覆盖压缩收益短路 next()、混合内容保持、取消传播与归因消息结构。

@ikunkun-sys ikunkun-sys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

针对当前 head e5d6a338 请求修改。插件构建、定向 adapter 测试、Raw manifest 校验及远端 CI 均已通过,但现有测试没有覆盖以下 DSH 宿主契约:

  • 两个 P1:压缩命中会截断 post-execute waterfall;真实 DSH bash 非零退出无法触发环境错误归因。
  • 一个 P2:Code Mode 嵌套调用会产生没有减少模型上下文的压缩统计和 stash。

详细复现依据见 inline comments。

Comment thread src/tokenless/adapters/tokenless/dsh/dist/index.js Outdated
Comment thread src/tokenless/adapters/tokenless/dsh/dist/index.js Outdated
Comment thread src/tokenless/adapters/tokenless/dsh/dist/index.js Outdated

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

整体质量较高:压缩主流程的 fail-open 设计严谨,Codex 上一轮提出的 P1(错误归因未按 isError 门控)已在 e5d6a338 正确修复并有测试钉死,打包链路(Makefile/raw/RPM/npm)完整且互相校验。主要问题在于:dsh 插件自带了一套与共享 ENV_PATTERNS 分类法不一致的错误模式与类目名,会造成跨 adapter 的归因行为分叉,建议合并前对齐。

审查结论

request changes

详细意见

🔴 必须修改(阻塞合并)

  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:64-118] 环境错误分类法与共享 canonical 列表分叉。项目已有唯一权威来源 adapters/tokenless/common/hooks/hook_utils.pyENV_PATTERNS(注释明确说明它是 codex 与 hook adapter 共享的"superset",类目为 ENV_DEPENDENCY_MISSING / ENV_PERMISSION / ENV_FILE_MISSING / ENV_NETWORK / ENV_PACKAGE_MISSING),而本 PR 新写了一份 JS 副本,存在两类分叉:

    1. 类目名不一致ENV_PERMISSION_DENIED(:87)vs 共享 ENV_PERMISSIONENV_PATH_MISSING(:98)vs ENV_FILE_MISSINGENV_NETWORK_FAILURE(:110)vs ENV_NETWORK。这些类目串会进入 agent 可见消息(且 manifest 已声明 stats 能力),同一故障在不同 adapter 下产生不同标签,现在就分叉、以后改就是破坏兼容。
    2. 模式覆盖不一致(已实测验证):dash 风格的 /bin/sh: 1: jq: not found 共享 classify_env_error 能归因为 ENV_DEPENDENCY_MISSING,dsh 插件则漏检(缺 /bin/sh:.*: not found 模式)——这是 Debian/Ubuntu 容器里最常见的缺依赖报错形态。此外还缺 cannot open .* for writingcurl: \(6\)/curl: \(7\)ECONNREFUSED/ETIMEDOUTConnection timed outFailed to connectName or service not knowncannot finddoes not existcannot import name 等共享列表已有模式;同时新增了共享列表刻意未收录的宽泛 /timeout/i(:107),对已报错但只是文本含 "timeout" 的结果容易误归因为网络故障。

    修复建议:与 hook_utils.pyENV_PATTERNS 完全对齐(类目名、模式、hint 文案逐条镜像,并加注释指明 canonical 来源及"修改需双侧同步");更稳妥的做法是把模式表抽成共享 JSON(参照 tool_categories.json 的做法)供两侧读取。若确属有意分叉,请在代码与 PR 描述中说明理由。

🟡 建议修改(不阻塞但推荐)

  • [src/tokenless/tests/test-dsh-adapter.sh] PR 描述声称覆盖 fail-open,但测试套件没有对应用例。现有用例覆盖了压缩、混合内容、取消(aborted signal)、错误归因,但从未触发 execFile 失败路径。fail-open 是本插件最关键的安全属性,建议补充:tokenlessBin 指向不存在的二进制(我手工验证过此路径行为正确:next() 被调用、原结果保留)、CLI 非零退出、CLI 超时、CLI 输出不缩短(candidate 长度 >= 原文)四种情形。另外建议补 skipTools 命中但 isError === true 时仍附加归因、以及 responseCompressionEnabled: false 的用例。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:339-349] 压缩命中时直接返回 decision,不调用 next(),与其余路径(passWithAttribution 总是先 await next())不对称。若 DSH 的 tools/post-execute 链上还有其他插件(例如未来其他组件也挂 post-execute),它们将永远看不到该结果。请与 DSH/Cordis 的 seam 契约确认"替换内容是否允许短路":若允许,请在文件头注释中写明这是有意的替换语义;若链应组合执行,建议先 await next() 再覆盖返回 decision 的 content(与 passWithAttribution 的写法一致)。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:20-62] skip/shell 工具表与阈值是硬编码副本,与 tool_categories.json 存在漂移风险。当前值与 JSON 完全一致(已核对),但 JSON 的 _meta 自称是"all adapters 的分类与阈值唯一来源",且曾为修正分类漂移而改版;一旦 JSON 更新,dsh 插件会静默落后。安装后 dsh bundle 与 common/hooks/ 同处 {datadir}/adapters/tokenless/ 之下,插件可在 apply() 时尝试加载 ../common/hooks/tool_categories.json 并以现有硬编码值兜底;至少请加一个 parity 测试或注明 canonical 来源。另外 :42 注释"the same thresholds used by the shared hook adapter"目前只是巧合成立,建议一并修正表述。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:313,322] responseCompressionEnabled: false 只关闭压缩,不关闭错误归因注入(已实测确认)。从命名看用户可能期望该开关让插件完全静默。若属有意设计(开关只管压缩),请在配置说明中写明;否则建议拆独立开关(如 errorAttributionEnabled)。
  • [src/tokenless/CHANGELOG.md / CHANGELOG_zh.md] 缺少 changelog 条目。项目遵循 Keep a Changelog,[Unreleased] 目前为空,且此前的 adapter 变更(Qoder 原生化、Qwencode 等)均有中英文双条目。新增 native adapter 属于用户可见变更,请补记。

🟢 值得肯定

  • Codex P1 修复到位:原始文本分类现在严格以 result?.isError === true 为前提(:315),结构化结果仅在显式报告失败(非零 exit_code / success:false / ok:false)时才归因(:162-180);successfulMatchzeroExitnonzeroExit 三个测试把修复钉得很牢。
  • fail-open 设计扎实:二进制缺失、CLI 出错、超时、无缩短、信号取消全部回落到 next()(我手工验证了二进制缺失路径),compressText 要求 candidate 可被 JSON.parse 且严格更短才替换(:298-304),替换守卫(单一 text block、JSON object/array)保证混合内容/图片/错误内容零改动。
  • 子进程卫生execFile 无 shell(argv 注入面为零)、stdin 挂 error handler 防 EPIPE 冒泡、maxBuffer/timeout/AbortSignal 三重边界。
  • 与共享分类法的一致性DEFAULT_SKIP_TOOLSDEFAULT_SHELL_TOOLS、两套阈值与 hook_utils.py 的 fallback/tool_categories.json 逐项一致。
  • 打包完整且自校验:Makefile build-dsh-plugin 作为验证接缝、raw/RPM/npm 三处均校验产物存在,clean 正确区分"生成的 package.json"与"入库的 dist/index.js"(后者是手写 ESM 源码而非构建产物,入库合理,Makefile 注释也已说明)。
  • 本地 bash tests/test-dsh-adapter.sh 通过(Node v22),CI Test tokenless 在 head e5d6a338 上为 SUCCESS。

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head e5d6a338 独立审查并在本地(node v22.21.1)运行了新增测试与自写探针。结论:原 Codex bot P1(成功结果 raw-text 误归因)已修复并经实测验证;但插件与 DSH 宿主契约的对接仍有两处阻塞级问题(与 ikunkun-sys 的审查一致,我已对照 deepseek-harness 源码独立核实),另发现一处误归因边角与若干测试缺口。建议修复后合并。

已验证通过的部分

  • 原 P1 修复落实:raw-text 归因(dist/index.js:314-317)现在以 result?.isError === true 为门控;结构化路径改为 classifyStructuredEnvironmentError(:162),仅在显式失败标记(exit_code 非零 / isError / success:false / ok:false)时归因。本地运行 bash tests/test-dsh-adapter.sh 通过;另用探针确认:成功文本含 "permission denied"、{"exit_code":0,"stderr":"…permission denied…"} 均不再产生 attribution。
  • fail-open 语义正确:手工探针覆盖 CLI 缺失、无压缩收益(输出 ≥ 输入)、输出非法 JSON 三种场景,均经 next() 放行且保留原内容。
  • 子进程边界完整:timeout / maxBuffer / AbortSignal 三重约束,execFile + argv 数组无 shell 注入面,内容走 stdin。
  • 打包集成完整一致:Makefile build/install/test、raw package.sh + verify-release.py、RPM spec、npm packaging 四处链路同步更新且带存在性断言;test-dsh-adapter.sh 已接入 make test-adapters

必须修改(阻塞合并)

  1. [P1] 压缩命中短路 next(),截断 tools/post-execute waterfalldist/index.js:343-349,与 ikunkun-sys 第一项一致)。DSH 自带 spill policy 的写法是先 const decision = await next() 再对 accepted content 做变换(packages/spill/spill-policy/src/index.ts),证明该 seam 是 waterfall 组合。当前实现在压缩命中时直接返回 accept,且测试显式断言 "next() must not run"——任何注册在 Tokenless 之后的 listener(包括可能返回 block 的 policy)在该结果上都不会运行,其决定被整体丢弃。建议:先 await next(),仅当下游决定为 accept 且内容为单 text block 时替换为压缩结果,block/value 等决定透传,并同步修正测试预期。
  2. [P1] 结构化失败识别与 DSH 真实结果契约不符dist/index.js:335,核实 ikunkun-sys 第二项)。DSH tool-bash 的 canonical 结果在 result.value{ kind:'foreground', exitCode: integer|null, timedOut, stdout:{text,truncated,…}, stderr:{text,…} };非零退出以 isError:false + 展示文本中的 [exit code: N] 标记呈现(packages/shell/tool-bash/src/index.ts canonicalBashResult 与 output schema)。因此 (a) 真实 bash 失败的 content 不是 JSON,JSON.parse 失败后直接放行、无任何归因——即本 PR 主打的 shell 错误归因在真实宿主上不触发;(b) 即使 JSON 化,stdout/stderr 是对象而非字符串,:174-176 的 typeof part === 'string' 过滤会全部丢弃;(c) 新增测试的 {"exit_code":1,"stderr":"…"} fixture 是虚构契约。建议:从 result.value 读取 exitCode/timedOut(并兼容嵌套 stderr.text/stdout.text)做归因,content-text 解析仅作为其他工具的兜底;测试改用真实 value 形状。
  3. [P1] 非数字字符串 exit code 被当作失败标记dist/index.js:164-165,自写探针发现)。成功结果 {"exit_code":"N/A","stderr":"…permission denied…"} 会被判定失败并注入 ENV_PERMISSION_DENIED … Skip retry——成功数据中恰好带非数字 exit_code 字段 + 错误短语的普通内容会被误判。建议字符串分支仅在可解析为非零整数(如 /^-?\d+$/)时视为非零退出,非数字字符串按"无显式退出信号"处理。

建议修改(不阻塞但推荐)

  1. [P2] Code Mode 子调用应跳过压缩(核实 ikunkun-sys 第三项):exec.parent !== undefined 的子调用,其展示 content 不进入模型上下文(core/tools README "Only the program's outer logs and return value re-enter model context"),当前实现仍会启动 Tokenless CLI、写 stash 并记录 savings,产生无实际收益的统计与孤立 stash。DSH spill policy 正是因此跳过 parented executions。归因路径可保留,仅压缩路径排除。
  2. 测试缺口test-dsh-adapter.sh 目前未覆盖 (a) skipped-tool 路径(如 Grep 成功 → 透传且不压缩;这是原 P1 的事发场景),(b) CLI 缺失 / 无收益 / 非法输出的 fail-open(行为我已手工验证正确,建议固化),(c) DSH 真实 bash value 形状的归因用例。
  3. 小项:skip 列表内工具在 isError === true 时仍会注入 attribution(探针确认)——如果这是有意设计(真实失败仍提示勿盲目重试),建议补注释 + 测试固定;build-dsh-plugin 目前只做存在性检查,可加一条低成本 node import/语法检查,让打包接缝提前暴露语法错误(与 Forrest-ly 同条建议)。

备注

Forrest-ly review 中两处行号与 e5d6a33 的实际代码不符:其提到的 :321 classifyEnvironmentError(parsed) 成功路径在当前 head 已由带显式失败标记门控的 classifyStructuredEnvironmentError 取代,:156-161 的失效防护代码不存在。探针验证 exit_code:0 + 可疑 stderr 不再产生归因,这两点可能基于较早版本,供作者参考避免重复修改。

值得肯定

  • fail-open 一致性、子进程边界、无注入面做得扎实
  • 内容替换策略非常保守(单 text block + JSON 对象/数组 + 严格更短 + 重新 parse 校验),宿主原内容始终权威
  • 原 P1 修复附带了针对性回归测试(成功文本含错误短语 / 零退出结构化结果 / 非零退出结构化失败)

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head e5d6a338 独立审查并在本地(node v22.21.1)运行了新增测试与自写探针。结论:原 Codex bot P1(成功结果 raw-text 误归因)已修复并经实测验证;但插件与 DSH 宿主契约的对接仍有两处阻塞级问题(与 ikunkun-sys 的审查一致,我已对照 deepseek-harness 源码独立核实),另发现一处误归因边角与若干测试缺口。建议修复后合并。

已验证通过的部分

  • 原 P1 修复落实:raw-text 归因(dist/index.js:314-317)现在以 result?.isError === true 为门控;结构化路径改为 classifyStructuredEnvironmentError(:162),仅在显式失败标记(exit_code 非零 / isError / success:false / ok:false)时归因。本地运行 bash tests/test-dsh-adapter.sh 通过;另用探针确认:成功文本含 "permission denied"、{"exit_code":0,"stderr":"…permission denied…"} 均不再产生 attribution。
  • fail-open 语义正确:手工探针覆盖 CLI 缺失、无压缩收益(输出 ≥ 输入)、输出非法 JSON 三种场景,均经 next() 放行且保留原内容。
  • 子进程边界完整:timeout / maxBuffer / AbortSignal 三重约束,execFile + argv 数组无 shell 注入面,内容走 stdin。
  • 打包集成完整一致:Makefile build/install/test、raw package.sh + verify-release.py、RPM spec、npm packaging 四处链路同步更新且带存在性断言;test-dsh-adapter.sh 已接入 make test-adapters

必须修改(阻塞合并)

  1. [P1] 压缩命中短路 next(),截断 tools/post-execute waterfalldist/index.js:343-349,与 ikunkun-sys 第一项一致)。DSH 自带 spill policy 的写法是先 const decision = await next() 再对 accepted content 做变换(packages/spill/spill-policy/src/index.ts),证明该 seam 是 waterfall 组合。当前实现在压缩命中时直接返回 accept,且测试显式断言 "next() must not run"——任何注册在 Tokenless 之后的 listener(包括可能返回 block 的 policy)在该结果上都不会运行,其决定被整体丢弃。建议:先 await next(),仅当下游决定为 accept 且内容为单 text block 时替换为压缩结果,block/value 等决定透传,并同步修正测试预期。
  2. [P1] 结构化失败识别与 DSH 真实结果契约不符dist/index.js:335,核实 ikunkun-sys 第二项)。DSH tool-bash 的 canonical 结果在 result.value{ kind:'foreground', exitCode: integer|null, timedOut, stdout:{text,truncated,…}, stderr:{text,…} };非零退出以 isError:false + 展示文本中的 [exit code: N] 标记呈现(packages/shell/tool-bash/src/index.ts canonicalBashResult 与 output schema)。因此 (a) 真实 bash 失败的 content 不是 JSON,JSON.parse 失败后直接放行、无任何归因——即本 PR 主打的 shell 错误归因在真实宿主上不触发;(b) 即使 JSON 化,stdout/stderr 是对象而非字符串,:174-176 的 typeof part === 'string' 过滤会全部丢弃;(c) 新增测试的 {"exit_code":1,"stderr":"…"} fixture 是虚构契约。建议:从 result.value 读取 exitCode/timedOut(并兼容嵌套 stderr.text/stdout.text)做归因,content-text 解析仅作为其他工具的兜底;测试改用真实 value 形状。
  3. [P1] 非数字字符串 exit code 被当作失败标记dist/index.js:164-165,自写探针发现)。成功结果 {"exit_code":"N/A","stderr":"…permission denied…"} 会被判定失败并注入 ENV_PERMISSION_DENIED … Skip retry——成功数据中恰好带非数字 exit_code 字段 + 错误短语的普通内容会被误判。建议字符串分支仅在可解析为非零整数(如 /^-?\d+$/)时视为非零退出,非数字字符串按"无显式退出信号"处理。

建议修改(不阻塞但推荐)

  1. [P2] Code Mode 子调用应跳过压缩(核实 ikunkun-sys 第三项):exec.parent !== undefined 的子调用,其展示 content 不进入模型上下文(core/tools README "Only the program's outer logs and return value re-enter model context"),当前实现仍会启动 Tokenless CLI、写 stash 并记录 savings,产生无实际收益的统计与孤立 stash。DSH spill policy 正是因此跳过 parented executions。归因路径可保留,仅压缩路径排除。
  2. 测试缺口test-dsh-adapter.sh 目前未覆盖 (a) skipped-tool 路径(如 Grep 成功 → 透传且不压缩;这是原 P1 的事发场景),(b) CLI 缺失 / 无收益 / 非法输出的 fail-open(行为我已手工验证正确,建议固化),(c) DSH 真实 bash value 形状的归因用例。
  3. 小项:skip 列表内工具在 isError === true 时仍会注入 attribution(探针确认)——如果这是有意设计(真实失败仍提示勿盲目重试),建议补注释 + 测试固定;build-dsh-plugin 目前只做存在性检查,可加一条低成本 node import/语法检查,让打包接缝提前暴露语法错误(与 Forrest-ly 同条建议)。

备注

Forrest-ly review 中两处行号与 e5d6a33 的实际代码不符:其提到的 :321 classifyEnvironmentError(parsed) 成功路径在当前 head 已由带显式失败标记门控的 classifyStructuredEnvironmentError 取代,:156-161 的失效防护代码不存在。探针验证 exit_code:0 + 可疑 stderr 不再产生归因,这两点可能基于较早版本,供作者参考避免重复修改。

值得肯定

  • fail-open 一致性、子进程边界、无注入面做得扎实
  • 内容替换策略非常保守(单 text block + JSON 对象/数组 + 严格更短 + 重新 parse 校验),宿主原内容始终权威
  • 原 P1 修复附带了针对性回归测试(成功文本含错误短语 / 零退出结构化结果 / 非零退出结构化失败)

@Forrest-ly
Forrest-ly dismissed their stale review August 15, 2026 03:24

重复提交(与 03:23:08Z 的 review 完全相同,操作失误重复投递),以该条为准。

@kongche-jbw
kongche-jbw force-pushed the feat/tokenless/dsh-native-plugin branch from e5d6a33 to 4fc92c1 Compare August 15, 2026 03:40
@kongche-jbw

Copy link
Copy Markdown
Collaborator Author

Addressed the latest Forrest and ikun reviews in 4fc92c1.

  • The post-execute handler now composes the full waterfall, preserving block/value/content/additionalContexts decisions before transforming accepted display content.
  • DSH bash attribution now uses canonical result.value (exitCode, timedOut, and nested stream text), skips nonnumeric string exit codes, and parented Code Mode calls bypass compression while retaining attribution.
  • DSH ENV patterns, category names, and hints now mirror the shared hook_utils.py canonical table, including the common /bin/sh: ... not found form and without the broad timeout matcher.
  • Added binary-missing, nonzero-exit, timeout, invalid/no-benefit fail-open coverage, exact argv and tool-taxonomy parity checks, disabled-compression attribution coverage, and a build-time ESM import check.
  • The bundle patch now resolves @anolisa/dsh-tokenless by installed package name, matching the upstream DSH bundle contract.

responseCompressionEnabled intentionally controls compression only; failure attribution remains active as a safety signal, now documented in code and pinned by a test. I did not add CHANGELOG entries because specs/documentation-standard.md §5 requires daily feature/fix PRs to leave changelog aggregation to release version-bump PRs.

Validation passed: make lint; all Rust, hook, Python, and adapter suites with the current 0.7.6 build; raw and npm package tests under the standard 0022 packaging umask.

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head 4fc92c1 的 re-review,结论:APPROVE。前两轮的两个 🔴 阻塞项均已确认修复,作者声称的 3 个修复点在代码与测试中均得到验证;本地运行 bash src/tokenless/tests/test-dsh-adapter.sh(Node v22.21.1)与 make build-dsh-plugin 全部通过,修复未引入新的阻塞问题。

🔴 阻塞项复核(均已修复)

  1. 第 1 轮:环境错误归因误报 — raw-text 归因现在仅在 result.isError === true 时执行(adapters/tokenless/dsh/dist/index.js:343-345),压缩路径不再有扫描成功结果的第二个调用点。结构化归因仅在 isError === false 时进行,且只认 canonical result.value 中的显式失败标记:数字/整数字符串 exitCode !== 0timedOutisErrorsuccess === falseok === falseclassifyStructuredEnvironmentError)。exitCode === 0 且 stdout 含敏感词不再注入归因(测试 zeroExit);非数字退出码('N/A')被拒绝(测试 nonnumericExit)。
  2. 第 2 轮:ENV_PATTERNS 与 hook_utils.py 分叉 — 类目名已与 canonical 列表一致(ENV_DEPENDENCY_MISSING / ENV_PERMISSION / ENV_FILE_MISSING / ENV_NETWORK / ENV_PACKAGE_MISSING);5 组模式逐条与 common/hooks/hook_utils.py 核对一致,包括 dash 风格 /bin/sh:.*: not found、curl (6)/(7)、ECONNREFUSED/ETIMEDOUT 等;过宽的 /timeout/i 已移除;镜像契约在注释中声明(dist/index.js:64-66)。

作者 3 个修复点验证

  1. Waterfall 顺序dist/index.js:356-380):先 await next();block 决策与 canonical-value 替换决策原样透传(测试用 strictEqual 断言引用透传);仅在 kind === 'accept'、决策无自有 value、单 text block 时替换展示内容;下游 additionalContexts 得到保留。此前“压缩命中短路返回、不调用 next()”的不对称已消除。
  2. 归因改读 canonical result.value:覆盖嵌套 stdout.text / stderr.text;测试改用真实 DSH bash 结果形状。
  3. Code Mode 子调用exec.parent !== undefined):保留环境归因,跳过压缩/stash/savings(测试 parentFailure 断言 tokenless 二进制未被调用)。

第 2 轮 🟡 建议复核

  • fail-open 测试:已补齐(binary 缺失 / 非零退出 / 超时 / 无压缩收益 / 无效 JSON 输出),全部通过。
  • tool_categories.json parityDEFAULT_SKIP_TOOLS / DEFAULT_SHELL_TOOLS / 阈值与 tool_categories.json 完全一致;新增 parity 测试直接读取该 JSON 并断言插件行为,未来漂移会导致测试失败。
  • responseCompressionEnabled 不门控归因:作者明确为有意设计——归因独立于压缩,配有代码注释与测试(disabledFailure),可接受。
  • changelog:仍缺。src/tokenless/CHANGELOG.mdCHANGELOG_zh.md 均有空的 [Unreleased] 段,本 PR 未新增条目,建议合并前补上(非阻塞)。

小观察(非阻塞,可选)

  1. classifyStructuredEnvironmentErrorstdout.text 一并拼入扫描文本,而 Python canonical classify_env_error 只扫 stderr+error。因仅在显式失败标记后触发,误报风险很低,但毕竟与 canonical 有语义差异,建议加注释说明或保持对齐。
  2. JS /which:\s+no/i 对应 Python 的 "which: no"(单空格字面子串),JS 版匹配略宽(\s+),影响可忽略。

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对新 head 4fc92c1(force-push 压缩后的单一 commit)独立审查,并在本地(Node v22.21.1)运行了适配测试与自写探针。上一轮三个 P1/P2(waterfall 截断、DSH canonical result.value 契约、非数字 exit_code、Code Mode 子调用压缩)均已正确修复并用回归测试钉死;ENV 分类表已与 hook_utils.py canonical 来源逐项对齐;raw/RPM/npm 打包链路完整。未发现阻塞问题,仅在新引入的 result.value 归因路径上有三个增量精度/覆盖建议。

审查结论

approve

详细意见

🔴 必须修改(阻塞合并)

无。

🟡 建议修改(不阻塞但推荐)

  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:205-208] timedOut 兜底归因为 ENV_NETWORK 会误导诊断。当 value.timedOut === true 且 stderr/stdout 未命中任何模式时,兜底注入 ENV_NETWORK (Network connectivity issue. Check DNS, proxy, and firewall settings.)。探针验证:exitCode: 124, timedOut: true, stderr: "" 的纯超时(如长构建、sleep 超限)也会被标记为网络故障——"Skip retry" 建议本身正确,但类目/hint 会把 agent 引向 DNS/代理/防火墙排查。现有测试(timedOut 用例)把该行为钉成了 ENV_NETWORK。建议:无模式命中时不做归因;或确需覆盖超时时,先在 hook_utils.py canonical 表引入中性超时类目再双侧同步镜像,并同步调整测试。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:159-167] result.error 为纯字符串时被 errorText 丢弃。当前只处理 error.message 为字符串的对象形态;若宿主以 result.error = "permission denied ..." 的字符串形态报错(工具协议中常见),该消息完全不参与分类,只扫描 content。探针验证:isError: true + 字符串 error + 无关 content → 无归因。方向安全(漏报而非误报),但错误归因这一主打能力会静默失效。建议:在 errorText 开头补 if (typeof error === 'string') return error
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:182-188] exitCode: null 未视为失败标记。DSH 契约中 exitCode 为 integer|null(null 一般对应进程被信号杀死);探针验证 exitCode: null + stderr Connection refused → 无归因。若 DSH 保证 null 仅出现在 signal-kill 场景,建议将其纳入 failed 判定(或至少允许对可匹配 stderr 归因);若属有意保守,请补注释说明意图并加一个 killed-process 探针用例钉住行为。

🟢 值得肯定

  • 上一轮全部 P1/P2 经探针独立验证已修复:handler 先 await next() 再仅替换 accepted display content(block / canonical value 决定原样透传、downstream additionalContexts 完整保留);归因改读 result.value 的 exitCode/timedOut 及嵌套 stdout.text/stderr.text;非数字字符串 exit_code 被 /^-?\d+$/ 正确拒绝;parented Code Mode 子调用保留归因但跳过压缩/stash/savings。每条修复都有对应回归测试。
  • ENV_PATTERNShook_utils.py canonical 表逐项一致(类目名、顺序、hint、模式集合,含 dash 的 /bin/sh:.*: not found 形态),注释明确了 canonical 来源与双侧同步要求;此前分叉的 ENV_PERMISSION_DENIED/ENV_PATH_MISSING/ENV_NETWORK_FAILURE 类目名与宽泛 /timeout/i 模式已消除。
  • 内容替换策略保持保守(单 text block + JSON 对象/数组 + 严格更短 + 重新 parse 校验 + 宿主的 downstream content 优先),fail-open 四路径(binary 缺失 / 非零退出 / 超时 / 无收益与非法输出)与 skip-tool、disabled 配置均有测试钉死;工具分类表与阈值通过运行时读取 tool_categories.json 的 parity 测试防漂移。
  • build-dsh-plugin 增加了 node import/契约校验接缝;Makefile、raw package.sh、verify-release.py、RPM spec、npm packaging 与两个打包测试全部同步更新且带存在性断言。本地 bash tests/test-dsh-adapter.sh 全量通过。

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head 4fc92c1 做了独立复审(含本地运行验证),确认既有两轮 APPROVE 的结论成立:此前所有 🔴/P1/P2 阻塞项均已修复并有测试钉死,未发现新的阻塞问题。本次增量发现仅一条用户文档缺口(🟡,既有 review 均未提及)。

审查结论

approve

独立验证记录

  • 本地(Node v22.21.1)运行 bash src/tokenless/tests/test-dsh-adapter.shmake build-dsh-pluginbash src/tokenless/tests/test-package-raw.sh 全部通过。
  • ENV_PATTERNS 与 canonical 来源 common/hooks/hook_utils.py 逐条核对一致:5 个类目名、hint 文案、模式集合与顺序(含 dash 风格 /bin/sh:.*: not found、curl (6)/(7)、ECONNREFUSED/ETIMEDOUT 等);差异仅为大小写不敏感(方向安全)与既有 review 已记录的 /which:\s+no/ 微宽。
  • DEFAULT_SKIP_TOOLS(19 项)/ DEFAULT_SHELL_TOOLS(11 项)/ shell 阈值 65536/128/8 / api 阈值 1048576/65536/32 与 common/hooks/tool_categories.json 逐项一致,parity 测试运行时读取该 JSON 防漂移。
  • component.toml.in 模板替换后可被 TOML 解析,8 个 adapter 块(含新增 dsh 的 bundle entry 与 >=0.1.0-rc.2 <0.2.0 compat)均正确;manifest.json.in 与 dsh package.json.in 为合法 JSON。
  • 打包四链路(Makefile install / raw package.sh + verify-release.py / RPM spec / npm package-npm.js)对 dsh 三件套(package.json、cordis.patch.yml、dist/index.js)的包含与存在性断言完整一致。
  • 核心插件复核:归因严格门控(raw-text 仅 isError === true,结构化仅认 canonical result.value 显式失败标记,非数字 exitCode 被拒);waterfall 先 await next() 再仅替换 accepted 单 text block 展示内容,block/value 决定原样透传;execFile argv 数组无注入面,timeout/maxBuffer/AbortSignal 边界完整,fail-open 四路径有测试。

详细意见

🔴 必须修改(阻塞合并)

无。

🟡 建议修改(不阻塞但推荐)

  • [docs/user-guide/en/token-saving/tokenless/framework-integration.md 及 zh 对应文档] 新增 native adapter 是用户可见变更(RPM spec 描述已指引 anolisa adapter enable tokenless dsh --profile <profile>),但用户指南未同步:framework-integration.md 的 "Agent adapter support matrix" 表(en 约 :11-21 / zh 约 :11 起)与 "Enable one adapter" 的 enable 命令列表(en :73-84 / zh :72-84)均未包含 dsh;QUICKSTART.md 的 Agent/Setup 表(en 约 :117 / zh 约 :114)同样缺 dsh 行。建议按既有条目格式补充:dsh 行(plugin 生命周期接入、Cordis patch、不改 profile)、anolisa adapter enable tokenless dsh 示例、兼容版本范围 >=0.1.0-rc.2 <0.2.0。与既有 review 已指出的 changelog 缺口同属发布完整性事项,可一并补齐。

🟢 值得肯定

  • 阻塞项修复质量高且每条都有回归测试钉死(waterfall 组合、canonical value 归因、非数字 exitCode 拒绝、Code Mode 子调用跳过压缩、fail-open 四路径)。
  • 与共享 canonical 来源(hook_utils.py ENV_PATTERNS、tool_categories.json)的镜像关系在注释中声明,并分别以注释契约与 parity 测试防漂移。
  • 打包验证接缝 build-dsh-plugin 含 node import/契约校验,raw/RPM/npm 四条链路同步更新且带存在性断言。

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head 4fc92c1 做了独立复审:既有 review 的所有阻塞项(归因门控、waterfall 组合、canonical value 归因、Code Mode 子调用、分类法 parity、fail-open 测试)均已确认修复并有测试钉死。本次另将插件行为与 DSH 官方 packages/core/tools README 的 PostToolDecision 契约逐项核对,未发现契约违背;增量发现一条:结构化归因路径在分类器行为层与 canonical classify_env_error 存在两处既有 review 未讨论的分叉。

审查结论

approve

详细意见

🔴 必须修改(阻塞合并)

无。

🟡 建议修改(不阻塞但推荐)

  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:202-208] classifyStructuredEnvironmentError 与 canonical common/hooks/hook_utils.py:312classify_env_error)存在两处分类器行为分叉,既有 review(含 ENV_PATTERNS parity 核查)均未覆盖:
    1. timedOut → ENV_NETWORK 兜底(:205-208):命令超时(timedOut: true)且文本未命中任何模式时,注入 ENV_NETWORK 与 "Check DNS, proxy, and firewall settings" 提示。DSH 的 timedOut 是工具执行超时(README 中 execute wrapper 的 timeout 语义),并非网络概念——死循环构建、CPU-bound 命令、被 DSH 超时杀掉的 sleep 都会被标成网络故障,把 agent 引向 DNS/代理/防火墙排查。而同一失败在 hook adapter 下 canonical 分类器返回空——这正是上一轮 🔴(ENV_PATTERNS 分叉)要求消除的"同一故障、不同 adapter 不同标签"形态。测试(timedOut case:exitCode 124 + stderr "command stopped after timeout" 断言 ENV_NETWORK)已把该行为钉死。
    2. stdout 纳入扫描文本(:202):canonical 只扫 stderr + error,此处为 stderr + error + stdout。失败命令的 stdout 若恰好含命中模式的数据文本(如输出中带 "permission denied" 字样),会误标类目,且与 hook adapter 行为分叉。
    • 修复建议(二选一并保持双侧同步):(a) 向 canonical 收敛——扫描文本去掉 stdout、移除 timedOut 兜底,timedOut 视为"失败但类目未知"(可保留归因消息但不套用网络类目与 hint);(b) 若确要保留超时归因,先在 canonical hook_utils.py 增设中性类目(如 ENV_TIMEOUT + 中性 hint)再双侧镜像,维持分类法单一权威来源原则。无论哪种,:64 注释目前只声明模式表 parity,建议把 parity 契约扩展到分类器行为层并注明。

🟢 值得肯定

  • 与 DSH 官方 README 契约核对无违背:waterfall 先 await next(),block / canonical value 决定原样透传,仅替换 accepted 单 text block 展示内容;归因附加到 block 决定时属于 README 明确允许的 "contexts explicitly supplied by the blocking decision"。
  • 归因门控严格(raw-text 仅 isError === true、结构化仅认 canonical result.value 显式失败标记、非数字 exitCode 拒绝),fail-open 五路径(binary 缺失 / 非零退出 / 超时 / 无收益 / 非法输出)齐全,且 fake binary 通过 TOKENLESS_TEST_ARGS 落盘 argv 做 CLI flag 断言,补上了既有 🟡 提出的缺口。
  • tool_categories.json parity 测试运行时读取 JSON 防漂移;component.toml.in / manifest.json.in 的 plugin_id / pluginId 双轨命名与 openclaw、claude-code 既有约定一致;raw/RPM/npm 四条打包链路对 dsh 三件套的包含与存在性断言完整。

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head 4fc92c1 独立审查:本地(Node v22.21.1)复跑 test-dsh-adapter.sh 全部通过,并自写探针验证。与 DSH 官方契约(deepseek-ai/deepseek-harness 的 packages/core/tools README 与 docs/subsystems/tools.md 生成区)逐项核对:listener 签名 (exec, result, next)PostToolDecision 三形态(accept-content / accept-value / block)、"content 替换保留 canonical value"、"block 仅暴露 blocking decision 显式提供的 contexts" 均吻合;ENV_PATTERNS 镜像表与 tool_categories.json 分类/阈值也逐项对照 canonical 来源核实一致。既有 review 的全部阻塞项确认已修复且有测试钉死。本次增量发现三条既有 review 均未涉及的非阻塞问题(配置覆盖语义、压缩管线缺 TOON 二段、无最小尺寸门控),均已用探针实证。

审查结论

approve

详细意见

🔴 必须修改(阻塞合并)

无。

🟡 建议修改(不阻塞但推荐)

  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:340, :254] skipTools/shellTools 配置覆盖是整体替换语义,与 :17-19 注释宣称的 "callers may extend or replace" 不符,且会静默剥夺 lossless 安全默认。探针实证:设置 skipTools: ['my_db_query'](用户意图:追加一个 lossless 工具)后,Read/Grep 等全部默认内容获取工具立即失去 skip 保护、进入有损压缩;shellTools 覆盖同理会把 Bash 从 shell 阈值降到 API 阈值。现有测试未覆盖任何配置覆盖路径。修复建议(选一):(a) 改为合并语义,或新增 extraSkipTools/extraShellTools 用于扩展、skipTools 保留整体替换语义;(b) 若确认只做替换,修正注释(删 "extend"),在 manifest/文档中写明"覆盖 skipTools 将同时失去默认 lossless 清单",并补配置覆盖测试钉死所选语义。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:260] 压缩管线缺少 canonical 管线具备的 compress-toon 二段。共享 hook adapter(common/hooks/compress_response_hook.py step 15/16)为 compress-response → compress-toon 两段取更短者;本插件仅执行 compress-response(全文件 0 处 compress-toon,探针确认 argv 只有单一子命令)。同一负载下 dsh 用户获得的 token 节省将系统性低于 hook adapter 用户,tokenless stats 的跨 adapter 节省对比也会因管线差异失真。若属 v1 原生插件的有意裁剪,建议在 PR 描述 Known limitations 中补记并在文档注明;否则按 hook adapter 模式补二段(同样 fail-open、同样"严格更短才替换"守卫)。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:309-330] 无最小尺寸门控:任意小的单 text block JSON 都会 spawn CLI 进程。探针实证:7 字符 {"a":1} 与 199 字符 JSON 均触发 tokenless 子进程;而 canonical hook 对 _MIN_RESPONSE_CHARS = 200 以下直接跳过(compress_response_hook.py:82, :345)。小负载不可能压缩得更短,fail-open 后行为正确,但在工具调用密集的 dsh 会话中每次调用都多一次进程 spawn(CLI 启动 + stats 记录开销)。建议在 compressText 入口镜像 canonical 的 200 字符门控(或提供 minResponseChars 配置)提前返回;归因路径独立于压缩,不受此门控影响。

🟢 值得肯定

  • 与 DSH 官方契约核对无违背且映射选择有据:exec.parentToolExecutionToken(symbol),=== undefined 判断正确识别 Code Mode 子调用;DSH agent registry 的 enter() 强制 agent.id === agent.session.id,故 exec.agent?.id 转发为 --session-id 语义正确,与 hook adapter 从宿主 payload 取 session_id 的统计分组语义一致。
  • 本地独立复跑 test-dsh-adapter.sh(22 组断言,Node v22.21.1)全部通过;fake binary 经 TOKENLESS_TEST_ARGS 落盘 argv,CLI flag 与 canonical 阈值 parity 断言可信。
  • ENV_PATTERNShook_utils.py canonical 表、DEFAULT_SKIP_TOOLS/DEFAULT_SHELL_TOOLS/双阈值与 tool_categories.json 均经本次独立逐项核对一致;compress-response argv 与 tokenless-cliCompressResponse 参数定义逐项吻合。

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head 4fc92c1 独立审查:本地克隆 PR 分支(Node v22.21.1)复跑 bash tests/test-dsh-adapter.sh 全部通过,并自写探针对结构化归因路径做了行为验证。与 canonical 来源逐项核对:ENV_PATTERNS 镜像表与 common/hooks/hook_utils.py 一致(类目名/hint/模式集合,含 dash 风格 /bin/sh:.*: not found、curl (6)/(7)、ECONNREFUSED/ETIMEDOUT);DEFAULT_SKIP_TOOLS(19 项)/DEFAULT_SHELL_TOOLS(11 项)/shell 65536/128/8、api 1048576/65536/32 与 common/hooks/tool_categories.json 逐项一致;canonical hook 的 compress-response → compress-toon 二段与 _MIN_RESPONSE_CHARS = 200 门控均已确认(对应既有 review 的两条管线建议)。既有 review 的全部阻塞项(归因门控、waterfall 截断、canonical value 归因、Code Mode 子调用、分类法分叉、fail-open 测试缺口)确认已修复且有测试钉死。本次增量发现两条既有 review(含 5 轮 APPROVE 与全部 inline comments)均未涉及的非阻塞问题:parity 测试对 API 层阈值失去保护、结构化归因丢弃无 message 的 error 对象。

审查结论

approve

详细意见

🔴 必须修改(阻塞合并)

无。

🟡 建议修改(不阻塞但推荐)

  • [src/tokenless/tests/test-dsh-adapter.sh:325-331] parity 测试对 API 层阈值硬编码,layer_3_api 漂移不会触发测试失败。该 parity 块的目的是运行时读取共享来源防漂移:layer_1_skip 工具表(:304)与 layer_2_shell 阈值(:312-314)都从 tool_categories.json 读取,但 API 默认工具的 argv 断言硬编码 '1048576' / '65536' / '32'(:328-330)——而同一 JSON 中存在数值相同的 layer_3_api.thresholds。若未来 canonical JSON 更新 layer_3_api 阈值,dsh 插件的 DEFAULT_THRESHOLDS.api(dist/index.js:59-62)会静默落后且本测试仍通过——正是该 parity 块本应防止的漂移失效模式。建议仿照 shell 层改为 String(categories.layer_3_api.thresholds.truncate_strings_at) 等读取式断言。
  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:193-203] 无字符串 messagevalue.error 对象被排除出扫描文本,与 canonical 分类器行为分叉classifyStructuredEnvironmentError 中,当 value.error 是无字符串 message 的对象(如 { code: 'EACCES', errno: 13 })时,errorValue 保持对象形态,被 :202-204 的 .filter((part) => typeof part === 'string') 丢弃。探针实证(Node v22.21.1):exitCode: 1 + error: {code:'EACCES',errno:13} + 空 stderr → 无归因;而 canonical classify_env_error(hook_utils.py)以 str(stderr) + str(error) 构造扫描文本,同一负载在 hook adapter 侧会命中 "EACCES" 归因为 ENV_PERMISSION(已独立验证 str({'code': 'EACCES', ...}) 含该模式)。这是继既有 review 指出的 timedOut 兜底、stdout 入扫描之后第三处分类器行为层分叉,方向安全(漏报而非误报),但同一故障跨 adapter 归因不一致。建议(二选一):镜像 canonical 将 error 对象的字符串投影纳入扫描文本;或在 :64-66 镜像契约注释中明确"无 message 的 error 对象不参与扫描"属有意收窄,并补探针用例钉死所选行为。

🟢 值得肯定

  • 既有全部阻塞项修复经本地独立复跑确认:head 4fc92c1bash tests/test-dsh-adapter.sh 全量通过;waterfall 先 await next()、block/canonical-value 决定原样透传、仅替换 accepted 单 text block 展示内容;归因严格门控(raw-text 仅 isError === true、结构化仅认 canonical result.value 显式失败标记、非数字 exitCode 拒绝);fail-open 五路径(binary 缺失/非零退出/超时/无收益/非法输出)齐全且经测试钉死。
  • ENV_PATTERNStool_categories.json 的镜像关系经本次独立逐项核对一致,镜像契约在注释中声明(dist/index.js:64-66),parity 测试运行时读取 JSON 防漂移——上述两条增量建议仅是补全该机制的保护范围,不影响现有正确性。
  • 打包四链路(Makefile install / raw package.sh + verify-release.py / RPM spec / npm package-npm.js)对 dsh 三件套(package.json、cordis.patch.yml、dist/index.js)的包含与存在性断言完整;build-dsh-plugin 验证接缝含 node import + apply/name 契约检查,raw/RPM/npm 复用同一接缝。

@ikunkun-sys ikunkun-sys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

针对当前 head 4fc92c1c 请求修改。上一轮 waterfall 组合、canonical bash value 和 Code Mode 子调用问题均已确认修复,Node 22 adapter 测试与远端 CI 也通过;但当前结构化错误归因仍会把任意成功工具的业务数据误判为宿主失败。具体复现和影响见 inline comment。

Comment thread src/tokenless/adapters/tokenless/dsh/dist/index.js

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head 4fc92c1 复核 ikunkun-sys 新提出的 P1(结构化归因把任意成功工具的 value 当作通用失败协议)。本地 Node v22.21.1 直接 import 插件独立复现成立:除 reviewer 给出的 audit-record 场景外,另复现两种误报形态,且确认 responseCompressionEnabled: falseskipTools 均无法阻断注入。核对 DSH pinned commit 的契约(packages/core/tools/README.mdToolExecutionResult):成功结果为 { isError:false, value:JsonValue, ... },失败为 { isError:true, error, ... }没有 value——value 不承载宿主级失败语义,exitCode/success/ok/timedOut 只是 shell/process 类工具自身 output schema 的字段。上一轮 APPROVE 认为"仅认显式失败标记"的门控已充分,本轮验证证明该前提对非 shell 工具不成立,故结论改为 request changes。

审查结论

request changes

详细意见

🔴 必须修改(阻塞合并)

  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:346-348] 结构化归因作用于任意成功工具的 result.value,超出 DSH 成功契约(核实并支持 ikunkun-sys 本轮 P1)。当前对所有 result.isError === false 的结果调用 classifyStructuredEnvironmentError(result.value),但 DSH 契约下成功工具的 value 只是其 output schema 声明的任意 JsonValue,exitCode/success/ok/timedOut 并非宿主级通用失败标志(只有 tool-bash 这类工具的契约声明 { kind:'foreground', exitCode, timedOut, stderr:{text,truncated}, ... })。本地复现(Node v22.21.1,直接对 dist 驱动 tools/post-execute listener):

    1. 成功业务工具返回 { kind:'audit-record', exitCode:1, stderr:{ text:'permission denied was captured in the historical record' } } → 注入 [tokenless:env] GetProcessRecord failed: ENV_PERMISSION ... Skip retry.(复现 reviewer 场景);
    2. 业务数据仅含 timedOut: true、无任何错误文本 → 走 timedOut 兜底注入 ENV_NETWORK ... Skip retry.(纯字段名误报,无需任何文本命中);
    3. 业务数据 success:false + error:{ message:'ENOENT: archived log no longer present' } → 注入 ENV_FILE_MISSING ... Skip retry.
    4. 上述注入在 responseCompressionEnabled: false 下同样发生(归因先于且独立于压缩门控,已在代码 :343-354 与 :359-360 确认);skipTools 命中的工具同样会被注入(归因在 skip 判断之前计算)。

    影响:归因消息断言"<工具> failed"并指令模型 "Skip retry",而工具实际成功——把合法业务数据变成误导模型的失败指令,与归因作为可信安全信号的设计目标直接冲突;且当前无任何配置开关可关闭该路径。

    修复建议(已本地验证):把结构化归因门控到输出契约可确认的工具——复用现有 shellTools 集合(DEFAULT_SHELL_TOOLS,可经 config.shellTools 覆盖),仅当 shellTools.has(exec.name) 时才解释 result.value。模拟该门控后全场景通过:上述 1/2/3 误报消除;DSH bash 真阳性(Bash/bash,canonical foreground value)保留;isError: true 的 raw-text 归因不受影响;经 shellTools 配置的自定义 shell 工具仍可归因。实现上可把 shellTools 提升到 apply() 中(当前在 compressionArgs 内每次调用从 config 重组,可顺带统一)。若倾向更精确的契约判定(如结合 value.kind === 'foreground'),请说明覆盖哪些工具契约并补充测试钉死。测试建议:现有结构化归因用例全部使用 name: 'Bash'(tests/test-dsh-adapter.sh:156 起),门控后不受影响;请补"非 shell 工具携带 exitCode/timedOut/success:false 业务数据不得产生归因"的用例钉死本次误报,并补 shellTools 配置扩展用例。

🟡 建议修改(不阻塞但推荐)

  • 本轮无新增非阻塞发现;上一轮 review 的两条非阻塞建议(parity 测试对 API 层阈值硬编码、无 message 的 error 对象不参与扫描)仍然有效,可随本次一并处理。

🟢 值得肯定

  • shell 工具范围内的既有修复依然正确且被测试钉死:非数字 exitCode 拒绝归因、exit 0 不归因、canonical foreground value 归因、waterfall 组合、Code Mode 子调用跳过压缩保留归因、五路 fail-open 均未在本轮验证中发现回归。本轮问题仅在于结构化归因的适用范围超出了 DSH 成功契约,收敛门控即可,不需要重构归因逻辑。

- compress JSON tool results through the native post-execute seam
- preserve unsafe or unsupported outputs with fail-open behavior
- ship the DSH bundle through raw, RPM, and npm packages

Signed-off-by: kongche-jbw <kongche.jbw@alibaba-inc.com>
@kongche-jbw
kongche-jbw force-pushed the feat/tokenless/dsh-native-plugin branch from 4fc92c1 to 4736009 Compare August 16, 2026 07:31
@kongche-jbw

Copy link
Copy Markdown
Collaborator Author

Review update for 47360097:

  • Gated structured-value attribution to configured shellTools; arbitrary successful business values no longer become failure instructions.
  • Preserved raw isError attribution for every tool and custom shell-tool support.
  • Completed the non-blocking parity/error-object suggestions and aligned classification to stderr + error without a generic timeout fallback.
  • Kept TOON and minimum-size expansion out of the v1 implementation; both are now explicit PR limitations.

Validated with the native DSH adapter test, lint, and the full Tokenless test suite against the branch binary.

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review(head 4736009):通过。针对 ikunkun-sys / Forrest-ly 提出的 P1(成功业务工具的 result.value 被当作通用失败协议)逐条验证如下。

1. P1 修复验证 ✅

dist/index.js 中结构化归因现已严格门控:

const structuredError = result?.isError === false && shellTools.has(exec.name)
  ? classifyStructuredEnvironmentError(result.value)
  : undefined
  • classifyStructuredEnvironmentError 只对配置 shellTools 契约内的工具生效(默认为共享 shell 工具分类表,可经 plugin config 覆盖);
  • 成功业务工具的任意 valueexitCode / success / ok / timedOut 等字段)不再被解释为宿主级失败。

本地独立复现(Node v22.21.1 直接 import 插件):

  • ikunkun-sys 的 audit-record 场景({ kind: 'audit-record', exitCode: 1, stderr: { text: 'permission denied...' } })→ 不再注入 [tokenless:env] ... Skip retry.
  • 另两种误报形态(timedOut: true 记录型 value、success: false 归档型 value)→ 均不再误归因;
  • 以上在 responseCompressionEnabled: truefalse 下均验证通过(归因计算先于压缩门控,路径独立)。

2. raw isError 归因路径不受影响 ✅

  • 非 shell 工具 isError: true(error.message / content 文本)仍正确归因(ENV_DEPENDENCY_MISSING 等);
  • canonical DSH bash 场景保持正确:exitCode 127 → ENV_DEPENDENCY_MISSING、exitCode 1 + stderr permission → ENV_PERMISSION、timedOut 124 + connection timed out → ENV_NETWORK;
  • 负向守卫保持:exitCode 0、非数字 exitCode('N/A')、仅 stdout 含关键词均不误归因;
  • 结构化与 raw 两条路径按 isError === true / false 互斥,不会双重注入。

3. 新增测试真实有效 ✅

tests/test-dsh-adapter.sh 已覆盖作者声称的三类用例并全部通过:

  • 三种 shell-shaped 业务 value(audit-record / latency-record / archived-result)断言 additionalContexts === undefined
  • 自定义 shellTools: ['custom_process'] 配置下结构化归因生效(ENV_PERMISSION);
  • canonical bash 用例及负向用例钉死。

本地执行结果(Node v22.21.1):bash tests/test-dsh-adapter.sh ✅、make test-adapters ✅、make build-dsh-plugin ✅。

4. 无新问题引入 ✅

  • 压缩关闭 / skip 工具 / Code Mode 子调用(exec.parent)路径:归因保留、decision 形状不被改写(waterfall 透传),且不调用 tokenless CLI;
  • 打包侧(Makefile、RPM spec、raw package、npm script、verify-release.py)与既有方案一致,未发现回归。

非阻塞备注

  1. shellTools 配置为数组时是整体替换默认集(注释已说明 "extend or replace"),自定义后默认 shell 工具不再走结构化归因——属文档化语义,使用方配置时需注意;
  2. JS 版 ENV_PATTERNScommon/hooks/hook_utils.py 实质一致(均为大小写不敏感匹配),仅 which:\s+no(JS)比字面量 "which: no"(py)略宽;两边仍靠注释约定手工同步,建议后续考虑加一个机械 parity 测试钉死(不阻塞本 PR,属既有设计)。

LGTM,感谢快速修复。

@Forrest-ly Forrest-ly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总体评价

针对 head 4736009 复核上轮 CHANGES_REQUESTED(结构化归因作用于任意成功工具的 result.value)与 ikunkun-sys 的 P1。修复方式与上轮建议完全一致且实现正确:shellTools 已提升到 apply() 中只构建一次(dist/index.js:346),结构化归因严格门控为 result?.isError === false && shellTools.has(exec.name)(dist/index.js:352-354),raw isError 归因对所有工具保留。本地 Node v22.21.1 独立复现上轮全部误报场景均已消除,真阳性全部保留,测试已将新行为钉死。无新增阻塞问题,同意合并。

审查结论

approve

详细意见

🔴 必须修改(阻塞合并)

无。

🟡 建议修改(不阻塞但推荐)

  • [src/tokenless/adapters/tokenless/dsh/dist/index.js:159-167](补充上轮"无 message 的 error 对象不参与扫描")结构化路径已修复——value.errormessage 时现在会 JSON.stringify(error) 参与扫描(errorObject 用例以 { code: 'EACCES', errno: 13 } 钉死 ENV_PERMISSION,已本地验证)。仅剩的对称性缺口在 raw 路径:errorText()isError: true 结果只取 error.message,message 缺失时退回 content 文本;若宿主以无 message 的对象形式给出 error 且 content 未复述诊断信息,该失败不会获得归因。方向是安全的(漏报而非误报),可选打磨:errorText() 对无 message 的 error 对象做同样的 stringify 兜底。不阻塞。

🟢 值得肯定

  • 门控修复的实现质量高:完全按上轮建议复用 shellTools 集合并提升作用域,compressionArgs 的 shell/api 阈值选择与归因门控现在共用同一集合,消除了两处重组的不一致风险;toolSet 对非法配置值不放宽默认集合。
  • 上轮 4 个误报形态全部以用例钉死(tests/test-dsh-adapter.sh):audit-record exitCode:1+stderr 业务数据、timedOut:true 纯字段名数据、success:false+ENOENT 业务数据、responseCompressionEnabled:false 下的注入——本地独立复现 8 组场景(含 skipTools 命中、自定义 shellTools 恢复归因、配置替换默认集合后 Bash 不再归因)全部符合预期。
  • timedOut 兜底(无文本命中即注入 ENV_NETWORK)被移除是正确收敛:该兜底对非网络超时语义不准,现在超时真阳性仍可通过 stderr 模式命中(exitCode:124+connection timed out → ENV_NETWORK 用例保留),无模式命中则不归因(unclassifiedTimeout 用例钉死),与"归因必须是可信信号"的目标一致。
  • 上轮两条遗留 🟡 均已落实:与 common/hooks/tool_categories.json 的 parity 测试(skip 集合不触发 CLI、shell 集合使用共享阈值 argv 逐项断言)——另经本地双向核对,DEFAULT_SKIP_TOOLS/DEFAULT_SHELL_TOOLS/DEFAULT_THRESHOLDS 与共享 JSON 完全一致;无 message error 对象的扫描修复见上。
  • 回归面完整:canonical DSH bash 归因、非数字 exitCode 拒绝、exit 0 不归因、waterfall 组合(block/value 透传、downstream additionalContexts 保留)、Code Mode 父调用跳过压缩保留归因、五路 fail-open、raw 打包测试,本地全部通过(test-dsh-adapter.shtest-package-raw.shmake build-dsh-plugin)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:tokenless src/tokenless/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants