Skip to content

feat(tokenless): add WorkBuddy (Tencent CodeBuddy) adapter - #2532

Open
Forrest-ly wants to merge 11 commits into
alibaba:mainfrom
Forrest-ly:feat/tokenless-workbuddy-adapter
Open

feat(tokenless): add WorkBuddy (Tencent CodeBuddy) adapter#2532
Forrest-ly wants to merge 11 commits into
alibaba:mainfrom
Forrest-ly:feat/tokenless-workbuddy-adapter

Conversation

@Forrest-ly

@Forrest-ly Forrest-ly commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add a workbuddy adapter for WorkBuddy (Tencent CodeBuddy product family), closing #2524.

WorkBuddy desktop, CodeBuddy Code CLI and WorkBuddy Enterprise share the .codebuddy settings protocol — a Claude Code-shaped hooks key in settings.json — but expose no plugin system, so the adapter merges the tokenless hook groups into the user-level ~/.codebuddy/settings.json.

Changes

  • adapters/tokenless/workbuddy/
    • hooks/hooks.json — template with @TOKENLESS_ADAPTER_DIR@ placeholder: PreToolUse Bashrewrite_hook.py, catch-all PreToolUse group → tool_ready_hook.sh, PostToolUse negative-lookahead group (skips Read|Glob|NotebookRead) → compress_response_hook.py. WorkBuddy matchers alias CLI/IDE tool names (Bashexecute_command), so CC-style matchers carry over
    • hooks/run-hook.sh — symlink to the shared dispatcher (same convention as qoder/qwencode)
    • scripts/install.sh — idempotent merge into ~/.codebuddy/settings.json; stamps the absolute adapter directory (no plugin-root substitution); preserves user-configured hooks and every other settings key; refuses invalid JSON without clobbering; graceful no-op when ~/.codebuddy is absent
    • scripts/uninstall.sh — removes only tokenless-owned entries (TOKENLESS_AGENT_ID=workbuddy marker)
    • scripts/detect.sh — tri-state report aligned with claude-code/qwencode conventions
  • common/hooks/rewrite_hook.py — emit WorkBuddy's modifiedInput partial field override (its documented PreToolUse input-rewrite field) when TOKENLESS_AGENT_ID=workbuddy, alongside the existing tool_input/updatedInput formats
  • manifest.json.inworkbuddy target (hooks: rewrite / compress-response / compress-toon / tool-ready)
  • Makefile — workbuddy-install / workbuddy-uninstall targets wired into adapter-install / adapter-uninstall, help text, installer test in test-adapters
  • Raw packager — materialize the workbuddy/hooks/run-hook.sh symlink; test fixture updated accordingly
  • RPM spec — package the adapter files, %preun cleanup hook
  • Docs (EN/ZH) — support-matrix row, activation notes (lifecycle script, same treatment as OpenCode since the anolisa adapter enable Rust driver set is maintained outside this component), QUICKSTART table; changelog entries
  • Tests — installer lifecycle script (10 cases), rewrite_hook unit tests for modifiedInput dialect behavior, compress_response_hook unit test pinning the additive delivery

Response compression is delivered via additionalContext per the issue scope. Note: the current CodeBuddy CLI hook reference documents PostToolUse updatedToolOutput for all tools (its stated typical use is exactly compression-style hooks), which could enable full replacement; since the issue explicitly scopes replacement as a follow-up pending per-product verification (WorkBuddy desktop / CodeBuddy CLI / WorkBuddy Enterprise are distinct products), this PR keeps the conservative additive path. A follow-up can upgrade workbuddy to the replacement dispatch in compress_response_hook.py once confirmed on the target surface.

测试情况 (Test report)

Environment: Linux x86_64; GNU bash 4.4.20; system Python 3.8.17 and user-local Python 3.11.14; GNU Make 4.2.1; Node v22.21.1.

Executed:

Test Result
tests/test-workbuddy-adapter-install.sh (new) ✅ 10/10 passed — merge/idempotency/user-hook & settings-key preservation/uninstall/fresh settings.json creation/no-WorkBuddy no-op/invalid-JSON refusal/detect tri-state
End-to-end dispatch smoke: stamped hooks.json command → run-hook.sh symlink → rewrite_hook.py ✅ emits modifiedInput + compat fields with a real rewrite
tests/test_rewrite_hook.py workbuddy cases (new) modifiedInput emitted for workbuddy, omitted for other agents
tests/test_compress_response_hook.py workbuddy case (new) ✅ additionalContext delivery pinned (no updatedToolOutput)
tests/test-run-hook-install-scope.sh ✅ passed
tests/test-tool-ready-readable-fixer.sh ✅ passed
tests/run-all-tests.sh 69/72 — the 3 failures are the pre-existing env-check hard bypass failures, reproduced identically on the unmodified base tree
tests/test_compress_response_hook.py / test_compress_schema_hook.py / test_resolve_agent_id.py / test_agentscope_middleware.py (py3.11) ✅ OK
tests/test-qoder-adapter-install.sh (regression) ✅ 17/17
tests/test-opencode-adapter-install.sh (regression) ✅ 13/13
tests/test-package-raw.sh ✅ passed after adding the workbuddy fixture hook
make stamp-adapter-templates + manifest inspection workbuddy target present with capabilities/actions
make -n workbuddy-install / make -n workbuddy-uninstall ✅ resolve to the new scripts
bash -n syntax check on all new scripts

Pre-existing failures (verified identical on the unmodified base tree, unrelated to this change):

  • tests/test_rewrite_hook.py — 11 anchor-test failures: a system-installed rtk in a hardcoded fallback path shadows the sandboxed fake rtk the test stages (my 2 new tests are path-independent and pass).
  • tests/test_hermes_plugin_import.py — 1 failure: a system-installed adapter tree under /usr/local/share/... shadows the test's XDG_DATA_HOME sandbox.
  • On system Python 3.8, test_agentscope_middleware.py cannot import (StrEnum needs 3.11) — same on the base tree.

Not run (with reasons):

  • cargo build / cargo test — no Rust code is touched by this PR.
  • make test-adapters full target — its build-openclaw-plugin prerequisite performs an npm dependency build not needed for this change; the individual installer tests above were run directly instead.
  • rpmbuild of tokenless.spec.in — a full RPM build requires the complete native build chain; the spec additions mirror the existing qwencode entries line-for-line.
  • Live WorkBuddy/CodeBuddy runtime verification — no WorkBuddy/CodeBuddy product is installed in the test environment; hook behavior against a running product (including the /hooks panel review flow and per-product updatedToolOutput support) needs verification on a machine with the product installed.

Rebase onto latest main (2026-08-22)

This branch was rebased onto the latest main to resolve merge conflicts (GitHub reported CONFLICTING). Conflicts and resolutions:

  • docs/user-guide/en/token-saving/tokenless/framework-integration.md / docs/user-guide/zh/token-saving/tokenless/framework-integration.md — kept main's updated Qwen Code schema column (, per main's note that current Qwen Code releases skip the BeforeModel hook) and kept this PR's WorkBuddy row and WorkBuddy section from both sides.
  • src/tokenless/CHANGELOG.md / CHANGELOG_zh.md — kept main's 0.7.7–0.7.11 release entries. This PR adds its changelog entry and then drops it in a later commit (net zero), so [Unreleased] stays empty as on main.
  • src/tokenless/tests/test_compress_response_hook.py — kept both main's TestToonMinPayloadThreshold additions and this PR's TestWorkBuddyCliDetection.

Test situation for the rebase push

Environment: Linux x86_64; cargo/rustc 1.96.0; system Python 3.8.17.

Check Result
cargo check --workspace (src/tokenless) ✅ pass
cargo test --workspace -- --test-threads=1 ✅ 615 passed, 0 failed, 2 ignored
cargo clippy --workspace -- -D warnings ✅ no warnings
bash tests/test-workbuddy-adapter-install.sh ✅ 15/15
bash tests/test-package-raw.sh ✅ pass
python3 tests/test_rewrite_hook.py ✅ 17/17
python3 -m py_compile on every Python file touched by this PR ✅ pass
Tree integrity: diff vs origin/main limited to this PR's own files ✅ verified

Not run (with reasons):

  • Python 3.9+ gated tests (test_compress_response_hook.py incl. TestWorkBuddyCliDetection, test_compress_toon_hook.py, test_compress_schema_hook.py, and other gated hook tests): only Python 3.8 is usable in this environment (3.9+ interpreters hang at startup; container runtime not accessible). Under 3.8 the files parse cleanly and all gated cases skip by design (e.g. 38 skipped in test_compress_response_hook.py).
  • test_hermes_plugin_import.py::test_resolves_via_xdg_data_home fails identically on a clean origin/main worktree in this environment (a real host installation shadows the test sandbox) — pre-existing, unrelated to this rebase.

