feat: customization reference — multi-agent collaboration & external ACP integration - #2139
feat: customization reference — multi-agent collaboration & external ACP integration#21391688mengdie wants to merge 10 commits into
Conversation
Multi-agent collaboration and external ACP agent integration enhancements on top of upstream main (e640aa4). Highlights: - ACP tool family: session-level direct connection with external ACP agents (CodeBuddy, Claude Code, etc.) via SessionControl / SessionMessage tools, real-session creation and lifecycle bridge - Warden guard system: failure-streak governance with scene fingerprinting, LLM judgement port, goal linkage and reference file following - RBAC subagent roles: role pinning for subagent-marked sessions, GeneralPurpose-specific template, K8 template batch (Executor read access) - Session/SessionControl enhancements: compact action, model_id, short names, ghost-deletion fix (created_by + owner/ancestor fallback), ghost-delivery fix - Engine/injection: runtime facts per-round refresh, user-context once-per-generation injection, context-usage display persistence - Legion/task/plan toolchain: legion control, task dual lifecycle with ACP support, plan tool family, goal dual-trigger - CodeBuddy provider: local gateway adapter (ApiFormat::CodeBuddy, Runs two-step streaming), empty finish_reason protection - Web UI: flow-chat session/turn display, legion pages, model switching UI - Notify-style injection: minimal metadata for background task completion (P-19 family), full content retrievable via SessionHistory
…ce (keep Warden/RBAC prepended_reminders via four-principles)
…ase to 98fc825, add taiji roadmap section, fix SessionMetadata init, drop upstream restrict test
There was a problem hiding this comment.
感谢这份详尽的定制参考快照,CUSTOMIZATIONS.md 写得很完整,ACP 接入与多智能体协作的设计思路也很清晰。以下把每个问题展开供逐项核对。
一、合入前必须解决的阻塞项
1. src/apps/cli/src/self_update.rs 编译破坏(Critical)
问题
本 PR 删除了 self_update.rs 中的两个函数 find_package_dir、validate_entrypoint_pair、常量 DEPRECATION_WARNING,以及三个导入 use flate2::read::GzDecoder、use tar::Archive、use std::io::Cursor。文件顶部现在只剩 9 行 import,不再包含 flate2/tar/Cursor。但 #[cfg(unix)] fn install_archive(archive: &[u8], current_exe: &Path)(约 936-937 行)内部仍调用这些已删除的符号:
Archive::new(GzDecoder::new(Cursor::new(archive))).unpack(...)(约 957-959 行)let package_dir = find_package_dir(extract_dir.path())?;(约 960 行)validate_entrypoint_pair(&new_primary, &new_legacy)?;(约 963 行)validate_entrypoint_pair(&staged_primary, &staged_legacy)?;(约 980 行)if let Err(error) = validate_entrypoint_pair(current_exe, &legacy_target)(约 1036 行)
全仓 grep 已确认 fn find_package_dir / fn validate_entrypoint_pair 零定义,只有调用点残留。
风险
- Linux/macOS 上
cargo build -p bitfun-cli(以及任何含该 crate 的 workspace 构建)会因未定义符号直接编译失败,属于打包级破坏。 - 同时丢失了一个安全防线:原
validate_entrypoint_pair会在覆盖安装前运行bitfun --version与bitfun-cli --version,并断言旧bitfun-cli的 stderr 打印DEPRECATION_WARNING,确认新归档入口对可用后才替换。删掉后等于放弃了"交换前验证入口对"的最后防线。 flate2/tar依赖仍在src/apps/cli/Cargo.toml(约 105/113 行),变成未被引用的死依赖。- 该改动与"多智能体/ACP"主题完全无关,属越界修改。
建议方案
- 回填被删函数定义与导入;或完整移除残留调用点并统一处理依赖。
- 在 CI 中实证
cargo build -p bitfun-cli在 Linux 上通过。 - 将该改动从本 PR 拆出单独处理。
2. CodeBuddy provider 后端不识别 api_format: "codebuddy"(High)
问题
src/shared/ai-provider-catalog/providers.json(约 299-323 行)仍提交了一个codebuddyprovider:id: "codebuddy"、base_url: "http://127.0.0.1:8080"、api_format: "codebuddy"、curated_models: ["codebuddy"]。- 后端
ApiFormat::parse(src/crates/adapters/ai-adapters/src/client/format.rs:16-27)只识别openai / responses / anthropic / gemini / gemini-code-assist,对 "codebuddy" 走兜底分支返回Err("Unknown API format: codebuddy")。 - 前端仍把它当合法格式暴露:
src/web-ui/src/infrastructure/config/services/builtinProviderCatalog.ts:29(isApiFormat白名单含 'codebuddy')、src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx:439({ label: 'CodeBuddy (本地网关 /runs)', value: 'codebuddy' })、src/web-ui/src/shared/types/chat.ts:14(ApiFormat联合类型含 'codebuddy')。 src/crates/adapters/ai-adapters/src/client/的send_message_stream*与validate_reasoning_preset都会调用ApiFormat::parse,因此发送必然失败。
风险
- 用户在前端选中该 provider 后发送必然报错,功能不可用。
- 与
CUSTOMIZATIONS.md§10 直接矛盾:报告声称"Plan A 的所有残留(provider 目录项、UI 格式选项、类型联合、/runs 网关)已全部删除、零代码复用 OpenAI 传输",但源码里仍保留完整的 Plan A 残留,且描述中"本地网关"恰恰是报告说要移除的东西。
建议方案
二选一:
- 若按报告意图"复用 OpenAI 传输、零代码":把
providers.json该条目的api_format改为"openai",并从builtinProviderCatalog.ts、AIModelConfig.tsx、chat.ts删除codebuddy格式选项与硬编码中文; - 若确需独立格式:在后端
format.rs增加"codebuddy" → OpenAIChat映射,补resolve_request_url的codebuddy分支,并加契约测试。 - 无论哪种,保证前后端对该格式认知一致,并补"选中该 provider 能真实发消息"的验证。
3. RBAC 默认开启且主会话被钉为 Commander,锁死主 Agent 核心工具(High)
问题
rbac_enabled默认true:src/crates/assembly/core/src/service/config/global.rs:28(static RBAC_ENABLED_CACHE: AtomicBool = AtomicBool::new(true))、src/crates/assembly/core/src/service/config/types.rs:829(#[serde(default = "default_true")])、types.rs:1855(AIConfig::default()里rbac_enabled: true)。- 主会话(无 creator、非 subagent)经
ConversationCoordinator::resolve_session_role(src/crates/assembly/core/src/agentic/coordination/coordinator.rs:2634-2643)解析为AgentRole::Commander,并在建会话时register_session_role(coordinator.rs 约 2855 / 3659)写入。 - Commander 模板(
src/crates/assembly/core/src/agentic/tools/restrictions.rs约 77-99):allowed_operation_classes = {ReadOnly, Communicate},allowed_tool_names = {Write, SessionControl, SessionMessage, SessionHistory, acp_control, acp_message, acp_history}。 - 管线在
rbac_enabled()为真时用effective_runtime_tool_restrictions(session_id, ...)(src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs:96-101, 1490-1508, 2171-2178),优先取get_session_restrictions(session_id),对不符合的validate_tool_execution_admission直接continue(跳过该工具)。 - 全仓没有任何生产配置把
rbac_enabled关掉(仅tests/rbac_master_switch.rs调用set_rbac_enabled(false))。
风险
- 默认配置下,主对话 Agent 的
Edit(WriteFile)、Delete(DeleteFile)、ExecCommand(ExecuteCode)、WebSearch/Skill/Task(ExecuteCode) 全部被拒,主流程严重回归。 - 这是对通用产品主流程的行为变更,且"默认开启 + 主会话 Commander"就是上线实际行为。
建议方案
- 默认关闭 RBAC,或对主/primary 会话不套用 Commander 模板;
- 若确需默认开启,显式声明
ai.rbac_enabled语义并补"主会话核心工具可用"的端到端测试; - 明确 Commander(编排专用角色)与"普通用户主对话"是否应是同一角色,避免误扣。
二、高风险(建议一并处理)
4. ACP 会话删除/取消授权缺口
问题
acp_control工具的delete/cancel(src/crates/assembly/core/src/agentic/tools/implementations/acp_tools.rs约 185-225)直接调port.delete_session_record()/port.cancel_session(),没有 SessionControl 删除路径里的created_by_match/ 祖先授权 / daemon 保护检查。ghost_acp_delete_authorized(session_control_tool.rs约 373-375)对"is_acp_flow_session_id且 metadata 无created_by"直接放行;is_acp_flow_session_id(约 361-365)只做acp_前缀判断,不校验尾部 uuid。- 而 ACP 流会话按设计一律没有
created_by。
风险
- "acp_ 前缀 + 无 created_by"对所有 ACP 流会话都成立 → 任意能调用工具的调用者(含被降权的子 agent)都可能删除/取消任意 ACP 流会话,绕过 RBAC 归属模型。
- 与 SessionMessage 的 ACP 直通路径不对称:直通前有持久化注册表权威校验(COORD-03),而 delete 路径完全没有注册表核验。
建议方案
- 让
acp_control delete/cancel复用 SessionControl 的归属/祖先授权检查; ghost_acp_delete_authorized接入 ACP 注册表校验,确认 id 确为 ACP 流会话且归属当前 workspace 后再放行;is_acp_flow_session_id与桌面端client_id_from_session_id统一为严格 uuid 形状校验。
5. SessionMessage 对隐藏会话投递无授权门
问题
dispatch_single用include_hidden: true解析目标 agent_type(session_message_tool.rs约 1988),随后直接submit_dialog_turn(约 2225-2246),无 owner/ancestor 校验,也未像 SessionControl 的list那样过滤 daemon/warden 会话。- SessionControl
list(约 1509)虽用include_hidden: true,但过滤了 daemon/warden;SessionMessage 投递路径没有对等过滤。
风险
- 任何知道/枚举到 session_id 的调用者都能向任意隐藏子代理甚至 daemon 会话投递消息,打破"隐藏即不可达"的隔离,无授权即可跨会话投递,权限面扩大。
建议方案
- 为投递增加与 cancel/delete 对齐的 owner/ancestor 门,并补 daemon/warden 拒绝;
- 或将工具明确定位为"可信调用者"并限制其 default_exposure。
6. 用户上下文"每代注入一次"(行为回归,需设计确认)
问题
round_dynamic_reminders(execution_engine.rs约 1541-1567)把 user_context 放入dynamic_ordered_reminders,每次发送仅当injected_generation != 当前 generation时追加,随后记录注入世代。- generation 只在
create_session与压缩(invalidate_prompt_cache(..., PromptCacheScope::All),execution_engine.rs 约 2827/3112)时递增。 - user context 是发送期临时追加到 AI messages 末尾(
build_ai_messages_for_send约 1977-1979),不写入持久化messages历史。
风险
- 同一会话第 2+ turn,以及同一 turn 内的工具轮(round≥1),模型请求里看不到 workspace root、相关路径、项目布局、记忆摘要等关键上下文 → 多轮执行与长会话质量明显回归。
- 现有测试只覆盖"每代一次"的注入频率(
round_dynamic_reminders_injects_user_context_once_per_cache_generation),未验证后续轮模型是否仍能看到 user context。
建议方案
- 确认预期语义:若目标是"每个 turn 首轮注入一次",改为按 turn 重置注入标记或每 turn 首轮重新注入;
- 若确为"每代一次",将 user context 作为持久消息写入历史(或纳入 static/前缀缓存);
- 补一条"多轮/工具轮后模型仍能看到 user context"的测试。
7. Warden"文档承诺但运行时未接线"
问题与风险
- shame-wall 持久化未接线:生产路径用
WardenRuntime::new(...)(warden/runtime.rs内存态、shame_wall_path: None;scheduler.rs约 550-552),with_shame_wall_path(runtime.rs 约 185)只在测试(runtime.rs 约 1020/1035/1049)被调用 → 进程退出即丢全部 violation 记录,"可审计 registry"不成立。 - 跨会话 L4 结构性不可达:
ViolationPolicy::level_for最多返回 L1/L2/L3,运行时从不跨会话累计违规算 L4,文档承诺的逐级升级线上走不到。 - Poke deadline/defer 执法未接线:
PokePriorityManager(warden/mod.rs)的register_poke/is_timeout/unregister_poke/track_defer(对应 3-turn/5-turn 截止、最多 3 次 defer)只在测试中出现,运行时从不调用 → 响应超时/无限制 defer 无法被检测。 - SKILL.md 与实现矛盾:
warden/SKILL.md:67-69承诺 L2 RBAC 降级、L3 冻结、L4 永久标记,而punishment_executor.rs是 R-25 纯提醒(rbac_change=None、session_frozen=false、permanent_mark=false)。 - 成本顾虑:默认开启时,无模型判断端口的情况下,每次破坏性调用(WriteFile/DeleteFile/ExecuteCode)都无条件追加 Audit-Poke(
tool_pipeline.rs约 1308-1310),外加约每 6.5 turn 一次的 Challenge-Poke → 长会话 token/上下文污染 + 逐次模型调用开销,接近"按固定节奏冲击 agent loop"的反模式(AGENTS.md 明确反对)。 - 其他小问题:
notify_user(L3/L4)是 no-op(仅warn!日志,runtime.rs 约 497-502);PoissonScheduler(poisson.rs约 80-81)rate 无上界/除零校验(rate=0 → 恒 poke);verify_warden_session(punishment_executor.rs 约 113-116)授权退化为对公共常量字符串WARDEN_RUNTIME_SESSION的比对。
建议方案
- 生产路径改用
with_shame_wall_path持久化,或明确"registry 仅内存态"并降低文档承诺; - 决定 L4 是否保留:实现跨会话累计,或删除该层;
- 把 deadline/defer 执法真正接入运行时,或删除误导性的
PokePriorityManager及测试; - 让 SKILL.md 与 R-25 实现保持一致;
- 重估默认开启的 Poke 注入频率与成本,必要时以配置收敛;
- 为 rate 加校验,为
notify_user补真实通知或去掉该承诺。
8. tombstone 注册表并发/原子性
问题
record_deleted_session_id(session_manager.rs约 1043-1070)采用"读整文件 → append/去重 → 整文件写回",全程无锁;删除仅按 session_id 持有session_mutation_locks,同一 workspace 下不同会话的并发删除会同时读写同一个deleted-session-ids.json。list_deleted_session_ids(约 1026-1035)对解析失败返回Ok(Vec::new())(静默空表),无法区分"文件不存在"与"解析失败"。- 写入用
tokio::fs::write(约 1068)裸写,而模块内其他 JSON(metadata、index)均用原子写。
风险
- 并发删除丢更新:两个会话基于旧快照 append 并写回,后写者覆盖先写者 → 已删除会话 id 未进 tombstone,击穿"防幽灵复活"目标。
- 进程崩溃/撕裂写留下半写文件,下轮解析失败即静默返回空表 → 一次撕裂写清空全部 tombstone,防护整体失效。
建议方案
- 为每 workspace 的 tombstone 文件加进程内/跨进程锁(复用
.index.lock或新建.tombstone.lock),锁内完成 read-modify-write; - 改用与 metadata/index 一致的原子写(temp + rename);
- 解析失败时记录错误并保留旧文件,而非静默返回空。
三、Web UI 问题
9. /goal clear 后无法再设置目标
问题
setThreadGoal(src/web-ui/src/flow_chat/store/FlowChatStore.ts 约 4074-4083):now = Date.now()(毫秒),而后端 ThreadGoal.updatedAt 是 epoch 秒(秒级)。设置目标时写入秒级时间戳;清除目标(goal=null)时把 Date.now()(约 1.75e12 ms)写入 threadGoalUpdatedAt。之后新目标返回的秒级时间戳(约 1.75e9)被 isGoalStaleForSession 判为"过期"而丢弃。
风险
- 用户
/goal clear后,同一会话内后续任何setThreadGoal/syncGoalToStore/handleThreadGoalUpdated都会被当作过期丢弃,直到会话重载;runGoalCommand的 set 分支仍弹"设置成功"toast,出现"提示成功但实际未生效"的不一致。
建议方案
- 统一时钟单位:清除时用
Math.floor(Date.now()/1000),或统一归一化为秒; - 补一条"clear 后重新 set 目标成功"的测试。
10. 新增硬编码中文违反 i18n 治理
问题
src/web-ui/src/flow_chat/utils/conversationLevelLabel.ts(约 15/24/29 行)返回用户可见中文军衔(主会话/副官/上尉/少尉/士官),被ChatInput.tsx(约 5352)直接渲染为按钮文字。- 大量新增 CJK 注释:
EventHandlerModule.ts(258-262、855-859、882-883、947-948、982-984、2135-2139、2171-2172、2750-2752、2832-2833、2854、2923、3026)、TextChunkModule.ts:81-83,182-183、ToolEventModule.ts:393,505,787-789、goalService.ts:73-76,110-111、FlowChatStore.ts:4072-4073、useComposerCapabilities.ts:91、AgenticEventListener.ts:76。 AIModelConfig.tsx:439硬编码"本地网关"。
风险
- 仓库
web-ui-source硬编码 CJK 预算为 0(scripts/i18n-hardcoded-baseline.jsonmaxCjkLines: 0),pnpm run i18n:audit会直接失败,CI 门禁红灯; - en-US/zh-TW 用户看到中文文案,属本地化缺陷。
建议方案
conversationLevelLabel改为 i18n key(en/zh-CN/zh-TW 三语);- 新增注释改英文或移除;
- 本地跑
pnpm run i18n:audit确认通过。
四、卫生 / 越界(建议从合入中剔除)
11. 过程/草稿文档入仓
COMMIT_MSG.txt(+31,提交信息草稿)、docs/workflow-architecture-draft.md(+321,自述"Draft for the PR customization description")、examples/example-pipeline.yaml(+19,引用不存在的 test_data/golden_tick/... 路径)、CUSTOMIZATIONS.md(+701,研究论文体 PR 自述,含 PR 内部行号与下游 "taiji" 路线图)。
风险:违反仓库 AGENTS.md"临时/过程产物留在本地"规范;docs/ 混入与产品文档体系重复的过程文档。
建议方案:这些内容放进 PR 描述或留在本地,不随代码合入;CUSTOMIZATIONS.md 若保留应移入所属产品文档目录并去重。
12. 新增 deny.toml 是孤立治理配置
未接入任何 CI(全 .github grep cargo-deny|deny.toml 零匹配);规则与真实依赖冲突:根 Cargo.toml 用 [patch.crates-io] 引入 git 依赖(tauri-runtime/tauri-runtime-wry/tauri-utils,约 291-293 行),unknown-git="deny" + allow-git=[] 一启用即拒绝;multiple-versions="deny"(skip 仅 4 项)、copyleft="deny"+default="deny"+confidence-threshold=0.8 在大依赖树几乎必然红灯。
建议方案:要么删除,要么按真实依赖基线校准并真正接入 CI(作为独立 PR)。
13. 新增 e2e.yml 重型 CI
触发 push: branches:[main] 或带 e2e label;每次 main push 触发完整 cargo build -p bitfun-desktop(debug)+ pnpm e2e:install + L0/L1,30 分钟超时;L1 步骤 continue-on-error: true。与主题无关却新增持续 CI 成本。
建议方案:单独评估触发范围(如仅 label 或按路径过滤),单独 PR 提交。
14. 其他越界改动
scripts/cargo-target-gc.mjs(+12/-4):target 缓存清理工具微调,与主题无关;src/crates/assembly/core/src/miniapp/manager.rs(−14):miniapp 子系统削减,与主题无关;src/web-ui/vite.config.ts:把生产 Vite 配置改接到vitest/config并新增test.setupFiles: ["./src/test/setup.ts"],测试基建耦合进生产构建(建议独立vitest.config.ts);- 一批 0/0 变更(换行符/mode 噪音,如
tests/rbac_master_switch.rs、service/config/*.rs)。
建议方案:vite 测试配置改为独立 vitest.config.ts;清理换行符噪音提交;无关改动拆分。
五、最关键的交付建议:请拆分 PR
问题:371 个文件、+4.3 万行(+43,526 / −2,519),跨越 runtime-ports → services → assembly/core → adapters → desktop → web-ui 全栈。
风险:review 与回滚成本极高;因个别问题整体被搁置;问题定位困难;掩盖"哪些改动属于哪个特性"。
建议方案:按域拆成独立小 PR,逐个评审、独立测试与回滚:
- ACP 通道 + SessionControl/SessionMessage(会话层)
- Warden 治理系统
- RBAC 子代理角色
- 编排/协调层(Coordinator/Scheduler/Legion/Task/Plan)
- 上下文与提示缓存(prompt cache 稳定性)
- CodeBuddy 集成
- Web UI
- 杂项(CI/脚本/依赖/卫生)
六、值得保留的亮点
finish_reason空串修复正确且安全:完成判定由流 EOF 驱动(ToolCallBoundary::StreamEnd),不会引入无限循环,实打实解决 CodeBuddy 工具调用被提前截断的痛点。- ACP 远程命令的 shell 转义健壮(
remote_shell.rs单引号转义、本地create_tokio_command不经 shell),未发现命令注入。 - coordination_store 的 WAL/崩溃恢复、
claim_terminal_tasks单赢家竞争、幂等 schema 初始化(CREATE ... IF NOT EXISTS+ user_version)都很扎实。 - Legion/Plan/Todo 的路径围栏(
../符号链接/绝对路径越界、.plan.md后缀校验)与环/越界校验充分。 - 代码注释带明确决策 ID(W-XX/R-XX),可读性与可追溯性好。
…elf_update restore, codebuddy cleanup, RBAC main-session exemption, ACP/SessionMessage authz gates, warden doc alignment, goal clear fix, i18n, hygiene strip, tombstone atomicity, S-56 sanitize)
…e from taiji (card-search + grid9 slot4-16)
Summary
This PR is a reference submission for upstream review (see issue #191 on the fork). It presents a complete, final-state snapshot of customizations built on BitFun's latest main, focusing on multi-agent collaboration and connecting BitFun to external AI agents through the ACP protocol.
This is a reference for how the customization was approached — not a request to merge blindly. It is intentionally large (370 files), because the purpose is to show the final state of a coherent customization effort. Maintainers are welcome to review, cherry-pick, or use it as a design reference.
What is included
Highlights (key domains)
Verification
Notes