Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ jobs:
cache-bin: false
save-if: ${{ github.event_name != 'pull_request' }}

- name: Run CLI tests
run: cargo test --locked -p bitfun-cli
- name: Run CLI, ACP, and agent runtime tests
run: cargo test --locked -p bitfun-cli -p bitfun-acp -p bitfun-agent-runtime

# ── Rust: build check ─────────────────────────────────────────────
rust-build-check:
Expand Down
37 changes: 27 additions & 10 deletions docs/architecture/agent-runtime-services-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ CLI Agent 体验边界见 [`cli-product-line-design.md`](cli-product-line-design

本文中的接口片段只说明依赖方向和职责,不自动构成当前 API 或实施承诺。当前接口名称、字段和消费方以代码为准;
新增公共类型前必须有真实生产调用方、版本边界和验证路径。现有 Agent Runtime SDK 仍是 v1 preview,CLI、ACP、
Desktop 仍保留 `bitfun-core/product-full` 兼容 owner。CLI 已消费独立的产品组装结果和 SDK 端口,但这不等于
协调器、调度器、持久化或工具执行 owner 已迁移;ACP 与 Desktop 尚未完成对应入口切换。
Desktop 仍保留 `bitfun-core/product-full` 兼容 owner。CLI 与 CLI 托管的 ACP server 已消费各自的产品组装结果和
SDK 端口,但这不等于协调器、调度器、持久化或工具执行 owner 已迁移;ACP 的完整持久化历史、模型/模式、MCP、
客户端路径与 Desktop 入口仍保留明确的兼容边界。

阅读路径:第 1 节确认 SDK、内核、产品特性、扩展接口和 crate 边界;第 2-3 节说明稳定接口、
运行时服务、内核、工具和工作流;第 4 节说明产品组装与扩展注册;第 5 节作为质量保护和
Expand Down Expand Up @@ -695,7 +696,8 @@ pub struct HarnessExecutionContext {
`src/crates/assembly/core` 仍承担 `bitfun-core` 兼容组装。现有 `ProductAssembler` 是具体结构体,
通过 `assemble(ProductAssemblyInput)` 产生 `ProductRuntimeParts`,本文件不再为它定义第二套目标接口。

当前 CLI 入口已使用类型化 `RuntimeServices` 构造 `ProductRuntimeParts`,并通过一个调用级上下文把
当前 CLI 与 CLI 托管的 ACP server 已使用类型化 `RuntimeServices`,分别以 `DeliveryProfile::Cli` 和
`DeliveryProfile::Acp` 构造 `ProductRuntimeParts`。CLI 通过一个调用级上下文把
Agent Runtime SDK、Harness、能力注册、调用级权限和 Agentic 事件广播交给 TUI、Exec、Session、Usage 与
交互模式下的 Peer Host。SDK 已承接会话创建/列举/删除/恢复、类型化转录读取、轮次提交/取消,以及 CLI/TUI
的工具确认、拒绝和用户问题回答;Peer Host 当前只消费同一 SDK 的工具确认与拒绝。SDK v1 尚未覆盖的固定 ID
Expand All @@ -704,8 +706,10 @@ Agent Runtime SDK、Harness、能力注册、调用级权限和 Agentic 事件
Core 的 Network、Git 和 MCP Catalog 当前仍含兼容 marker,因此该诊断不等于对这些外部服务做实时探活。

该切换仍是 `product-full` 兼容组装,不是 owner 迁移。协调器、调度器、持久化、工具管线和 Agentic Event Queue
仍由 Core 唯一持有;CLI 不复制这些状态。ACP 与 Desktop 尚未接入组装结果,Server 仅提供健康检查、信息与
ping 路由。未接入入口的 profile、枚举分支和单元测试仍不能证明对应产品形态可用。
仍由 Core 唯一持有;CLI 与 ACP 不复制这些状态。ACP 服务端通过 SDK 处理会话创建/列举、轮次、取消、交互响应和事件订阅,
但完整持久化历史回放、模型/模式、模型目录/配置和 MCP 仍走单一 Core 兼容门面;ACP stdio、连接和协议投影仍在
`interfaces/acp`。Desktop 尚未接入组装结果,Server 仅提供健康检查、信息与 ping 路由。未接入入口的 profile、
枚举分支和单元测试仍不能证明对应产品形态可用。

职责:

Expand All @@ -718,8 +722,8 @@ ping 路由。未接入入口的 profile、枚举分支和单元测试仍不能

| 阶段 | 约束 |
|---|---|
| 当前 | CLI 与 Peer Host 消费真实 Runtime Parts SDK,Core 兼容门面只承接 SDK v1 缺口;不扩张字段或再造描述符 |
| 迁移 | 迁移执行 owner 或继续接入 ACP、Desktop ,必须分别证明行为等价;relay 的 Cargo 反向边已删除,room/device 状态、account/sync 存储、asset store 与 HTTP/WebSocket router 已下沉,但 embedded TCP bind、静态 fallback 和任务生命周期仍是 assembly 兼容债务 |
| 当前 | CLI、Peer Host 与 CLI 托管的 ACP server 消费真实 Runtime Parts / SDK,Core 兼容门面只承接已列明的 SDK v1 缺口;不扩张字段或再造描述符 |
| 迁移 | 迁移执行 owner、ACP 剩余兼容路径或 Desktop 入口前,必须分别证明行为等价;relay 的 Cargo 反向边已删除,room/device 状态、account/sync 存储、asset store 与 HTTP/WebSocket router 已下沉,但 embedded TCP bind、静态 fallback 和任务生命周期仍是 assembly 兼容债务 |
| 完成 | 每个声称支持的 profile 都由生产入口消费组装结果,并有最小入口验证;无消费方的 profile 不对外宣称可用 |

产品定义、品牌资源和界面布局的长期边界以
Expand All @@ -731,6 +735,7 @@ ping 路由。未接入入口的 profile、枚举分支和单元测试仍不能

- 具体运行时服务通过 `RuntimeServicesBuilder` / provider registry 构造。
- CLI 只选择 `DeliveryProfile::Cli` 一次;必需服务缺失时组装失败,不回退到静态计划或另一 profile。
- CLI 的 ACP stdio 入口只选择 `DeliveryProfile::Acp` 一次;组装或 SDK runtime 构造失败时在接受 stdio 请求前退出。
- CLI 的 `json` 输出为单结果文档,`stream-json` 直接复用现有 `AgenticEventEnvelope`;协议层不新增
`schema_version`、`sequence` 或平行事件 taxonomy。
- 能力计划选择工具提供方组计划和 Harness 描述符;当前不存在供任意模块注册所有对象的通用组装注册表。
Expand Down Expand Up @@ -778,8 +783,9 @@ ping 路由。未接入入口的 profile、枚举分支和单元测试仍不能

当前 Runtime SDK 已提供会话创建、列出、删除、恢复和类型化转录读取。`AgentSessionRestoreRequest/Result` 与
`AgentSessionRestorePort` 归 Agent Runtime SDK,以继续复用 Runtime owner 的完整 `SessionState`;类型化
`SessionTranscript` 归 `runtime-ports`。两者都由 `assembly/core` 注入真实 persistence owner,CLI/TUI 是当前恢复与
转录消费方。CLI/TUI 的工具确认、拒绝和用户问题回答,以及 Peer Host 的工具确认与拒绝,通过类型化
`SessionTranscript` 归 `runtime-ports`。两者都由 `assembly/core` 注入真实持久化 owner,当前由 CLI/TUI
消费;ACP 为保证模型配置与完整历史来自同一次恢复,继续通过 Core 兼容门面读取协议回放所需的完整轮次,避免为单一协议
扩张通用 transcript。CLI/TUI 的工具确认、拒绝和用户问题回答,以及 ACP 服务端 / Peer Host 的工具确认与拒绝,通过类型化
`AgentInteractionResponsePort` 回到 Core 的工具管线或用户输入 owner,不改变审批策略或交互所有权。
`CoreAgentRuntimeCompatibility` 仍承载未迁移的
固定 ID 会话创建、模型更新、持久化、分支、用量和快照等操作;不能据此把整个兼容门面一次性删除,也不能把这些
Expand Down Expand Up @@ -835,6 +841,11 @@ feature group、运行时服务要求、工具提供方组、Harness 描述符

`bitfun-acp` 保持集成归属。

CLI 托管的 ACP 服务端使用 `DeliveryProfile::Acp` 组装一个 Agent Runtime,通过 SDK 处理会话创建/列举、轮次提交/取消、
交互响应和只读 Agent 事件订阅。ACP 只把共享运行时事实映射成协议更新;标准输入输出、连接、权限 RPC 与通知生命周期
不进入 SDK。完整持久化历史恢复、模型/模式、模型目录/配置、MCP 和 ACP 客户端路径仍是明确的 Core 兼容范围,
不据此扩张通用 runtime DTO。

继续拥有:

- ACP protocol。
Expand Down Expand Up @@ -1021,13 +1032,19 @@ Product 测试:
wire schema、Relay ACK/重放和重连协议未在该切换中扩张。
- CLI 通过 SDK 处理已覆盖的 session/turn/cancel 操作,并通过一个 Core 兼容门面处理 SDK v1 缺口;
该门面复用现有 owner,不建立第二套状态或事件 schema。
- CLI 托管的 ACP 服务端已以 `DeliveryProfile::Acp` 构造真实 Runtime Parts;会话创建/列举、轮次、取消、工具确认/拒绝和
Agent 事件订阅复用同一 SDK 语义,ACP stdio、连接与协议投影保持不变。Agentic Event Queue 仍是唯一事件 owner;
全局有界 broadcast 继续服务 CLI/TUI,活动 ACP prompt 使用固定容量、仅接收本会话事件的临时通道,并在最后一个订阅者
释放时立即回收。CLI 宿主进程只保留一个旧消费队列排空任务,不增加每会话转发任务或第二套事件 schema。ACP 组装入口使用独立的
轮次提交适配器,在会话锁内拒绝忙碌会话的第二个 prompt;CLI/TUI、Desktop 和远程入口的既有排队策略不变。

仍需完成:

- 把 embedded relay 的 TCP bind、静态 fallback 和任务生命周期移出 assembly;room/device 状态、account/sync
存储、asset store 与 HTTP/WebSocket router 已归属 `services/relay-service`,Cargo 反向边已删除并由通用边界检查保护。
- 继续缩小 CLI 的 Core 兼容门面;只有稳定端口、真实生产调用方和行为等价测试齐备时才迁移 owner。
- 让 ACP、Desktop 依次接入产品组装,并为每条路径证明行为等价;ACP 生命周期和 Desktop 平台资源仍留在入口。
- 继续按真实复用需求缩小 ACP 的完整持久化历史、模型/模式、MCP 与客户端兼容路径;Desktop 入口另行接入产品组装并
证明行为等价。ACP 生命周期和 Desktop 平台资源仍留在各自入口。
- 为 Agent Runtime SDK 增加至少一个非 `bitfun-core` 的真实嵌入方;预览 facade 和单元测试不等于外部可用 SDK。
- 仅在真实端到端切片中接入插件主机;外部插件先转换为类型化工具、Hook、事件、权限请求或诊断,
不把生态对象带入 Agent Runtime。
Expand Down
12 changes: 7 additions & 5 deletions docs/architecture/cli-product-line-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ BitFun CLI 应成为可独立安装和发布的 Agent 产品,而不是 Desktop
- 独立 CLI 测试与打包工作流;主 CI 的三平台 workspace check 同时覆盖 `bitfun-cli` 编译。

上述切换不等于运行时 owner 已迁移,也不表示 CLI-P0 全部完成。CLI crate 仍以 `bitfun-core/product-full`
承载协调器、调度器、持久化、工具管线和部分 SDK v1 缺口,但 Peer Host 不再自行构造这些 owner;ACP stdio
仍走原入口,插件命令仍以来源管理和静态预览为主。兼容门面只转发,不重新计算或写入同一事实。
承载协调器、调度器、持久化、工具管线和部分 SDK v1 缺口,但 Peer Host 不再自行构造这些 owner;ACP 的 stdio、
连接和协议投影仍由 `interfaces/acp` 持有,后端已切换至 `DeliveryProfile::Acp` 与组装后的 SDK runtime;插件命令
仍以来源管理和静态预览为主。兼容门面只转发,不重新计算或写入同一事实。

目标态仍存在以下结构缺口:

Expand All @@ -147,13 +148,14 @@ CLI-P0 的目标是建立后续功能补齐所需的稳定边界,不改变现

CLI-P0 不是一个统一重构 PR。静态 profile、真实 Runtime Services、Runtime Parts、调用级审批、共享事件源和
本地 Agent 纵向入口已接入;旧门面仅在后续 owner 迁移的行为等价成立后退出。配置解释、产品定制消费、TUI
进一步拆分、ACP 切换和 package smoke 仍需独立交付,不能由本次运行时切换代替。
进一步拆分和 package smoke 仍需独立交付。CLI 托管的 ACP 服务端已独立切换到 ACP profile 与组装后的 SDK runtime;
真实模型、PTY 与权限失败等进程级验收仍需另行完成,不能由本次运行时切换代替。

其余工作独立立项,不能与 profile 迁移互相充当完成条件:

| 切片 | 范围 | 退出条件 |
|---|---|---|
| 调用级审批 | TUI`exec` 已使用调用级策略且不写全局配置;ACP 需独立迁移 | Runtime-context `Allow always`、审批规划、`exec` 安全默认值和显式 `--auto` 有 focused test;真实模型/PTY 审批流与 ACP 另行验收 |
| 调用级审批 | TUI`exec` ACP 已使用各自调用级策略且不写全局配置 | Runtime-context `Allow always`、审批规划、`exec` 安全默认值和显式 `--auto` 有 focused test;真实模型/PTY 审批流与 ACP 仍需另行验收 |
| 输出协议 | 保持通用 `text/json/stream-json` 心智,复用现有 Agentic envelope,不新建 CLI schema | 已覆盖结果/envelope 序列化、参数与前置 JSON 失败、失败完成、同会话跨 turn 隔离和 stream-json/Patch stdout 冲突;真实信号、模型权限失败与 Patch I/O 故障注入仍需进程级契约 |
| 配置解释 | Canonical Config 层级、全局/项目持续来源、加载状态和兼容导入 dry-run | 不自动写入;冲突、未知字段、待确认能力和凭据引用可解释 |
| 产品定制 | 消费最小产品定义、组装结果和已注册 TUI layout/theme ID | 第二个真实 CLI 产品复用后再提升公共字段 |
Expand Down Expand Up @@ -585,7 +587,7 @@ CLI Agent 能力加强必须落在共享 Agent Runtime、Tool Runtime 或 Harnes
| Action/Keymap | registry 唯一性、Slash/Palette/Help/dispatch 一致、配置键位真实输入、冲突来源和终端恢复 fallback |

通用 `cargo check --workspace` 负责三平台 CLI 编译保护;独立 CLI CI 运行
`cargo test --locked -p bitfun-cli`。已落地的 focused 协议契约进入该测试;完整进程/PTY 矩阵与打包 smoke
`cargo test --locked -p bitfun-cli -p bitfun-acp -p bitfun-agent-runtime`。已落地的 focused 协议契约进入该测试;完整进程/PTY 矩阵与打包 smoke
仍按对应切片补入门禁,不能由序列化单测代替。

### 10.2 阶段退出条件
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/product-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ flowchart LR
| HarmonyOS PC 原生 CLI/TUI | 未来平台目标,当前未实现 | 目标、问题和风险见平台规约;具体适配另立专题,HAP、手机 Remote App 与远端代执行均不替代 |
| HarmonyOS PC GUI | 完整 HarmonyOS PC 支持的另一目标形态,当前未实现 | 与 CLI/TUI 共享稳定能力和 Runtime 语义,但独立设计宿主、界面与发布验证;Web、Remote 或现有 Tauri Desktop 均不能替代 |
| HarmonyOS 手机 Remote App | `src/apps/mobile/harmonyos` 是 phone-only ArkTS 远程入口,不持有本地 Rust Agent Runtime | 保持当前能力并按移动端专题独立演进;本轮不提前设计移动 Runtime/TUI/GUI,也不能据此宣称 HarmonyOS PC 本地能力 |
| ACP | 生产入口仍直接依赖 `bitfun-core/product-full` | `DeliveryProfile::Acp` 尚未进入入口组装;不得把测试中的 profile 解释为生产隔离 |
| ACP | CLI 托管的服务端仍以 `bitfun-core/product-full` 作为兼容执行层 | 入口已选择 `DeliveryProfile::Acp` 并消费 Runtime Parts;组装层在入队前原子拒绝忙碌会话,不改变其他产品入口的排队行为;会话恢复、模型/模式、MCP、客户端与协议生命周期仍留在现有 Core/ACP 归属,不据此宣称完整解耦 |
| Server / Remote | 当前生产路由没有插件状态消费闭环;Remote 插件执行未实现 | 不在本地替远端项目发现、准备或执行插件;未接入时返回明确不支持 |
| Web / Mobile Web | 依赖现有后端入口,不持有插件执行单元 | 对应 profile 当前为空计划或未接入生产,不能据枚举值宣称独立产品能力 |
| SDK | 仅有 preview 门面、空 profile 计划和测试替身 | 不牵引 `product-full`、具体服务管理器或插件 host ABI;未满足独立嵌入验证前不宣称可发布 |
Expand Down
47 changes: 45 additions & 2 deletions src/apps/cli/src/product_assembly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,27 @@ use bitfun_runtime_services::RuntimeServices;
pub(crate) fn assemble_cli_runtime_parts(
services: RuntimeServices,
) -> Result<ProductRuntimeParts, ProductAssemblyError> {
ProductAssembler::new().assemble(ProductAssemblyInput::new(DeliveryProfile::Cli, services))
assemble_runtime_parts(DeliveryProfile::Cli, services)
}

pub(crate) fn assemble_acp_runtime_parts(
services: RuntimeServices,
) -> Result<ProductRuntimeParts, ProductAssemblyError> {
assemble_runtime_parts(DeliveryProfile::Acp, services)
}

fn assemble_runtime_parts(
profile: DeliveryProfile,
services: RuntimeServices,
) -> Result<ProductRuntimeParts, ProductAssemblyError> {
ProductAssembler::new().assemble(ProductAssemblyInput::new(profile, services))
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use super::assemble_cli_runtime_parts;
use super::{assemble_acp_runtime_parts, assemble_cli_runtime_parts};
use crate::runtime::{
approval::{CliApprovalPolicy, CliPermissionService},
services::{CliClock, CliRuntimeEventSink, CliRuntimeServicesProvider},
Expand Down Expand Up @@ -87,4 +100,34 @@ mod tests {
));
assert!(!parts.harness_registry().provider_ids().is_empty());
}

#[test]
fn acp_product_assembly_uses_acp_profile_and_production_services() {
let workspace = tempfile::tempdir().expect("workspace");
let services = CliRuntimeServicesProvider::new(
workspace.path(),
Arc::new(CliPermissionService::new(CliApprovalPolicy::Ask)),
Arc::new(CliRuntimeEventSink::new(8)),
Arc::new(CliClock),
)
.expect("provider")
.build()
.expect("runtime services");

let parts = assemble_acp_runtime_parts(services).expect("ACP product runtime parts");

assert_eq!(parts.plan().profile(), DeliveryProfile::Acp);
assert!(parts.missing_service_requirements().is_empty());
assert!(parts
.service_availability()
.iter()
.all(|entry| entry.status() == ProductServiceCapabilityStatus::Available));
assert!(matches!(
parts.plugin_runtime().availability(),
PluginRuntimeAvailability::Disabled {
reason: PluginRuntimeUnavailableReason::UnsupportedProfile
}
));
assert!(!parts.harness_registry().provider_ids().is_empty());
}
}
5 changes: 4 additions & 1 deletion src/apps/cli/src/root_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,9 @@ pub(crate) async fn serve_acp_stdio() -> Result<()> {
.context("Failed to initialize agentic system")?;
tracing::info!("Agentic system initialized");

bitfun_acp::BitfunAcpRuntime::serve_stdio(agentic_system).await?;
let workspace_root = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let runtime = crate::runtime::AcpRuntimeContext::build(agentic_system, workspace_root)?;
let (agent_runtime, compatibility) = runtime.parts();
bitfun_acp::BitfunAcpRuntime::serve_stdio(agent_runtime, compatibility).await?;
Ok(())
}
Loading
Loading