@github-actions github-actions Bot added component:tokenless src/tokenless/ scope:documentation ./docs/|./*.md|./NOTICE labels Aug 14, 2026

@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.

[P1] WorkBuddy 文档与 detect.sh 三态语义略有偏差:QUICKSTART 与 framework-integration 强调“无 WorkBuddy 时生命周期脚本优雅跳过”,但 detect.sh 在 ~/.codebuddy 缺失时按约定返回 2(缺前置条件)。建议在 WorkBuddy 章节中补充说明 detect.sh 的 0/1/2 语义及“缺 ~/.codebuddy 时视为未安装”的判定逻辑,以免用户混淆。

[P2] WorkBuddy detect.sh 对 tokenless/rtk 二进制的前置条件判定在未来“仅 CLI 压缩”或远程 Tokenless 场景下可能偏严:当前缺少本地 tokenless/rtk 即归入 PREREQ_MISSING 并返回 2,合理但值得在后续扩展时评估是否弱化为 install-missing 或在文档中明确现有限制。

[P2] tokenless.spec.in 中 %preun 仅清理 root 家目录的 WorkBuddy hooks,多用户主机上其他用户需自行运行 uninstall.sh。当前注释已有提示,建议在 docs/CHANGELOG 中也增加一句“非 root 用户需自行运行 uninstall.sh”以对齐 OpenCode/Qwen Code 的卸载说明,减少管理员误解。


🤖 Generated by QoderView workflow run

Comment on lines +205 to +207
### WorkBuddy

WorkBuddy (Tencent CodeBuddy) has no plugin system for hooks. The bundled lifecycle script merges the Tokenless hook groups into the user-level `~/.codebuddy/settings.json`, whose `hooks` key follows the Claude Code matcher-group shape:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P1] detect.sh 在无 WorkBuddy 时返回 2 与文档“无 WorkBuddy 时优雅 no-op”略有偏差,建议在文档中补充说明三态含义。

当前 docs/QUICKSTART 与 framework-integration 中对 WorkBuddy 的描述强调“无 WorkBuddy 时生命周期脚本优雅跳过”,而 scripts/detect.sh 在 ~/.codebuddy 缺失时按约定返回 2(缺前置条件)。为了避免用户误解,可在 WorkBuddy 章节中简要说明 detect.sh 的 0/1/2 语义及“缺 ~/.codebuddy 时视为未安装”的判定逻辑。


🤖 Generated by QoderFix in Qoder

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳。已在 docs/user-guide/en|zh/token-saving/tokenless/framework-integration.md 的 WorkBuddy 章节补充 detect.sh 的三态退出码语义:0 = 已检测到 WorkBuddy/CodeBuddy 且 Tokenless Hook 已安装;1 = 已检测到但 Hook 尚未安装;2 = 缺少前置条件。同时说明了“缺 ~/.codebuddy 视为未安装”的判定逻辑(detect.sh 返回 2,而 install.sh 对同一情况优雅跳过并以 0 退出,二者语义差异已在文档中讲明)。见 cc39b59

Comment on lines 451 to +447
bash "$OPENCODE_SCRIPT" || true
fi

# --- WorkBuddy hooks cleanup ---

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] tokenless.spec.in 中 %preun 针对 root 家目录的 WorkBuddy hooks 清理逻辑需要用户文档提示以避免“多用户环境”误解。

%preun 仅调用 /usr/share/anolisa/adapters/tokenless/workbuddy/scripts/uninstall.sh,这在注释中也说明只触及 root 的 ~/.codebuddy/settings.json。为避免管理员误以为卸载 RPM 即会自动清理所有用户的 WorkBuddy hooks,建议在 docs 或 CHANGELOG 中增加一句“在多用户主机上,非 root 用户需自行运行 uninstall.sh”提示,与 OpenCode/Qwen Code 的清理说明保持一致。


🤖 Generated by QoderFix in Qoder

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳。考虑到仓库文档规范规定 CHANGELOG 仅在 release version bump PR 中修改,提示改为写入用户文档:已在 docs/user-guide/en|zh/token-saving/tokenless/framework-integration.md 的 WorkBuddy 章节补充“RPM %preun 以 root 运行,仅清理 root 用户的 ~/.codebuddy/settings.json;多用户主机上其他用户需自行运行 uninstall.sh”。见 cc39b59

note_prereq_missing "shared hooks dir"
fi

if [ ${#PREREQ_MISSING[@]} -gt 0 ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] WorkBuddy detect.sh 对 tokenless/rtk 二进制的前置条件判定可能在“仅 CLI 压缩”场景下过于严格。

scripts/detect.sh 一旦找不到 tokenless 或 rtk 即将其归入 PREREQ_MISSING 并返回 2,这与“无 WorkBuddy 时优雅 no-op”的整体叙述一致,但在未来如果存在纯 CodeBuddy/WorkBuddy runtime 使用场景(例如仅依赖服务器侧 Tokenless),本地缺少这些二进制并不必然意味着“无法安装”。建议在后续扩展时评估是否需要单独弱化这两项为 install-missing 或在文档中明确当前限制。


🤖 Generated by QoderFix in Qoder

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

不采纳(保留现有代码行为),理由:workbuddy detect.sh 对 tokenless/rtk 二进制的前置条件判定与现有 qwencode detect.sh 的约定完全一致(均按前置条件缺失报退出码 2);弱化为 install-missing 属于语义变更,且意见中提到的“仅服务器侧 Tokenless”场景目前并不存在,不宜提前引入 adapter 间的行为分歧,留待该场景真正出现时再统一评估。意见中“在文档中明确当前限制”的替代方案已落实:本次文档更新已写明“本版本中缺少 tokenless 或 rtk 二进制按前置条件缺失(退出码 2)上报”。见 cc39b59

@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: 45bd8d8191

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"uninstall": "qwencode/scripts/uninstall.sh"
}
},
"workbuddy": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add WorkBuddy to the install contract

This declares the WorkBuddy target in the adapter manifest, but anolisa install lays adapter directories from the component contract's [[adapters]] entries, not from manifest.json. Since src/tokenless/.anolisa/component.toml.in still ends at qwencode, raw/anolisa installs will not copy {datadir}/adapters/tokenless/workbuddy, so the documented lifecycle script is missing on the primary install path; add a matching WorkBuddy adapter entry to the component contract and synced built-in manifest.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not adopting this one — the omission is intentional and matches the established OpenCode pattern:

  • WorkBuddy (like OpenCode) is a lifecycle-script adapter; the user guide states explicitly that both "are not registered with the anolisa adapter enable driver set in this release" (framework-integration.md, QUICKSTART.md).
  • The component contract's [[adapters]] list declares adapters that ship a built-in CLI framework driver. That convention was set by 0f48bd3 ("Declare every shipped driver ... Keep qwencode unlisted until a built-in driver is available"), and qwencode was only added after its driver landed. The closed DriverRegistry in anolisa-core has no opencode or workbuddy driver.
  • A driver-less [[adapters]] entry would surface workbuddy in anolisa adapter scan/enable flows and produce "no built-in driver for framework" reports.
  • The documented staging paths install the adapter directory explicitly: RPM %install and make -C src/tokenless install (the docs' bash block references exactly that staging), and the raw tarball ships the full adapter tree via packaging/raw/package.sh.
  • The synced built-in manifest (src/anolisa/manifests/components/tokenless/component.toml) belongs to the anolisa component; this tokenless PR does not modify it.

If a built-in workbuddy driver lands later, adding the contract entry (both copies) should come with that driver PR, same as qwencode did.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

跟进(新一轮 review 总结中再次提到本项):已在 819df7f 修复 raw 安装契约缺口。

  • src/tokenless/.anolisa/component.toml.in 新增 workbuddy 的 [[adapters]] 条目(source = "adapters/workbuddy"{datadir}/adapters/{component}/workbuddy/)。raw 安装器从制品内嵌的 .anolisa/component.toml 读取 [[adapters]] 铺设 adapter 目录(install 只铺数据,不触发 anolisa adapter enable 驱动,workbuddy 仍保持 lifecycle-script 形态、不进驱动集),而 raw payload 本就携带 adapters/workbuddy/ 全量文件,因此 anolisa install tokenless 后文档引用的 .../workbuddy/scripts/install.sh 生命周期脚本即存在。
  • 之前以"与 OpenCode 一致"为由不采纳的结论不成立:opencode 同样缺失该条目属于既有缺口,不能作为 workbuddy 保持缺口的依据。opencode 的同类缺口不在本 PR 范围内,未一并改动。
  • 注册表侧的内置 manifest(src/anolisa/manifests/components/tokenless/component.toml)由发布流程同步(当前 dsh 条目同样尚未同步进去),不随本功能 PR 变更。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

按 review 摘要中的再次指出,此问题现已修复:src/tokenless/.anolisa/component.toml.in 增加了 workbuddy 的 [[adapters]] 条目(framework/source/dest;与 OpenCode 相同是生命周期脚本 adapter,不带 bundle),raw 安装的 adapter 铺设逻辑会把 adapters/workbuddy 复制到 {datadir}/adapters/tokenless/workbuddy/anolisa install tokenless 后文档引用的 lifecycle script 存在(见 819df7f,后续 commit 保留)。

两点说明:

  • 之前不采纳的理由部分仍然成立(0f48bd33 的"只声明带内置驱动的 adapter"约定、anolisa-core 的驱动集没有 workbuddy 驱动),所以该条目刻意不带 adapter_type/bundle:它只负责安装铺设,anolisa adapter enable tokenless workbuddy 仍走 fail-closed 报错,QUICKSTART 也写明本版本不以 anolisa adapter enable 注册 WorkBuddy。
  • 内置同步 manifest(src/anolisa/manifests/components/tokenless/component.toml)不在本 PR 修改——它与 dsh 一样由 release 同步流程更新;raw 安装读取的是 artifact 内随包的 component.toml,文档引用的路径已由本条覆盖。

Comment thread src/tokenless/CHANGELOG.md Outdated
Comment on lines +12 to +14
### Added

- WorkBuddy (Tencent CodeBuddy) adapter: hook groups are merged into the user-level `~/.codebuddy/settings.json`, command rewriting emits WorkBuddy's `modifiedInput` partial field override, and response compression is delivered via `additionalContext` ([#2524](https://github.com/alibaba/anolisa/issues/2524)).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove the daily changelog entry

This feature commit adds an Unreleased changelog entry, but the repository documentation standard reserves CHANGELOG edits for release version bump PRs; daily feature/fix PRs should update the README/user-guide only. Keeping this here makes the docs change non-compliant for a non-release commit, so move it to the release bump instead.

AGENTS.md reference: AGENTS.md:L337-L343

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Adopted — removed the Unreleased entry this PR added from both CHANGELOG.md and CHANGELOG_zh.md, restoring the section to its state on main. Per specs/documentation-standard.md ("CHANGELOG is written exclusively in release version bump PRs"), the entry will be aggregated into the next release bump PR instead. See cc39b59.

@Forrest-ly
Forrest-ly force-pushed the feat/tokenless-workbuddy-adapter branch 2 times, most recently from cc39b59 to 387d2ca Compare August 17, 2026 08:43

@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 387d2ca94f48d9e84ac3cd77cf0f65e8957372fa 请求修改。主要阻塞项:

  • WorkBuddy 安装/卸载会放宽现有 settings.json 的权限(见内联评论)。
  • rewrite hook 的 modifiedInput 输出不满足官方 WorkBuddy/CodeBuddy 宿主契约(见内联评论)。
  • CodeBuddy CLI 已支持替换工具结果,但当前实现与测试锁定在追加上下文路径,实际会同时保留原结果和压缩结果(见内联评论)。
  • 现有评论 3781421789 指出的 raw ANOLISA 安装契约仍未解决:raw installer 只铺设 layout 和 manifest 的 [[adapters]],而本 PR 未把 WorkBuddy 加入这两个入口;因此 anolisa install tokenless 后文档要求执行的 WorkBuddy lifecycle script 并不存在。该问题已有未解决线程,这里不重复创建。

次要兼容性问题:manifest 使用 compatibleVersions: "*",但 CodeBuddy CLI Hooks 官方要求 v1.16.0+;文档中“没有插件系统”的能力描述也已被当前官方 plugin hooks 支持所取代。

本地验证通过了新增 WorkBuddy 测试、rewrite/compress hook 测试、raw package 测试和 git diff --check,但这些测试没有运行真实 WorkBuddy/CodeBuddy 宿主,不能覆盖上述宿主契约问题。


config["hooks"] = hooks

tmp_path = config_path + ".tmp"

@ikunkun-sys ikunkun-sys Aug 19, 2026

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.

[P1] 安装会放宽 settings.json 的权限。这里用默认创建模式写固定 .tmp 后再 os.replace;在 umask 022 下可复现现有文件从 0600 变为 0644,uninstall.sh 的同一路径也一样。CodeBuddy 官方允许在 settings.json.env 中保存 CODEBUDDY_API_KEY 和认证 token,因此安装或卸载 Tokenless 后,其他本地用户可能读取这些凭据。

官方依据:CodeBuddy CLI settings

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(819df7f4)。

修复方式(install.sh / uninstall.sh 同一处理):

  • 重写前先 os.stat 记录现有 settings.json 的权限与属主;
  • 临时文件改用 os.open(..., 0o600) 创建,避免默认创建模式在 umask 022 下产生 0644 的中间文件(该窗口期内凭据同样不可被其他用户读取);
  • 写入后先 chmod 恢复原文件权限位、best-effort chown 恢复属主,再 os.replace;任何失败都会清理临时文件。
  • 文件不存在时新建,直接以 0600 起始(CodeBuddy 官方文档明确 settings 的 env 字段可保存 CODEBUDDY_API_KEY / CODEBUDDY_AUTH_TOKEN,新文件按最保守权限处理)。

复现 → 修复对比(umask 022,文件初始 0600):

  • 修复前:install 后变为 0644(已复现确认);
  • 修复后:install/uninstall 后保持 0600

新增测试(tests/test-workbuddy-adapter-install.sh,14/14 通过):

  • umask 022 下 install 与 uninstall 均保持既有 0600;
  • 既有 0644 不被改动(不改变现有用户的行为);
  • 新建文件为 0600。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已修复。install.sh 与 uninstall.sh 都改为:在同一目录下用 tempfile.mkstemp 创建唯一的 0600 临时文件写入,os.replace 之前先恢复现有 settings.json 的权限位;文件不存在时新建文件保持 0600。umask 022 下 0600 不会再被放宽成 0644,固定的 .tmp 路径也一并消除(临时文件失败即清理)。

回归测试见 tests/test-workbuddy-adapter-install.sh:umask 022 下 install/uninstall 保持 0600、既有 0644 不被改动、新建文件 0600、无临时文件残留。

Commits: 819df7f、c1186b82。

"updatedInput": updated_input,
}
if _AGENT_ID == "workbuddy":
hook_output["modifiedInput"] = {"command": rewritten}

@ikunkun-sys ikunkun-sys Aug 19, 2026

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.

[P1] 当前 WorkBuddy 改写结果缺少宿主要求的决策字段。官方 WorkBuddy Enterprise 与 CodeBuddy IDE 的参数修改示例和故障排查均要求 modifiedInputpermissionDecision: "allow" 同时返回;这里只输出 modifiedInput,现有单测仅验证 JSON 形状,无法证明真实宿主会应用改写。由于 allow 还会绕过权限系统,这需要明确的安全取舍和真实宿主验证,不能仅视为补一个字段。

官方依据:WorkBuddy Enterprise HooksCodeBuddy IDE Hooks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(819df7f4)。这里确实不能只当补字段处理,按"映射 rtk 自身裁定"做了明确的安全取舍:

合约核对:CodeBuddy CLI Hooks 文档(v1.16+,PreToolUse 决策控制一节)与 CodeBuddy IDE Hooks 文档的参数修改示例均要求 modifiedInput 与显式 permissionDecision 同时返回;allow 绕过权限系统、ask 弹确认框、deny 阻止执行。

安全取舍(rewrite_hook.py,仅 workbuddy 分支生效,其他宿主输出语义完全不变):

  • rtk exit 0(rtk 权限规则已批准的 Allow 裁定)→ permissionDecision: "allow" + reason。此处并未绕过本应存在的门禁:rtk 自己的权限模型已对改写后的命令给出 Allow。
  • rtk exit 3(Ask/Default 裁定)→ permissionDecision: "ask" + reason,由用户在宿主界面确认改写后的命令,保留 rtk 要求的确认环节,绝不静默放行。
  • rtk exit 2(deny)与 exit 1(无改写)在上游即 skip,不会输出任何决策。
  • 即:对于 rtk 会拒绝或要求确认的命令,hook 永远不会输出 allow

测试(tests/test_rewrite_hook.py,15/15 通过):新增 allow/ask/deny 三种裁定的映射用例,并断言非 workbuddy 宿主不会输出 permissionDecision(避免意外改变其他运行时的权限行为)。

真实宿主验证说明:本环境没有可交互的 CodeBuddy 安装,验证手段为官方合约文档逐条比对 + 单测强制输出形状。建议合并前在有 CodeBuddy Code v1.16+ 的环境做一次端到端确认:① Allow 裁定的改写是否被宿主应用;② Ask 裁定的改写是否弹出确认框。如果真实宿主行为与文档有出入,我再跟进调整。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已修复,并明确了安全取舍。三份官方文档(CodeBuddy CLI Hooks 参考、CodeBuddy IDE Hooks 的故障排查 Q5、WorkBuddy Enterprise Hooks)都表明 modifiedInput 只有与 permissionDecision 成对返回才会生效,因此 hook 现在必须携带决策字段——此前只输出 modifiedInput 意味着改写在所有 workbuddy 宿主上都不会生效。

为了不让 "allow" 绕过宿主权限系统超出 rtk 自身权限模型的范围,决策按 rtk 的 verdict 映射:

  • rtk Allow(exit 0)→ permissionDecision: "allow":rtk 的权限规则已批准该重写;
  • rtk Ask(exit 3)→ permissionDecision: "ask":由用户在宿主界面确认改写后的命令,保留 rtk 要求的确认环节;
  • rtk deny(exit 2)→ 直接 skip,不输出任何决策。

即 hook 永远不会对 rtk 会拒绝或要求确认的命令输出 "allow"

关于真实宿主验证:本环境无法运行真实 WorkBuddy/CodeBuddy 宿主,已按官方文档的字段约定补了 allow / ask / deny 三种映射的单测(tests/test_rewrite_hook.py),并验证非 workbuddy 宿主不受影响;建议合入前在有真实宿主的机器上再做一次端到端确认(尤其 ask 场景下宿主的确认对象)。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(c8db850)。本轮先用真实环境核实了该意见:

验证(rtk v0.43.0 源码 + 本机安装的 rtk 0.43.0 实测)

  • rtk hooks/rewrite_cmd.rs 的退出码协议:Allow→0、Passthrough→1、Deny→2、Ask 与 Default 都→3,且其安全测试明确注释 "CRITICAL: PermissionVerdict::Default MUST map to exit 3 (ask), NOT exit 0"。rtk 的判定只读取 Claude/Cursor/Gemini 的权限配置文件,WorkBuddy 宿主上通常没有任何规则。
  • 实测 rtk rewritegit status / ls -la / cargo test / kubectl get pods 等普通命令全部退出 3
  • 结合官方契约(参数修改示例要求 permissionDecision: "allow";IDE Hooks 故障排查 Q5 明确其他决策下工具按原参数执行),旧的 3→ask 映射意味着所有普通命令的改写在 WorkBuddy 上都被静默丢弃——意见成立。

契约冲突与取舍:WorkBuddy 无法表达"应用改写 + 弹窗确认"的组合——allow 会对 rtk 未背书的命令绕过宿主权限确认,ask 会丢弃 modifiedInput;而 rtk 刻意把 Default 与 Ask 归为同一退出码,就是不让未背书命令被自动放行。两者不可兼得,因此:

  • 默认(安全侧):workbuddy + 退出码 3 时不再输出任何决策,原命令原样透传,保留宿主自身的权限流程(与"无规则/被拒绝时透传"的既有语义一致)。
  • 显式 opt-inTOKENLESS_WORKBUDDY_AUTO_ALLOW=1 时对这些改写输出 allowpermissionDecisionReason 中明确记录该绕过,供接受此取舍的用户启用。
  • 退出码 0(rtk allow 规则背书)→ allow + modifiedInput 不变;退出码 2(deny)→ 透传不变;其他 agent 无决策契约,Ask/Default 改写照常下发,不受影响。

用户文档(en/zh framework-integration)已同步更新,含 opt-in 说明。

测试tests/test_rewrite_hook.py 17/17(Python 3.10 / 3.13),新增/改写用例覆盖 3→透传、opt-in→allow(理由串含 opt-in 标记)、0→allow、2→透传、非 workbuddy 行为不变;并用真实 rtk 0.43.0 做了端到端复核(见 PR body 追加的测试报告)。

真实宿主验证仍无法在本环境执行:建议合入前在有 WorkBuddy/CodeBuddy 产品的机器上确认两点——退出码 0 场景 modifiedInput 生效,以及 opt-in 场景宿主不再弹窗。

hso = result.get("hookSpecificOutput", {})
self.assertIn("additionalContext", hso,
"WorkBuddy should use additionalContext")
self.assertNotIn("updatedToolOutput", hso,

@ikunkun-sys ikunkun-sys Aug 19, 2026

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.

[P1] 这个断言把 CodeBuddy CLI 锁定到会增加上下文的路径。官方 v1.16+ 合约说明 additionalContext 会保留原工具结果并追加内容,而 updatedToolOutput 对所有内置及 MCP 工具执行替换,压缩长输出正是其用途。由于 hooks.json 把 CLI、IDE 和 Enterprise 都标成 workbuddy,CLI 实际会收到“原结果 + 压缩结果”,与 manifest 宣称的响应压缩能力相反。

官方依据:CodeBuddy CLI Hooks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(819df7f4)。

修复

  • compress_response_hook.py 将 workbuddy 从 additionalContext 追加路径移到 updatedToolOutput 替换路径(与 claude-code/qoder-cli/opencode 同一分发分支)。官方 CodeBuddy CLI Hooks 文档(v1.16+)明确:additionalContext 保留原结果并追加,updatedToolOutput 对所有内置及 MCP 工具执行替换,"压缩型 hook 依赖的正是替换能力"——此前 CLI 收到"原结果 + 压缩结果"确实与 manifest 宣称的响应压缩相反。
  • additionalContext 仅保留给真正附加的环境归因诊断(与 claude-code 等一致)。
  • 结构化工具响应输出紧凑 JSON 字符串形式的 updatedToolOutput(官方示例均为字符串载荷,与 qoder-cli 同样做保守处理)。
  • Fail-open 语义:WorkBuddy/CodeBuddy 家族中未实现 updatedToolOutput 的宿主会忽略该字段——压缩静默跳过,永远不会出现重复载荷;不再存在"变长"风险。
  • manifest 的 workbuddy compatibleVersions"*" 改为 ">=1.16.0",与文档声明的 Hook 合约版本一致。
  • hooks.json 无需按 CLI/IDE/Enterprise 拆分标记:三者共用 ~/.codebuddy/settings.json hooks 协议,替换路径对不支持的宿主是安全 no-op。

测试(tests/test_compress_response_hook.py,17/17 通过):原 test_workbuddy_uses_additional_context 断言反转为替换语义——updatedToolOutput 存在、内容为压缩后载荷(stdout 500→20 字符)、additionalContext 不存在。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已修复。CodeBuddy CLI Hooks 契约(v1.16.0+——Hooks 能力本身就始于该版本,因此能执行这些 hook 的 CLI 必然支持 updatedToolOutput)定义 updatedToolOutput 为对所有内置与 MCP 工具的整体替换,压缩长输出正是其用途;additionalContext 是追加(原结果保留)。

compress hook 现在按宿主分流:当 hook 运行在 CodeBuddy Code CLI 下(通过 CLI 文档记载的 CODEBUDDY_PROJECT_DIR 环境变量识别)时,输出 updatedToolOutput 整体替换工具结果(沿用 schema 字段恢复逻辑;环境归因仍以追加的 additionalContext 交付,官方契约明确两者可同时返回)。IDE 与 Enterprise 宿主的 PostToolUse 官方契约只定义了追加式 additionalContext,因此这些宿主保持追加路径——压缩在它们上面仍然生效,而 CLI 不再出现"原结果 + 压缩结果"的重复。

测试:test_workbuddy_cli_uses_updated_tool_output / test_workbuddy_non_cli_uses_additional_contexttests/test_compress_response_hook.py)。

Forrest-ly added a commit to Forrest-ly/anolisa that referenced this pull request Aug 19, 2026
…dling

Address human review feedback on PR alibaba#2532:

- install.sh/uninstall.sh: preserve settings.json mode and ownership
  across the tmp-file rewrite (create temp 0600, restore existing mode;
  new files start at 0600). CodeBuddy documents credentials in the
  settings env field, so the merge must never widen permissions.
- rewrite_hook.py: emit permissionDecision alongside modifiedInput as
  required by the official WorkBuddy/CodeBuddy PreToolUse contract,
  mapped from rtk's own verdict (allow only for rtk Allow, ask for rtk
  Ask) so the hook never bypasses the host permission system for a
  command rtk would deny or ask for.
- compress_response_hook.py: move workbuddy to the updatedToolOutput
  replacement path (CodeBuddy Code v1.16+ contract: full replacement
  for all built-in and MCP tools); hosts without the field ignore it,
  so compression no-ops instead of appending a duplicate payload.
- component.toml.in: add the workbuddy [[adapters]] entry so raw
  installs lay the lifecycle scripts the user guide references.
- manifest.json.in: declare >=1.16.0 for workbuddy, matching the
  documented hook contract version.
- docs: refresh WorkBuddy section (integration wording, permissionDecision
  trade-off, replacement semantics, version requirement).
- tests: permission-preservation cases for install/uninstall, workbuddy
  decision-mapping cases for the rewrite hook, updatedToolOutput
  assertion for the compress hook.
@Forrest-ly

Copy link
Copy Markdown
Collaborator Author

已按 review 逐条审核并推送修复(commits 819df7f、c1186b82、14c6d383),各 inline comment 下已分别答复:

  1. [P1] settings.json 权限放宽:install/uninstall 改为唯一 0600 临时文件写入 + os.replace 前恢复现有权限位,新建文件保持 0600;回归测试覆盖 umask 022 场景。✔
  2. [P1] rewrite hook 缺 permissionDecision:按官方契约成对输出 modifiedInput + permissionDecision,决策映射 rtk verdict(Allow→allow,Ask→ask,deny→skip)——"allow" 不越过 rtk 自身权限模型,hook 永不对 rtk 会拒绝/要求确认的命令输出 allow。✔
  3. [P1] CodeBuddy CLI 被锁在追加路径:CLI 宿主(CODEBUDDY_PROJECT_DIR 识别)改走 updatedToolOutput 整体替换;IDE/Enterprise 官方 PostToolUse 契约只有 additionalContext,保持追加路径,压缩在其上仍然生效。✔
  4. raw ANOLISA 安装契约(评论 3781421789)component.toml.in 增加 workbuddy [[adapters]] 条目,raw 安装现在会铺设 lifecycle scripts;条目刻意不带 adapter_type/bundle,anolisa adapter enable 仍不注册 workbuddy(QUICKSTART 已写明)。✔
  5. 次要项:manifest workbuddy compatibleVersions 改为 ">=1.16.0"(官方 CLI Hooks 要求 v1.16.0+);文档中"没有插件系统"的过时描述已更新(en/zh),并补充 permissionDecision 取舍、替换/追加的宿主分流与版本要求。✔

本地验证:test-workbuddy-adapter-install.sh 15/15、test_rewrite_hook.py 16/16、test_compress_response_hook.py 18/18、test_compress_schema_hook + test_resolve_agent_id 通过、test-package-raw.sh 通过、test-posttooluse-replacement.sh 通过、git diff --check 干净。

说明:本环境没有真实 WorkBuddy/CodeBuddy 宿主,契约符合性以官方文档字段约定 + 单测断言;建议合入前在真实宿主上做一次端到端确认(尤其 ask 场景宿主的确认对象与 updatedToolOutput 替换效果)。

@ikunkun-sys 请重新 review。

@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.

Re-reviewed current head 14c6d383. The settings-file permission issue and raw-install contract gap are fixed, but two P1 host-contract blockers remain: (1) CODEBUDDY_PROJECT_DIR is also exposed to CodeBuddy IDE hooks, so the current detector misroutes IDE to the CLI-only updatedToolOutput path; (2) the existing rewrite thread remains unresolved because RTK v0.43 maps ordinary Default commands to exit 3 while the WorkBuddy/IDE documentation requires permissionDecision: allow for modifiedInput to take effect. CI and synthetic hook tests pass, but they do not exercise either real-host contract. Requesting changes until both paths are corrected or validated with reproducible IDE/Enterprise host evidence.

needed once the CLI is detected: hooks exist only in CodeBuddy Code
v1.16.0+, and that same contract defines ``updatedToolOutput``.
"""
return bool(os.environ.get("CODEBUDDY_PROJECT_DIR"))

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.

[P1] Do not use CODEBUDDY_PROJECT_DIR to distinguish CLI from IDE. The official CodeBuddy IDE Hooks documentation says IDE hook scripts also receive this variable, so _workbuddy_cli_host() returns true on the desktop/IDE host and routes it to updatedToolOutput instead of the IDE-documented additionalContext contract. The new test encodes the opposite assumption by deleting this variable for non-CLI hosts, so it cannot catch the real behavior. Please use an actual host discriminator or otherwise select a response field supported by each host. Official reference: https://www.codebuddy.ai/docs/zh/ide/Features/Hooks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(c8db850)。

确认问题成立:官方 IDE Hooks 参考的"环境变量"一节明确列出 IDE hook 脚本可访问 CLAUDE_PROJECT_DIRCODEBUDDY_PROJECT_DIR 两个变量,因此它确实无法区分 CLI 与 IDE——旧实现在 IDE 宿主上会误走 CLI 专属的 updatedToolOutput 路径。

修复

  • _workbuddy_cli_host() 不再读任何环境变量,改为进程祖先识别:CLI 以 codebuddy 二进制(经用户 shell)fork hook 子进程,hook 向上遍历自身祖先链(Linux 走 /proc,其他 POSIX 单次 ps 表扫描,深度上限 24,带环路保护),匹配 CLI 二进制 basename(含 shebang/解释器启动形态)。检测 best-effort、fail-safe:无法遍历时(如 Windows)按非 CLI 处理。
  • 同时修正了非 CLI workbuddy 宿主的出口:原实现落入"压缩载荷经 additionalContext 追加"分支,而追加式字段会保留原始结果,等于让上下文变长。现在非 CLI 宿主直接 fail-open 禁用压缩,仅保留真正追加式的环境归因诊断。

测试(删除了靠"删掉该环境变量"模拟非 CLI 的旧用例):

  • e2e:把 hook 真实挂在一个名为 codebuddy 的祖先进程下运行 → 断言走 updatedToolOutput 替换路径;
  • e2e(即本条意见的场景):CODEBUDDY_PROJECT_DIR 已设置但祖先链中没有 codebuddy(IDE 形态)→ 断言无 updatedToolOutput、无追加压缩载荷,原样透传;环境错误场景下归因诊断仍正常交付;
  • 单测:argv 形态匹配(二进制直启、sh/python 解释器启动、codebuddy-ide 等不同产品不误判);monkeypatch 祖先树验证"仅有环境变量不得选中 CLI 路径"与"存在 CLI 祖先即选中"。

tests/test_compress_response_hook.py 23/23 通过(Python 3.10 / 3.13),详见 PR body 追加的测试报告。

说明:本环境没有真实 IDE/Enterprise 宿主,非 CLI 分支的行为依据是官方文档对 PostToolUse 只定义追加式 additionalContext;如有真实宿主证据表明 IDE 实际支持 updatedToolOutput,可以再恢复该路径。

@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.

Re-reviewed current head c8db8506. The RTK exit-3 behavior is now safely explicit, but the WorkBuddy response-compression path still has one P1 host-contract blocker and one P2 side-effect bug. The published CodeBuddy package shows that WorkBuddy itself starts the same cbc/codebuddy headless sidecar, so process ancestry cannot distinguish the desktop host from a standalone CLI. In addition, the non-CLI decision occurs only after compression, stash, and stats work has already run. The updated synthetic tests all pass, but their IDE process tree deliberately omits the real WorkBuddy sidecar and they assert only the final JSON output, not compressor invocations or persistent side effects.

needed once the CLI is detected: hooks exist only in CodeBuddy Code
v1.16.0+, and that same contract defines ``updatedToolOutput``.
"""
return any(_argv_is_codebuddy_cli(argv) for argv in _ancestor_argvs())

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.

[P1] Process ancestry is still not a valid CLI/IDE discriminator. I inspected the officially published @tencent-ai/codebuddy-code@2.137.1 artifact: its bin/codebuddy entry explicitly says WorkBuddy starts cbc with CODEBUDDY_FORCE_HEADLESS_BUNDLE=1 for workbuddy-server --serve and prewarm, and the HookExecutor runs inside that same headless bundle. Therefore a real WorkBuddy desktop hook can also have node .../bin/codebuddy in its ancestor chain and this predicate returns true, routing the IDE back to the CLI-only updatedToolOutput path. The new IDE test avoids this by constructing a tree without the WorkBuddy sidecar, so it does not reproduce the product architecture. Official package/install reference: https://www.codebuddy.ai/docs/cli/quickstart ; WorkBuddy Host CLI reference: https://www.codebuddy.cn/docs/workbuddy/Changelog

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(fbfec04)。本轮直接解包了官方 @tencent-ai/codebuddy-code@2.137.1 产物逐条核实:

核实结果(官方产物):

  • bin/codebuddy 入口注释与代码确认:宿主进程(WorkBuddy 经 workbuddy-server 起 sidecar、--prewarm 起预热池)在 spawn cbc 前设置 CODEBUDDY_FORCE_HEADLESS_BUNDLE=1;入口按 toLowerCase() === '1' || === 'true' 判定,其他值忽略。
  • bundle 的 HookExecutor(buildHookSpawnOptions)以 {...process.env, ...preparedEnv} spawn hook——hook 继承 bundle 的完整进程环境,因此该变量会传导到 hook,与意见描述的真实桌面端架构一致。
  • 另外发现 package.json 的 bin 条目为 codebuddy / codebuddy-code / cbc 三个名字指向同一入口,祖先匹配一并扩展。

修复_workbuddy_cli_host() 改为双信号组合判定——

  1. 祖先链中存在 CLI 二进制(三个 bin 名字均匹配,含 node/解释器启动形态);
  2. 且 hook 环境中 CODEBUDDY_FORCE_HEADLESS_BUNDLE 未置位(按入口脚本同款语义解析:1/true 任意大小写视为置位,其余值忽略)。

桌面端 sidecar/prewarm 场景(祖先含 node .../bin/codebuddy 但变量置位)现在正确判为非 CLI。判定逻辑 best-effort、fail-safe:无法遍历或信号矛盾时一律走非 CLI 的 attribution-only 路径。

测试:新增 e2e 用例完整复刻真实产品架构——把 hook 挂在名为 codebuddy 的真实祖先进程下 设置 CODEBUDDY_FORCE_HEADLESS_BUNDLE=1CODEBUDDY_PROJECT_DIR,断言原样透传、无 updatedToolOutput、压缩子进程从未运行;单测覆盖变量解析(1/true/TRUE → 非 CLI;0/其他值 → 忽略)与三个 bin 名字的 argv 形态。tests/test_compress_response_hook.py 25/25(Python 3.10 / 3.13),并用真实 tokenless 二进制做了 CLI/sidecar 两形态的端到端复核(见 PR body 追加的测试报告)。

遗留说明:Enterprise 宿主的进程模型无公开产物可查证;若其同样以 headless bundle 契约托管 hook(与桌面端同源),该变量同样生效;若未来有真实 Enterprise 证据,可再行校准。

# shrinking it. Non-CLI workbuddy hosts therefore fail open:
# compression is disabled, and only genuinely additive environment
# attribution (if any) is delivered.
if agent_id == _WORKBUDDY_AGENT_ID:

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.

[P2] Classify the host before running response compression. This branch disables delivery for non-CLI WorkBuddy hosts, but it is reached only after compress-response and compress-toon have already run. compress-response opens the stash by default and records compression stats (tokenless-cli/src/main.rs lines 593-666), so every compressible IDE/Enterprise result can create savings records and stash entries even though this branch discards the candidate; it also adds avoidable subprocess latency to every such tool call. The current test asserts only that the final hook output is {} and does not verify that the compressor was never invoked.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(fbfec04)。

修复:workbuddy 宿主分类从分发阶段(压缩与 TOON 子进程之后)前移到压缩之前——现在紧跟小响应/跳过工具判定之后(步骤 14b):非 CLI workbuddy 宿主在 compress-response / compress-toon 任何子进程运行之前就 warn 并 fail-open。由此:

  • 不再为最终被丢弃的候选产生 savings 统计与 stash 条目(compress-response 默认开启的副作用);
  • 每次此类工具调用省去两次压缩子进程的延迟;
  • 环境归因诊断不受影响——它由进程内的 classify_env_error 计算,仍在透传时交付。

测试:mock tokenless 增加了调用记录能力(经环境变量指定日志路径),三个非 CLI workbuddy e2e 用例(IDE 形态、桌面 sidecar 形态、环境错误归因形态)均断言调用日志不存在——即压缩器从未被调用;CLI 路径用例反向断言 compress-response 确在日志中,确保前移没有误伤正常路径。tests/test_compress_response_hook.py 25/25(Python 3.10 / 3.13),另有真实 tokenless 二进制的端到端复核(sidecar 形态输出 {} 且无压缩动作,见 PR body 追加的测试报告)。

@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.

One blocking compatibility issue remains. The new launcher marker correctly handles current WorkBuddy sidecars, and host classification now happens before compression, but the marker is not available across the declared >=1.16.0 range. The inline finding documents the affected official package versions and the required fail-safe behavior. I rechecked this exact head; CI and the targeted tests are green, but the tests cover only marker-present sidecars.

CodeBuddy Code v1.16.0+, and that same contract defines
``updatedToolOutput``.
"""
if _launched_by_workbuddy_host():

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.

[P1] Do not infer a standalone CLI from an absent launcher marker across the declared support range. I unpacked the official @tencent-ai/codebuddy-code stable tarballs: CODEBUDDY_FORCE_HEADLESS_BUNDLE is absent in 1.16.0, 2.50.0, 2.100.0, 2.125.5, 2.132.0, 2.133.1, 2.134.0, and 2.135.0; it first appears in 2.136.0. The manifest still declares workbuddy >=1.16.0, and those earlier artifacts already contain the hosted --serve/--prewarm modes. On a supported desktop host without this marker, a cbc/codebuddy ancestor makes this function return true, so the IDE is routed to the CLI-only updatedToolOutput path and compression stats/stash side effects run again. The new sidecar test only covers marker=1. Please recognize legacy hosted argv/process shapes (for example --serve/--prewarm), or enforce >=2.136.0 in the actual detect/install path; a missing marker must remain unknown/non-CLI rather than prove standalone. Official package history: https://www.npmjs.com/package/%40tencent-ai/codebuddy-code?activeTab=versions

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(c42ee1d)。本轮先独立解包官方产物逐版本核验,意见完全成立:

核验结果(官方 tarball):

  • marker 引入点复现:CODEBUDDY_FORCE_HEADLESS_BUNDLE 在 2.135.0 的 bin/codebuddy 中出现 0 次、2.136.0 出现 2 次,与 reviewer 的版本扫描一致;
  • 2.135.0(pre-marker)已含托管形态:--serve sidecar(含 --teammate-mode 的 detached teammate 后端)、--prewarm / --prewarm-force 预热池,以及 CODEBUDDY_SESSION_KIND 会话类型声明(缺省 unset / interactive;teammate / daemon 等为非交互类型);
  • 2.135.0 与 2.136.0 的 hook 均以 {...process.env, ...preparedEnv} spawn,两个时代的环境继承一致。

修复:判定改为多信号组合,且每个信号都向非 CLI 方向保守失败——

  1. marker(>=2.136.0)或声明为非交互的 CODEBUDDY_SESSION_KIND → 托管;
  2. CLI 二进制祖先携带托管 sidecar 标志(--serve / --prewarm / --prewarm-force / --teammate-mode)→ 托管,与 marker、终端无关,覆盖 pre-marker 产物;
  3. 其余情况下,CLI 二进制祖先必须挂接控制终端——这是交互式终端会话的正向证据;终端状态无法确定时一律按非 CLI。marker 缺失不再构成任何正向证据。

关于另一个备选方案:未采用"detect/install 强制 >=2.136.0",原因是版本门槛无法阻止旧版桌面宿主加载 hook(settings.json 是共享集成面,hooks 仍会在旧版宿主上执行),单独提高版本声明防不住 legacy 桌面误路由;而 1.16.0–2.135.x 的独立 CLI 契约确实支持 updatedToolOutput,降版本会无故剥夺这些用户的压缩能力。legacy 托管形态识别 + 控制终端正向证据可以同时覆盖两个时代。

测试(25 → 29 例,py3.10 / 3.13 全绿):

  • CLI e2e 改为在真实 pty 会话中运行(真实 codebuddy 祖先 + 真实控制终端),不再依赖测试运行器自身的终端状态;
  • 新增 legacy 桌面 sidecar e2e:codebuddy 祖先 + --serve存在控制终端 → 仍原样透传,压缩子进程从未被调用(调用日志断言);
  • 新增单测:会话类型(daemon/teammate → 非 CLI;interactive/unset → 继续判定)、4 个托管 argv 标志(带终端亦判非 CLI)、终端要求(False / 无法确定 → 非 CLI)。

另用真实 pty 会话端到端复核了三种形态(交互 CLI 替换生效、legacy --serve 透传且未压缩、marker sidecar 透传且未压缩),详见 PR body 追加的测试报告。

遗留说明:对已枚举信号之外的假设宿主形态(如未来 Enterprise 的未知进程形态),判定一律 fail-open 不压缩,契约安全。

@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.

Re-reviewed current head c42ee1d. The legacy pre-marker WorkBuddy sidecar misclassification is fixed, but one P1 compatibility blocker remains: requiring a controlling terminal disables response compression for official standalone headless CLI invocations (codebuddy -p) when they run from CI, automation, or stdin without a TTY. The official CLI hook contract supports updatedToolOutput in this mode. A no-PTY process-shape reproduction returned {} and never invoked the compressor, while the new e2e test covers only an interactive PTY. Requesting changes until supported standalone headless CLI shapes are preserved without reopening hosted WorkBuddy misrouting, or the declared capability/support scope is narrowed accordingly.

continue
if _argv_is_hosted_shape(argv):
return False
return has_tty is True

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.

[P1] Do not require a controlling terminal for every standalone CodeBuddy CLI. The official Headless Mode documents codebuddy -p for CI/CD, automation scripts, and stdin pipelines, where the process legitimately has no controlling TTY; the CLI Hooks contract still supports updatedToolOutput for compressing tool results. I reproduced that process shape with a real codebuddy --print ancestor and no PTY: this return is false, the hook emits {}, and compress-response is never called. The current e2e test only exercises an interactive PTY, so it locks out this supported standalone mode. Please distinguish supported headless CLI shapes from hosted WorkBuddy sidecars, or narrow the advertised capability/support scope. References: https://www.codebuddy.ai/docs/cli/headless and https://www.codebuddy.ai/docs/cli/hooks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(a3d0db5)。

确认问题成立:官方 Headless Mode 文档明确 codebuddy -p / --print 用于 CI/CD、自动化脚本与 stdin 管道,此类进程合法地没有控制终端;CLI Hooks 参考对 PostToolUse 的 updatedToolOutput(替换工具结果、面向"压缩冗长工具输出"的典型用途)并未以交互式为前提。旧判定把"CLI 二进制祖先挂接控制终端"当作 standalone 的正向证据,导致所有受支持的 headless 形态被误判为非 CLI。

复现:以真实 codebuddy --print 祖先进程、无 PTY 的进程树跑上一版 hook——判定返回 false,输出 {}compress-response 从未被调用,与意见描述一致。

修复:不再要求控制终端,改为以"托管证据"排除宿主——CLI 二进制祖先在不含任何托管信号时即判为 standalone(无论是否有 TTY)。托管信号保持为三者,均向非 CLI 方向保守失败:

  1. CODEBUDDY_FORCE_HEADLESS_BUNDLE launcher marker(2.136.0+,宿主 spawn sidecar/prewarm 前设置);
  2. 声明为非交互的 CODEBUDDY_SESSION_KIND(teammate / daemon / bg 等;standalone headless 会话缺省为 unset / interactive,不受影响);
  3. 托管 sidecar argv 标志(--serve / --prewarm / --prewarm-force / --teammate-mode,早于 marker 的产物即已存在)。

官方 bin/codebuddy 入口注释明确 WorkBuddy 以 --serve 起 sidecar、--prewarm 起预热池,因此每个已知托管形态至少命中其一;而受支持的 headless 形态(-p / --print / --acp / --bg / daemon)均不携带任何托管信号。同时移除了不再参与判定的 tty_nr / ps tty 列采集。这样既放开了受支持的 headless CLI,又没有重新引入 hosted WorkBuddy 的误路由。

测试(30 例全绿):

  • 新增 e2e:复刻意见所述形态——真实 codebuddy --print 祖先、无 PTY,判定为 CLI,updatedToolOutput 替换生效且 compress-response 被调用;
  • 单测以 -p / --print / --acp / daemon / --bg 等 headless argv 形态替换原"要求控制终端"用例;
  • 交互式 pty CLI、pre-marker --serve sidecar、桌面端 marker sidecar、IDE、环境归因等既有 e2e 全部保持通过,确认各托管形态无回归。
  • 真实进程树端到端复核五种形态:headless --print(无 PTY)→ 替换且压缩执行;交互 CLI → 替换;--serve sidecar / marker=1 / session_kind=teammate → 均透传 {} 且压缩子进程从未调用。

回归test_rewrite_hook.py 17/17、test-posttooluse-replacement.sh 15/15、test-workbuddy-adapter-install.sh 15/15、schema / resolve-agent-id / agentscope(py3.13)通过。test_hermes_plugin_import.py 一例失败为本机存在系统级安装遮蔽测试夹具所致,在未含本次改动的干净工作树上复现一致,与本改动无关。

@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.

Re-reviewed current head a3d0db59. The ordinary no-TTY codebuddy -p path is fixed, but one P1 compatibility blocker remains: real codebuddy --bg sessions are restarted as --print -y with CODEBUDDY_SESSION_KIND=bg, and that inherited session kind still short-circuits the CLI classifier before compression. A current-head reproduction returns {} and never invokes the compressor, while the new --bg unit shape clears the real launcher variable. The bilingual WorkBuddy guide also still documents the removed controlling-terminal requirement. Requesting changes until the real background CLI launcher shape is covered and the user-facing contract is synchronized.

"""
if _launched_by_workbuddy_host():
return False
if _session_kind_is_hosted():

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.

[P1] Do not classify the documented bg worker kind as a non-CLI host. Official CodeBuddy background sessions are first-class CLI tasks: codebuddy --bg is restarted as --print -y and sets CODEBUDDY_SESSION_KIND=bg; the published 2.137.1 bundle then inherits that environment into hook processes. This branch therefore makes a real background CLI return {} before compression, even though the Hooks contract supports updatedToolOutput. I reproduced exactly that shape: unset kind compressed and replaced, while kind=bg skipped and never called the compressor. The new --bg unit case misses this by forcing the kind to empty. References: https://www.codebuddy.ai/docs/cli/daemon and https://www.codebuddy.ai/docs/cli/hooks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(ea5fbbf)。

确认问题成立:官方 Daemon Mode 参考明确把 CODEBUDDY_SESSION_KIND 记载为 worker 类型(interactive / bg / daemon),且 bundle 会为自己的第一类 worker 设置它——bg 对应 codebuddy --bg(以 --print -y fork)、daemon 对应 daemon 子进程、teammate 对应 team sidecar。旧实现把任何非 interactive 的 kind 都当作宿主持有的正向证据,导致真实的 --bg 后台 CLI 被误判为 WorkBuddy 宿主、在压缩前返回 {}——而 CLI Hooks 契约支持 updatedToolOutput

复现:按意见步骤用真实 codebuddy --print -y 祖先进程、设置 CODEBUDDY_SESSION_KIND=bg 实测——修复前返回 {}、压缩器从未被调用;修复后正常输出 updatedToolOutput 并调用压缩器。

修复:从 _workbuddy_cli_host() 移除 session-kind 信号。WorkBuddy 宿主识别只保留两个真正由 WorkBuddy 宿主设置的信号:

  • CODEBUDDY_FORCE_HEADLESS_BUNDLE launcher marker(WorkBuddy 宿主自 2.136.0 起设置);
  • 托管 sidecar argv 标志(--serve / --prewarm / --prewarm-force / --teammate-mode)。

bg / daemon / teammate 等 session kind 都是独立 CLI 的 worker 进程,不再作为宿主证据。同时验证修复不影响宿主识别:--serve / --prewarm 祖先进程与 marker=1 仍返回 {}、压缩器不被调用。

测试:将 test_session_kind_disables_cli_detection 重写为 test_session_kind_never_disables_cli_detection(断言各 worker kind 不再禁用 CLI 检测),新增单测 test_bg_session_kind_is_standalone_cli 与 e2e test_workbuddy_bg_session_uses_updated_tool_output——复刻真实 --bg 会话形态(CODEBUDDY_SESSION_KIND=bg--print -y、无 TTY),断言压缩执行并替换工具结果。test_compress_response_hook.py 40/40 在 py3.10 / py3.13 均通过。


User-configured hooks and every other settings key are preserved; the uninstall script removes only the Tokenless-owned entries. Both scripts rewrite `settings.json` through a temporary file and never loosen the existing file mode, because the `.codebuddy` home may carry credentials (`settings.json.env` officially supports `CODEBUDDY_API_KEY` and auth tokens); a newly created `settings.json` defaults to `0600`.

The rewrite hook emits WorkBuddy's `modifiedInput` partial field override together with `permissionDecision: "allow"`, which the official PreToolUse contract requires for parameter changes to take effect (the contract's troubleshooting guidance says the tool keeps its original parameters under any other decision). Because `allow` bypasses the host permission prompt, the hook only emits it when rtk's own permission rules approved the rewrite (rtk Allow verdict). rtk v0.43 exits 3 for every rewrite its permission rules have not attested — including ordinary Default commands, its normal case — because rtk groups Default with Ask on purpose so unattested commands never auto-allow. The WorkBuddy contract cannot combine `modifiedInput` with a confirmation prompt (`ask` would silently drop the change), so for these verdicts the hook passes the original command through unchanged and keeps the host's normal permission flow. Users who accept running unattested rewrites without the host confirmation can set `TOKENLESS_WORKBUDDY_AUTO_ALLOW=1`; the hook then emits `allow` for these rewrites too, and the decision reason records the bypass. Response compression replaces the tool result via `updatedToolOutput` when the hook runs under the CodeBuddy Code CLI. The CLI host is recognized through multi-signal classification in which every signal fails safe to the non-CLI path: `CODEBUDDY_FORCE_HEADLESS_BUNDLE` (set by WorkBuddy hosts before spawning the headless bundle since CLI 2.136.0) and any declared non-`interactive` `CODEBUDDY_SESSION_KIND` are positive hosted evidence; a CLI-binary ancestor (`codebuddy` / `codebuddy-code` / `cbc`) carrying a hosted sidecar flag (`--serve` / `--prewarm` / `--teammate-mode`) is a spawned headless process even in packages predating the marker; otherwise the CLI-binary ancestor must be attached to a controlling terminal — the positive evidence of an interactive terminal session. An absent marker is never treated as proof of a standalone CLI: it only exists from CLI 2.136.0 on, while the declared support range starts at 1.16.0 and the earlier artifacts already ship the hosted modes. `CODEBUDDY_PROJECT_DIR` cannot discriminate either because the IDE Hooks reference documents it for IDE hook scripts too (CLI hooks require CodeBuddy Code v1.16.0 or newer). The host is classified before any compression runs, so non-CLI hosts pay no compression latency and create no compression statistics or stash entries. The IDE and Enterprise surfaces document only the additive `additionalContext` for PostToolUse, which keeps the original tool result; compressing through it would grow the context instead of shrinking it, so on these hosts compression is disabled and only genuinely additive environment attribution is delivered. Restart WorkBuddy/CodeBuddy after installing or removing; the CodeBuddy CLI `/hooks` panel may ask you to review externally added hooks before they take effect.

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.

[P2] Synchronize both WorkBuddy guides with this commit. The current English paragraph and the matching Chinese paragraph still say a CLI ancestor must have a controlling terminal, but a3d0db59 deliberately removed that requirement to support headless -p. Leaving the old contract in the user guide makes the documented behavior contradict the implementation; update both language versions together.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳,已修复(ea5fbbf)。

已同步更新中英文 docs/user-guide/{en,zh}/token-saving/tokenless/framework-integration.md 的 WorkBuddy 章节,删除两处过时表述:

  • 「CLI 二进制祖先必须挂接在控制终端上(交互式终端会话的正向证据)」——该要求已在上一轮为支持 headless -p 而移除,文档与实现矛盾;
  • 「任何非 interactiveCODEBUDDY_SESSION_KIND 声明都是宿主持有的正向证据」——与本轮 P1 一并修正,session kind 是 CLI worker 类型、并非宿主信号。

更新后的文档表述:宿主识别仅依据 CODEBUDDY_FORCE_HEADLESS_BUNDLE marker 与托管 sidecar argv 标志(--serve / --prewarm / --prewarm-force / --teammate-mode);不携带这些托管信号的 CLI 二进制祖先即为独立 CLI,刻意不要求控制终端——受支持的 headless 形态(用于 CI/CD 与 stdin 管道的 -p / --print,以及 --acp--bgdaemon)本来就没有 TTY,CLI Hooks 契约在这些场景下同样支持 updatedToolOutput。并明确 CODEBUDDY_SESSION_KIND 的 worker 类型(interactive / bg / daemon)都是独立 CLI 的工作进程,绝不作为宿主证据。

Forrest-ly and others added 10 commits August 22, 2026 16:46
WorkBuddy desktop, CodeBuddy Code CLI and WorkBuddy Enterprise share the
.codebuddy settings protocol (Claude Code-shaped hooks key) but expose no
plugin system, so the adapter merges the tokenless hook groups into the
user-level ~/.codebuddy/settings.json:

- hooks.json template with Bash rewrite matcher, catch-all tool-ready
  group, and PostToolUse compression group; install.sh stamps the absolute
  adapter directory because WorkBuddy has no plugin-root substitution
- idempotent merge preserving user-configured hooks and every other
  settings key; uninstall.sh removes only tokenless-owned entries
  (TOKENLESS_AGENT_ID=workbuddy marker); graceful no-op without .codebuddy
- rewrite_hook.py emits WorkBuddy's modifiedInput partial field override
  (its PreToolUse input-rewrite field) in addition to the existing formats
- response compression stays on the additionalContext path per the issue
  scope; full updatedToolOutput replacement is a documented follow-up
- detect.sh tri-state report, run-hook.sh shared dispatcher symlink
- Makefile workbuddy-install/workbuddy-uninstall targets and test wiring
- raw packager materializes the run-hook symlink; RPM packages the adapter
- bilingual docs and changelog entries

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
…hangelog entry

Address bot review feedback on the WorkBuddy adapter PR:

- framework-integration.md (en/zh): document detect.sh tri-state exit
  semantics (0 ready / 1 not installed / 2 missing prerequisites), the
  'missing ~/.codebuddy counts as not installed' decision, and that a
  missing tokenless/rtk binary is treated as a missing prerequisite in
  this release; note install.sh no-ops gracefully on the same condition
- framework-integration.md (en/zh): note that RPM %preun runs as root
  and only cleans root's ~/.codebuddy/settings.json, so non-root users
  on multi-user hosts must run uninstall.sh themselves
- CHANGELOG.md / CHANGELOG_zh.md: drop the Unreleased WorkBuddy entry;
  documentation-standard.md reserves CHANGELOG edits for release
  version bump PRs

Co-authored-by: multica-agent <github@multica.ai>
…dling

Address human review feedback on PR alibaba#2532:

- install.sh/uninstall.sh: preserve settings.json mode and ownership
  across the tmp-file rewrite (create temp 0600, restore existing mode;
  new files start at 0600). CodeBuddy documents credentials in the
  settings env field, so the merge must never widen permissions.
- rewrite_hook.py: emit permissionDecision alongside modifiedInput as
  required by the official WorkBuddy/CodeBuddy PreToolUse contract,
  mapped from rtk's own verdict (allow only for rtk Allow, ask for rtk
  Ask) so the hook never bypasses the host permission system for a
  command rtk would deny or ask for.
- compress_response_hook.py: move workbuddy to the updatedToolOutput
  replacement path (CodeBuddy Code v1.16+ contract: full replacement
  for all built-in and MCP tools); hosts without the field ignore it,
  so compression no-ops instead of appending a duplicate payload.
- component.toml.in: add the workbuddy [[adapters]] entry so raw
  installs lay the lifecycle scripts the user guide references.
- manifest.json.in: declare >=1.16.0 for workbuddy, matching the
  documented hook contract version.
- docs: refresh WorkBuddy section (integration wording, permissionDecision
  trade-off, replacement semantics, version requirement).
- tests: permission-preservation cases for install/uninstall, workbuddy
  decision-mapping cases for the rewrite hook, updatedToolOutput
  assertion for the compress hook.
…contracts

Address the P1 review findings on the WorkBuddy adapter:

- install.sh/uninstall.sh: rewrite settings.json through a unique 0600
  temp file and restore the existing mode before os.replace, so a 0600
  config can never loosen to 0644 under umask 022. The .codebuddy home
  can carry credentials (settings.json.env officially supports
  CODEBUDDY_API_KEY and auth tokens). New files are created 0600.
- rewrite_hook.py: emit modifiedInput together with
  permissionDecision "allow" — the official CLI/IDE/Enterprise hook
  references only apply modifiedInput as that pair. The permission
  bypass is limited to rtk's Allow verdict (exit 0); Ask-verdict
  (exit 3) rewrites are skipped so the host permission flow keeps
  applying to the original command.
- compress_response_hook.py: replace the tool result via
  updatedToolOutput when the hook runs under the CodeBuddy Code CLI
  (detected through CODEBUDDY_PROJECT_DIR; hooks exist only in
  v1.16.0+, the contract that defines updatedToolOutput).
  additionalContext would keep the original result and append,
  duplicating the payload. IDE/Enterprise hosts document only the
  additive additionalContext and stay on that path.
- manifest.json.in: workbuddy compatibleVersions ">=1.16.0" (first
  CodeBuddy CLI release with hook support).
- docs (en/zh): refresh the WorkBuddy section — the outdated
  "no plugin system" claim, the rewrite and compression contracts,
  and the raw-install staging scope.
- tests: settings.json mode preservation, workbuddy rewrite contract
  (allow pairing, Ask-verdict skip, non-workbuddy unaffected), CLI vs
  non-CLI compression paths; stage rewrite-test binaries in the
  highest-priority fallback slot so the suite stays deterministic on
  hosts carrying a system-wide rtk/tokenless.

Co-authored-by: multica-agent <github@multica.ai>
The workbuddy [[adapters]] entry added for the raw install path means
the component contract now lays the lifecycle scripts too; update the
WorkBuddy section wording accordingly (en/zh).

Co-authored-by: multica-agent <github@multica.ai>
Compress hook: CODEBUDDY_PROJECT_DIR cannot discriminate the CodeBuddy
Code CLI from the IDE, because the IDE Hooks reference lists that
variable for IDE hook scripts as well. Detect the CLI through process
ancestry (hooks run as descendants of the codebuddy binary) instead,
and fail open on non-CLI workbuddy hosts: their PostToolUse contracts
document only the additive additionalContext, which keeps the original
tool result, so routing the compressed payload through it would grow
the context instead of shrinking it. Genuinely additive environment
attribution is still delivered there.

Rewrite hook: WorkBuddy hosts apply modifiedInput only together with
permissionDecision allow (official PreToolUse contract), while rtk
v0.43 exits 3 for every rewrite its permission rules have not attested,
including ordinary Default commands. Mapping exit 3 to ask therefore
silently dropped every ordinary rewrite. The safe default now passes
the original command through (keeping the host's normal permission
flow); TOKENLESS_WORKBUDDY_AUTO_ALLOW=1 opts into emitting allow for
these rewrites, with the bypass recorded in the decision reason.
WorkBuddy desktop runs its agent inside an embedded headless CodeBuddy
Code bundle: the officially published CLI package's bin/codebuddy entry
documents that a host process sets CODEBUDDY_FORCE_HEADLESS_BUNDLE=1
before spawning cbc for its sidecar and prewarm pool, and the bundle's
hook executor merges its own process environment into the hook
environment. A desktop hook can therefore have the CLI binary among its
ancestors, so ancestry alone misroutes the IDE to the CLI-only
updatedToolOutput path. Combine the ancestry check (extended to the
package's three bin names: codebuddy, codebuddy-code, cbc) with the
absence of the launcher variable, parsed like the entry script
(1/true, any case; other values ignored).

Classify the host before any compressor subprocess runs: non-CLI
workbuddy hosts discard the candidate anyway, and compress-response
records savings statistics and writes stash entries by default, so the
late classification created stats/stash side effects and subprocess
latency for every compressible IDE/Enterprise tool call.

Tests: new e2e case reproducing the desktop architecture (codebuddy
ancestor + launcher variable -> pass-through, compressor never
invoked), call-log assertions proving the compressor does not run on
any non-CLI workbuddy path (and still runs on the CLI path), and unit
cases for the launcher-variable parsing and the new bin names.
CODEBUDDY_FORCE_HEADLESS_BUNDLE only exists from CLI 2.136.0 on, while
the declared workbuddy support range starts at 1.16.0, and the earlier
official artifacts already ship the hosted --serve/--prewarm modes. An
absent marker therefore proves nothing on a supported desktop host, and
ancestry alone routed the IDE back to the CLI-only updatedToolOutput
path.

Make the classification multi-signal, with every signal failing safe to
the non-CLI path:
- the launcher marker and any declared non-interactive
  CODEBUDDY_SESSION_KIND remain positive hosted evidence;
- a CLI-binary ancestor carrying a hosted sidecar flag (--serve,
  --prewarm, --prewarm-force, --teammate-mode) is a spawned headless
  process across all versions, marker or not;
- otherwise the CLI-binary ancestor must be attached to a controlling
  terminal — the positive evidence of an interactive terminal session;
  hosted bundles are headless, and an undetermined terminal state stays
  non-CLI.

Tests: the CLI e2e case now runs the hook under a real codebuddy
ancestor inside a fresh pty session (interactive shape, deterministic
regardless of the runner's terminal); a new e2e case reproduces the
pre-marker desktop sidecar (--serve argv with a controlling terminal
still passes through and never invokes the compressor); unit cases
cover session kinds, hosted argv flags, and the controlling-terminal
requirement (False/undetermined stay non-CLI).
…CodeBuddy CLI

The Headless Mode documents `codebuddy -p` / `--print` for CI/CD,
automation scripts and stdin pipelines — processes that legitimately
own no controlling terminal — and the CLI Hooks contract still honors
`updatedToolOutput` for compressing tool results there. The previous
multi-signal classifier gated the standalone verdict on the CLI-binary
ancestor being attached to a controlling terminal, so every supported
headless CLI session was routed to the non-CLI path: the hook emitted
`{}` and `compress-response` never ran.

Distinguish supported headless CLI shapes from hosted WorkBuddy
sidecars instead of demanding a terminal. A CLI-binary ancestor is a
standalone CLI unless it carries positive hosted evidence:
- the CODEBUDDY_FORCE_HEADLESS_BUNDLE launcher marker (2.136.0+),
- a declared non-interactive CODEBUDDY_SESSION_KIND (teammate / daemon
  / bg, ...), or
- a hosted sidecar argv flag (--serve / --prewarm / --prewarm-force /
  --teammate-mode), which predates the marker.

Every official hosted shape carries at least one of these (the entry
script documents WorkBuddy spawning sidecars with --serve and prewarm
pools with --prewarm), while standalone headless shapes (-p / --print /
--acp / --bg / daemon) carry none. Drop the tty_nr / ps tty column
collection, which no longer feeds any decision.

Tests: add an e2e case reproducing the reported shape (a real
`codebuddy --print` ancestor with no pty now selects the CLI path,
replaces the tool result and runs the compressor); replace the
controlling-terminal unit test with headless-shape cases (-p /
--print / --acp / daemon / --bg); keep the interactive-pty CLI,
legacy --serve sidecar, desktop-marker sidecar, IDE and env-attribution
e2e cases green so no hosted shape regresses.
@Forrest-ly
Forrest-ly force-pushed the feat/tokenless-workbuddy-adapter branch from a3d0db5 to 17d38a1 Compare August 22, 2026 09:06

@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.

Re-reviewed current rebased head 17d38a1f. The force-push preserves the existing P1 compatibility blocker: official codebuddy --bg sessions are restarted as --print -y with inherited CODEBUDDY_SESSION_KIND=bg, while the current classifier treats every non-interactive session kind as hosted. An exact current-head reproduction still returns {} and never invokes the compressor; the green --bg unit case clears the real launcher variable. The existing P1 thread remains current and unresolved: #2532 (comment). The bilingual WorkBuddy guide also retains the removed controlling-terminal requirement; its current unresolved thread is #2532 (comment). Targeted head and synthetic-merge tests are green, but they do not cover the real background launcher environment. Requesting changes until the background CLI path is preserved and both language guides match the implementation.

CODEBUDDY_SESSION_KIND was used as hosted evidence for any non-
interactive value, but the official Daemon Mode reference documents it
as the CLI worker type (interactive / bg / daemon) and the bundle sets
it for its own first-class workers — bg for `codebuddy --bg`, daemon
for the daemon child, teammate for team sidecars. A background CLI
session is a standalone CLI whose Hooks contract honors
updatedToolOutput, yet the old check routed it to the non-CLI path and
returned {} before compression.

Drop the session-kind signal from _workbuddy_cli_host(). Hosted
detection keeps the two real WorkBuddy signals: the
CODEBUDDY_FORCE_HEADLESS_BUNDLE launcher marker and the hosted sidecar
argv flags (--serve / --prewarm / --prewarm-force / --teammate-mode).

Sync the EN/ZH framework-integration guides, which still described the
removed controlling-terminal requirement and the session-kind signal.

Tests: replace the session-kind-disables test with one asserting the
worker kinds never disable CLI detection, add a unit case and an e2e
case reproducing the real `codebuddy --bg` shape (CODEBUDDY_SESSION_KIND=bg,
--print -y, no TTY) to confirm compression runs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:tokenless src/tokenless/ scope:documentation ./docs/|./*.md|./NOTICE

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants