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
105 changes: 90 additions & 15 deletions docs/architecture/agent-runtime-deployment-design.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/architecture/agent-sdk-product-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ flowchart TB
一次性 Headless CLI 继续 Embedded;公开 SDK 默认连接私有 SDK Host。Shared Agent Runtime process 和 SDK Host 都是 Rust 产品进程,
与运行第三方 JS/TS 的 Node/Bun Plugin Host 不同;三者不能共享名称或业务归属。

当前代码已经交付显式启用的 Shared TUI 最小切片,包含本机 IPC、身份、握手、Session/Turn、当前 Session 的 Agent mode/model、Permission/UserInput、
当前代码已经交付显式启用的 Shared TUI 最小切片,包含本机 IPC、身份、握手、Session/Turn、当前 Session 的 name/Agent mode/model、Permission/UserInput、
ownership 和生命周期治理;GUI、Headless CLI、ACP、SDK Host、Server/Remote 仍没有 Shared consumer。该图中的多入口逻辑复用是
当前事实,除 Shared TUI 外的跨进程 Shared deployment 仍是目标架构。

Expand Down
11 changes: 9 additions & 2 deletions docs/architecture/cli-product-line-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,15 @@ Headless CLI 和公开 Agent SDK 都调用同一 Agent Runtime API,但交付

| 形态 | 默认部署 | 当前 Shared 范围 |
|---|---|---|
| 交互式 TUI | Embedded | 显式 `--shared` 后支持 Session list/create/restore、transcript、当前 Session Agent mode/model、Turn submit/cancel、Permission 和 UserInput |
| 交互式 TUI | Embedded | 显式 `--shared` 后支持 Session list/create/restore、transcript、当前 Session rename/Agent mode/model、Turn submit/cancel、Permission 和 UserInput |
| `bitfun exec` / CI | Embedded | 不接受 Shared;保持独立进程、stdout/stderr 和退出码语义 |
| ACP / SDK Host / GUI / Remote / Peer | 各自既有部署 | 不消费 TUI IPC,也不因本开关改变生命周期 |

Shared TUI 不提供 Session delete/fork、模型目录/默认值、Agent/Subagent 管理、MCP/扩展、账号同步、用量、observer、replay 或 controller transfer;对应入口给出明确的 Embedded 恢复建议,不在 Client 进程初始化第二套 Core owner。
Shared 模式的命令面板、快捷键帮助和底部提示使用同一能力投影:`/agent`、Tab 和 Shift+Tab 只切换当前 Session 的 Agent mode,`/models` 只切换当前 Session 的 model,二者都不进入管理页面或修改未来 Session 的默认值;其他不支持动作不显示为可执行入口。Session 切换失败保留原控制权,单个连接已有活动 Turn 时拒绝重复提交和 Session mode/model update;事件订阅失效后当前视图立即失效并要求重启 Shared TUI。
Shared 模式的斜杠命令、快捷键帮助和底部提示使用同一能力投影:`/rename <name>` 修改当前 Session 名称;`/agent`、Tab 和 Shift+Tab 只切换当前 Session 的 Agent mode;`/models` 只切换当前 Session 的 model。Embedded 与 Shared 的 `/help` 都从 Action Registry 展示 `/rename <name>`;在 slash menu 中选择它只预填命令并等待用户输入名称。若外部来源使用相同命令名,用户明确选择的 BitFun 命令可完成这一次参数提交,即使偏好保存失败也不会重新弹出来源选择。它们不进入管理页面,也不修改未来 Session 的默认值。其他不支持动作不显示为可执行入口。Session 切换失败保留原控制权;单个连接已有活动 Turn 时拒绝重复提交以及 Session rename/mode/model update;事件订阅失效后当前视图立即失效并要求重启 Shared TUI。

部署差异由 CLI Runtime client 封装。Embedded 以 Rust 类型直接调用 `AgentRuntime`,不初始化 IPC 或执行 JSON 编解码;Shared 将同一业务请求映射为一个有界本机 frame,Client/Server 各自只编码一次,再交给同一 Runtime owner。多 TUI 复用一个 Runtime 进程,连接和队列保持有界,不按 TUI 数量复制 Session owner。详细的 4+1 视图、帧上限和并发边界见
[`agent-runtime-deployment-design.md`](agent-runtime-deployment-design.md)。

#### 管理与诊断

Expand Down Expand Up @@ -298,6 +301,8 @@ TUI renderer、实验性接口和完整外部 Server 协议按总矩阵明确降
| 层/模块 | 负责 | 不负责 |
|---|---|---|
| `src/apps/cli` | Clap 入口、TUI 状态/渲染、终端事件、入口本地设置、命令展示与结构化输出 | 会话状态机、工具执行、权限裁决、插件内部 ABI、品牌能力真值 |
| CLI Runtime client | 屏蔽 Embedded/Shared 部署差异,将 CLI 的类型化调用映射到进程内 Runtime 或私有本机 IPC | 实现 Session 业务规则、暴露公开 SDK 或在两种部署中复制行为 |
| `adapters/agent-runtime-ipc` | Shared TUI 的私有本机 transport、严格握手、frame 上限、连接控制和封闭 operation 映射 | 服务 Embedded、公开协议、Remote transport 或 Runtime 业务 owner |
| `assembly/product-capabilities` | Delivery Profile、Product Capability 计划、静态 eligibility、服务需求和组装计划 | 品牌资源读取、动态可用性、用户配置、UI 状态、具体服务创建 |
| 产品构建期校验 | 校验产品定义、品牌资源、TUI 布局选择和内置扩展版本,输出产品组装结果 | 创建运行时服务、实现终端行为或保存用户配置 |
| Product Assembly | 读取产品组装结果中本次 CLI 需要的字段,选择能力/服务/扩展,构建 Runtime Parts | 读取原始品牌资源、实现 Agent/Tool/插件适配器/终端行为或运行构建脚本 |
Expand Down Expand Up @@ -334,6 +339,8 @@ CLI/TUI 的会话创建、列出、删除、恢复和历史转录读取通过 Ru
账户同步、富历史及其他未覆盖操作继续使用经过审查的 Core compatibility 方法,直到各自具备明确 owner、稳定 DTO、远程语义和行为等价测试。
这是一条垂直链路迁移,不是删除整个兼容接口或新建 CLI 专用服务层。

交互式命令 `/rename <name>` 复用已有 Session rename owner。Runtime 只写名称相关 metadata,再发布内存名称;写入失败时先恢复旧 metadata,无法确认恢复结果则返回 `outcome_unknown`。Shared 请求写入后的超时或断连也返回 `outcome_unknown`。两种情况都要求恢复 Session 后检查,不自动重试可能已经生效的写操作;发送前编码失败或请求过大则明确未执行并保留连接。Session 选择器不保留第二套内联重命名状态。

Runtime Configuration Service 当前由 `bitfun-core/service/config` 负责。在经评审的 port/provider
迁移完成前,CLI 和生态适配器不得另建写入器;adapter 只做 discover/parse/normalize,配置服务才能
预览/应用、记录来源,并通过远程工作区 provider 写目标层。产品定义、品牌资源、界面布局选择
Expand Down
5 changes: 4 additions & 1 deletion docs/architecture/product-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ BitFun 同时面向桌面 GUI、TUI/CLI、Web、ACP、Server、Remote、SDK 和

4+1 视图分别描述系统职责、代码组织、运行协作、部署边界和关键场景,避免把逻辑模块、crate、进程和调用链混在同一张图中。分类沿用 [Kruchten 4+1](https://www3.software.ibm.com/ibmdl/pub/software/rational/web/whitepapers/2003/Pbk4p1.pdf),图的层级、动态协作和部署节点表达参考 [C4](https://c4model.com/diagrams) 以及 arc42 的 [Building Block](https://docs.arc42.org/section-5/)、[Runtime](https://docs.arc42.org/section-6/) 和 [Deployment](https://docs.arc42.org/section-7/) 视图;这些方法只提供视角和表达规则,不替代 BitFun 的真实 owner 与代码边界。

Level 0 展示系统级主要边界和依赖方向;Level 1 再按 Level 0 的模块或范围展开。每张图必须能独立说明范围和图例,关系使用明确方向或协议,逻辑模块、crate、运行任务和部署实例不要求一一对应。
Level 0 展示系统级主要边界和依赖方向;Level 1 再按 Level 0 的模块或范围展开。每张图必须能独立说明范围和图例,关系使用明确方向或协议,逻辑模块、crate、运行任务和部署实例不要求一一对应。Agent Runtime 的 Embedded/Shared 逻辑、开发、进程、物理和场景视图集中在
[`agent-runtime-deployment-design.md`](agent-runtime-deployment-design.md),本文件不重复其连接和性能细节。

### 2.1 Logical View · Level 0

Expand Down Expand Up @@ -529,6 +530,8 @@ flowchart LR
Assembly["产品组装"] -. "选择" .-> Runtime
```

入口 adapter 消费同一 Runtime API,部署选择不能进入业务 owner:Embedded 使用进程内强类型调用;Shared 或 SDK Host 才在各自私有 adapter 中执行 transport 封装。GUI、TUI、Headless CLI、ACP 和 SDK 不共享 wire、renderer 或生命周期,也不得为了统一接口而让默认 Embedded 路径承担序列化成本。

### 4.2 插件调用

```mermaid
Expand Down
2 changes: 1 addition & 1 deletion scripts/core-boundaries/rules/source/forbidden-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const forbiddenContentRules = [
reason: 'agent-runtime-ipc operation scope is frozen to the reviewed Shared TUI slice',
patterns: [
{
regex: /^\s+(?!(?:Health|ListSessions|CreateSession|RestoreSession|UpdateSessionMode|UpdateSessionModel|SubmitTurn|CancelTurn|PendingPermissions|RespondPermission|SubmitUserAnswers|Unit|Sessions|SessionCreated|SessionRestored|TurnAccepted|TurnCancelled|Self|AgentDialogTurnRequest|AgentSessionCreateRequest|AgentSessionCreateResult|AgentSessionListRequest|AgentSessionModeUpdateRequest|AgentSessionModelUpdateRequest|AgentSessionSummary|AgentTurnCancellationRequest|AgentTurnCancellationResult|SessionTranscript)\b)[A-Z][A-Za-z0-9_]*\b/,
regex: /^\s+(?!(?:Health|ListSessions|CreateSession|RestoreSession|RenameSession|UpdateSessionMode|UpdateSessionModel|SubmitTurn|CancelTurn|PendingPermissions|RespondPermission|SubmitUserAnswers|Unit|Sessions|SessionCreated|SessionRestored|TurnAccepted|TurnCancelled|Self|AgentDialogTurnRequest|AgentSessionCreateRequest|AgentSessionCreateResult|AgentSessionListRequest|AgentSessionModeUpdateRequest|AgentSessionModelUpdateRequest|AgentSessionSummary|AgentTurnCancellationRequest|AgentTurnCancellationResult|SessionTranscript)\b)[A-Z][A-Za-z0-9_]*\b/,
message:
'agent-runtime-ipc may not add replay, observer, controller-transfer, deletion, fork, or other operations beyond the reviewed Shared TUI slice',
},
Expand Down
1 change: 1 addition & 0 deletions scripts/core-boundaries/self-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4858,6 +4858,7 @@ export function runManifestParserSelfTest({
'ManageAgents',
].every((name) => runtimeIpcOperationPattern.test(` ${name},`)) ||
runtimeIpcOperationPattern.test(' Health,') ||
runtimeIpcOperationPattern.test(' RenameSession {') ||
runtimeIpcOperationPattern.test(' UpdateSessionMode {') ||
runtimeIpcOperationPattern.test(' UpdateSessionModel {') ||
runtimeIpcOperationPattern.test(' SubmitTurn {')
Expand Down
37 changes: 36 additions & 1 deletion src/apps/cli/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub(crate) enum ActionHandler {
AddModel,
NewSession,
Sessions,
RenameSession,
Skills,
ReloadSkills,
McpServers,
Expand Down Expand Up @@ -114,7 +115,7 @@ pub(crate) enum ActionHandler {
pub(crate) const SHARED_TUI_EMBEDDED_HANDOFF: &str =
"Exit all Shared TUI clients, wait up to 30 seconds for their Runtime to stop, then use default Embedded `bitfun chat`";
pub(crate) const SHARED_TUI_HELP_NOTE: &str =
"Shared TUI: start with `bitfun chat --shared`. Multiple TUI processes reuse one workspace Runtime, while each TUI controls at most one Session and each Session has one controller. Use `/agent`, Tab, or Shift+Tab to change the current Session Agent mode, and `/models` to change its model. Model configuration, Agent/Subagent management, MCP, extension, account-sync, usage, and other management remain Embedded. Exit all Shared TUI clients and wait up to 30 seconds before returning to default Embedded `bitfun chat`.";
"Shared TUI: start with `bitfun chat --shared`. Multiple TUI processes reuse one workspace Runtime, while each TUI controls at most one Session and each Session has one controller. Use `/rename <name>` to rename the current Session, `/agent`, Tab, or Shift+Tab to change its Agent mode, and `/models` to change its model. Model configuration, Agent/Subagent management, MCP, extension, account-sync, usage, and other management remain Embedded. Exit all Shared TUI clients and wait up to 30 seconds before returning to default Embedded `bitfun chat`.";

impl ActionHandler {
pub(crate) const fn available_in_shared_tui(self, context: ActionContext) -> bool {
Expand All @@ -126,6 +127,7 @@ impl ActionHandler {
| Self::SelectTheme
| Self::NewSession
| Self::Sessions
| Self::RenameSession
| Self::AcpHelp
| Self::Init
| Self::History
Expand Down Expand Up @@ -371,6 +373,21 @@ static ACTION_SPECS: &[ActionSpec] = &[
shortcut_label: None,
slash_on_startup: true,
},
ActionSpec {
id: "rename_session",
name: "Rename session",
aliases: &["/rename"],
description: "Rename the current session: /rename <name>",
contexts: CHAT,
availability: ActionAvailability::Idle,
handler: ActionHandler::RenameSession,
default_bindings: &[],
fallback_bindings: &[],
shortcut_field: None,
palette: None,
shortcut_label: None,
slash_on_startup: false,
},
ActionSpec {
id: "skills",
name: "Skills",
Expand Down Expand Up @@ -1795,6 +1812,7 @@ mod tests {
ActionHandler::SwitchAgent,
ActionHandler::SwitchAgentReverse,
ActionHandler::SelectModel,
ActionHandler::RenameSession,
] {
assert!(
action.available_in_shared_tui(ActionContext::Chat),
Expand All @@ -1819,10 +1837,26 @@ mod tests {
assert!(SHARED_TUI_HELP_NOTE.contains("bitfun chat --shared"));
assert!(SHARED_TUI_HELP_NOTE.contains("one Session"));
assert!(SHARED_TUI_HELP_NOTE.contains("`/models`"));
assert!(SHARED_TUI_HELP_NOTE.contains("`/rename <name>`"));
assert!(SHARED_TUI_HELP_NOTE.contains("Agent/Subagent management"));
assert!(SHARED_TUI_HELP_NOTE.contains("remain Embedded"));
}

#[test]
fn rename_is_an_idle_current_session_chat_action() {
let action = action_by_id("rename_session", ActionContext::Chat)
.expect("current session rename action");

assert_eq!(action.aliases, &["/rename"]);
assert_eq!(action.handler, ActionHandler::RenameSession);
assert_eq!(action.availability, ActionAvailability::Idle);
assert!(action.description.contains("/rename <name>"));
assert!(action.available(ActionState::chat(false, false)));
assert!(action.available(ActionState::chat(false, false).for_shared_tui()));
assert!(!action.available(ActionState::chat(true, false)));
assert!(action_by_id("rename_session", ActionContext::Startup).is_none());
}

#[test]
fn shared_tui_projections_hide_embedded_management_actions() {
let state = ActionState::chat(false, false).for_shared_tui();
Expand All @@ -1846,6 +1880,7 @@ mod tests {
assert!(palette_ids.contains(&"switch_agent"));
assert!(slash_ids.contains(&"select_model"));
assert!(palette_ids.contains(&"select_model"));
assert!(slash_ids.contains(&"rename_session"));

let help = ResolvedKeymap::new(&ShortcutsConfig::default()).help_text(state);
assert!(help.contains("Switch Agent"));
Expand Down
Loading