diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c5afe720c3..839e383b31 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -401,6 +401,9 @@ jobs:
- name: Build web UI
run: pnpm run build:web
+ - name: Run mobile web tests
+ run: pnpm --dir src/mobile-web test
+
- name: Type-check mobile web
run: pnpm --dir src/mobile-web run type-check
diff --git a/Cargo.lock b/Cargo.lock
index ad8171b9f8..5954a59d59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -905,20 +905,16 @@ name = "bitfun-app-server"
version = "0.2.18"
dependencies = [
"agent-client-protocol",
- "anyhow",
"async-trait",
"bitfun-agent-runtime",
"bitfun-app-server-client",
"bitfun-app-server-protocol",
"bitfun-core",
- "bitfun-product-domains",
"bitfun-runtime-ports",
"log",
"serde",
"serde_json",
"tokio",
- "tracing",
- "url",
]
[[package]]
@@ -977,9 +973,6 @@ dependencies = [
"bitfun-agent-runtime",
"bitfun-agent-runtime-ipc",
"bitfun-agent-tools",
- "bitfun-app-server",
- "bitfun-app-server-client",
- "bitfun-app-server-protocol",
"bitfun-core",
"bitfun-core-types",
"bitfun-events",
diff --git a/docs/architecture/agent-runtime-deployment-design.md b/docs/architecture/agent-runtime-deployment-design.md
index 0bacb6376c..4ee3d6d417 100644
--- a/docs/architecture/agent-runtime-deployment-design.md
+++ b/docs/architecture/agent-runtime-deployment-design.md
@@ -5,8 +5,8 @@
Agent Runtime 的模块职责见 [`agent-runtime-services-design.md`](agent-runtime-services-design.md),公开 SDK 见
[`agent-sdk-product-architecture.md`](agent-sdk-product-architecture.md),第三方 JS/TS 进程见
[`extensions/plugin-runtime-design.md`](extensions/plugin-runtime-design.md)。Rich Client 的 Embedded direct-runtime 决策、App Server 协议
-和 Shared transport 提案见 [`app-server-architecture.md`](app-server-architecture.md)。Embedded 迁移完成前,当前调用路径以本文及
-已接线代码为准;Shared transport 未通过独立评审前继续使用 v17。
+和 Shared transport 提案见 [`app-server-architecture.md`](app-server-architecture.md)。Embedded TUI 的 Phase 5 direct-runtime 已交付;
+Shared transport 未通过独立评审前继续使用 v18。
## 1. 决策与当前状态
@@ -18,14 +18,16 @@ BitFun 只有一套 Agent Runtime 行为。`Embedded` 和 `Shared` 只描述同
flowchart TB
Desktop["Desktop GUI"] --> DesktopAdapter["Desktop / Tauri adapter"]
Web["Web UI"] --> WebAS["loopback WebSocket App Server"]
- TUI["Interactive TUI"] --> Backend["TuiBackend"]
- Backend -->|"Embedded current"| EmbeddedAS["in-process App Server"]
- Backend -->|"--shared"| SharedIPC["private Runtime IPC v17"]
+ TUI["Interactive TUI"] --> Client["CliAgentRuntimeClient"]
+ Client -->|"Embedded default"| Embedded["TuiRuntimePort::Embedded(AgentRuntime)"]
+ Client -->|"--shared"| SharedIPC["TuiRuntimePort::Shared(RuntimeIpcClient)
private Runtime IPC v18"]
+ TUI --> Management["TuiManagementOwners
concrete domain providers"]
Other["Headless CLI · ACP · Peer Host · SDK Host"] --> Adapter["独立 first-party adapters"]
DesktopAdapter --> API["Agent Runtime API / owner ports"]
WebAS --> API
- EmbeddedAS --> API
+ Embedded --> API
SharedIPC --> API
+ Management --> Owners
Adapter --> API
API --> Coordinator["ConversationCoordinator"]
Coordinator --> Owners["Session / Tool / Permission / MCP owners"]
@@ -42,19 +44,21 @@ flowchart LR
Current 图只表示已经接线的业务请求路径;下方 composition 图中的虚线只表示启动期构造与依赖注入。
-### 1.2 Approved Embedded target
+### 1.2 Delivered Embedded composition
```mermaid
flowchart TB
- TUI["Embedded interactive TUI"] --> Composition["TuiBackend composition"]
- Composition --> RuntimePort["TuiRuntimePort"]
- RuntimePort --> Direct["DirectRuntimeTuiRuntime"]
- Direct --> API["Agent Runtime API / owner ports"]
- Composition --> Management["owner service/provider interfaces"]
+ TUI["Embedded interactive TUI"] --> Client["CliAgentRuntimeClient"]
+ Client --> RuntimePort["TuiRuntimePort::Embedded(AgentRuntime)"]
+ RuntimePort --> API["Agent Runtime API / owner ports"]
+ TUI --> Management["TuiManagementOwners"]
+ Management --> Providers["concrete owner providers"]
API --> Owners["Session / Tool / Permission / MCP owners"]
```
-这是已批准但尚未交付的 Embedded direct-runtime 目标,属于 Phase 5;在 Host 完成迁移和验证前,Current 图中的 Embedded App Server 路径保持不变。
+这是 Phase 5 已交付的 Embedded direct-runtime 组合。`TuiRuntimePort` 是
+`CliAgentRuntimeClient` 内部的私有部署枚举,不是公开 transport 或新的 Runtime owner;
+管理面由 `TuiManagementOwners` 组合具体 domain provider,不建立总括性的 management port。
### 1.3 Optional Shared App Server proposal
@@ -67,25 +71,25 @@ flowchart LR
Runtime --> Storage["Workspace / Session storage"]
```
-这是 Phase 6 的待评审提案,不是当前 Shared TUI 的必经链路,也不改变 Current 图中的 private Runtime IPC v17。只有完成连接治理、安全、恢复、取消、限制、性能和回滚门槛后,才可评审是否替换 v17;评审也可以决定长期保留 v17。
+这是 Phase 6 的待评审提案,不是当前 Shared TUI 的必经链路,也不改变 Current 图中的 private Runtime IPC v18。只有完成连接治理、安全、恢复、取消、限制、性能和回滚门槛后,才可评审是否替换 v18;评审也可以决定长期保留 v18。
### 1.4 Current implementation facts
| 范围 | 当前状态 |
|---|---|
| Embedded Desktop GUI | 当前继续使用 Desktop 事件投影和 Tauri adapter;按实际打开的本机 workspace 延迟取得并持有 Embedded ownership,不增加后台进程;Embedded direct Runtime 是后续 Host/infrastructure 目标,不在此处宣称已接线 |
-| Embedded interactive TUI | 当前仍通过同进程私有 App Server、in-memory transport、`AppServerClient` 和 `AppServerTuiBackend`;目标切换为 backend composition,由 `DirectRuntimeTuiRuntime` 直接调用 `AgentRuntime` typed API |
+| Embedded interactive TUI | `CliAgentRuntimeClient -> TuiRuntimePort::Embedded(AgentRuntime)` 直接调用同进程 Runtime;CLI 不再组装 App Server client/server、in-memory transport 或旧 backend |
| Embedded Headless CLI/Peer Host | 保留各自独立 Runtime adapter、展示和断流策略;不因交互式 TUI 迁移而强制使用 App Server |
| ACP/SDK Host | 使用同一个 Runtime 事件入口的 session-scoped 订阅;各自协议和进程生命周期保持独立 |
| Runtime ownership | Desktop、CLI、ACP、SDK Host 和现有 Server agent bootstrap 共用 Core owner;Embedded 取得共享锁,Shared TUI 取得独占锁,同一 workspace 上两种 deployment 互斥 |
| Session 写入 | BitFun Runtime 的持久化 Session 由 `SessionManager` 管理;同一存储位置中的同一 Session 同时只允许一个本机进程写入,list/view 等只读操作不受影响 |
| 当前 HTTP Server | 已组装 Embedded Runtime 和 `BitfunAppServer`,每个 `/ws` 连接通过 WebSocket transport 运行一条 App Server connection;当前固定 loopback、单用户且缺少连接级身份与作用域绑定,不构成远程或多用户 Server API |
-| Shared local IPC | 未发布的 v17 本机协议已有 discovery、实例锁、严格握手、Session 控制权、有界事件流和 cleanup;唯一 consumer 是第一方交互式 TUI compatibility adapter;是否由 Shared App Server 替换仍待评审与等价证据 |
-| Shared TUI | `bitfun --shared` / `bitfun chat --shared` 可列出、创建、恢复 Session,删除未被控制的空闲非当前 Session,通过 `/fork` 从完整历史或选中提示词之前创建分支,重命名当前 Session,读取 transcript,通过 **View subagents** 只读查看当前根 Session 的子会话并定向取消子会话活动 Turn,切换当前 Session 的 Agent mode/model,通过 `/reload [skills|instructions]` 刷新声明式上下文,通过 `/compact` 或 `/summarize` 压缩当前 Session 上下文,在 Turn 空闲时通过 `/diff` 读取 Runtime 绑定工作区的只读差异,提交/取消 Turn,处理 Permission 和 UserInput;当前管理面具体接线为 `SharedTuiBackend -> concrete AppManagementService -> existing product owners`,其中 `AppManagementService` 是现有 owner 上的 adapter;按 domain 拆分 owner service/provider 是 Phase 5 的后续工作;Account/Settings Sync、Worktree 和后续 External Application V2 未由当前 Shared Host 提供,默认仍是 Embedded |
+| Shared local IPC | 未发布的 v18 本机协议已有 discovery、实例锁、严格握手、Session 控制权、有界事件流和 cleanup;唯一 consumer 是第一方交互式 TUI compatibility adapter;是否由 Shared App Server 替换仍待评审与等价证据 |
+| Shared TUI | `bitfun --shared` / `bitfun chat --shared` 通过 `CliAgentRuntimeClient -> TuiRuntimePort::Shared(RuntimeIpcClient) -> Runtime IPC v18` 使用 Shared Runtime;Runtime 用例和事件保持 v18 的 controller、limits、取消和 unknown-outcome 合同。管理面不进入 v18,而由 `TuiManagementOwners` 组合具体 provider;MCP 进程属于 Shared Runtime Host,当前 Shared TUI 的 MCP 管理返回 typed unsupported,并要求退出 Shared clients、在 Embedded 模式管理后重启 Shared Runtime。其他 provider 按 deployment/Remote scope 返回真实 capability 或 typed unsupported |
| Shared GUI/Headless/ACP/SDK Host/Remote | 未交付,也不会由 `--shared` 隐式启用;Replay、Observer、通用 Controller transfer 和 Session archive 同样不在当前协议中 |
-因此当前交付的是 Embedded TUI App Server 与一条窄的、显式启用的 Shared TUI compatibility deployment;
-下一步将先把 Embedded TUI 切换为 direct Runtime adapter,不把它扩展成通用本机 Server。
+因此当前交付的是 Embedded TUI direct Runtime 与一条窄的、显式启用的 Shared TUI compatibility deployment;
+两者由同一个 CLI Runtime client 隐藏部署差异,不把 TUI 扩展成通用本机 Server。
具体 `EventQueue` 仍由 Core 产品装配;当前 Shared IPC 只把 TUI 必需的强类型操作和事件映射到同一个 Runtime owner,
没有公开协议承诺。是否以 App Server Shared transport 替换并删除它,由行为等价、性能、安全和回滚证据决定。
@@ -97,7 +101,7 @@ flowchart LR
| Embedded deployment | Runtime 与调用入口位于同一 Rust 进程 | 简化版 Runtime |
| Shared deployment | 同一 Runtime 由一个本机进程承载,多个第一方 Client 通过私有 IPC 使用 | 新 Runtime、公开 Server 或 Agent SDK |
| Embedded direct Runtime | Host 通过稳定 Rust typed facade 调用同进程 Runtime owner | 第二套 Runtime、Core singleton 直连或 App Server wire |
-| Embedded App Server | 当前 TUI 使用、仅作迁移前基线的同进程 App Server 实例和 in-memory transport | Embedded 的目标默认路径、后台进程或网络 Server;Phase 5 切换后删除 |
+| Embedded App Server | Phase 5 前 TUI 使用、现已删除的同进程 App Server 实例和 in-memory transport | 当前 Embedded 路径、后台进程或网络 Server |
| Shared App Server | 独立本机 Host 承载、由多个已认证 Rich Client 通过受控 transport 使用的 App Server | 公网 API 或每个 Client 一个 Runtime |
| Agent SDK Host | 将公开 SDK 合同映射到 Runtime API 的私有进程/adapter | CLI、Shared deployment 或 Plugin Host |
| Plugin Host | 运行 Node/Bun 和第三方插件代码的受监督子进程 | Agent Runtime 或 Rust IPC client |
@@ -117,14 +121,14 @@ flowchart TB
Desktop["Desktop GUI"] --> DesktopAdapter["Desktop / Tauri adapter"]
Web["Web UI"] --> AppServer["loopback WebSocket App Server"]
- EmbeddedTUI["Embedded TUI"] --> EmbeddedCurrent["in-process App Server · current"]
- EmbeddedTUI -. "target" .-> Composition["TuiBackend composition"]
- Composition --> RuntimePort["TuiRuntimePort"]
- RuntimePort --> DirectRuntime["DirectRuntimeTuiRuntime"]
- Composition --> Management["owner service/provider interfaces"]
+ EmbeddedTUI["Embedded TUI"] --> Client["CliAgentRuntimeClient"]
+ Client --> RuntimePort["TuiRuntimePort::Embedded(AgentRuntime)"]
+ EmbeddedTUI --> Management["TuiManagementOwners"]
+ Management --> Providers["concrete owner providers"]
DesktopAdapter --> API
AppServer --> API
- DirectRuntime --> API
+ RuntimePort --> API
+ Providers --> API
SharedCompat["Shared Runtime IPC · temporary compatibility"] --> API
Headless["Headless / ACP adapters"] --> API
SDK["SDK Host adapter"] --> API
@@ -132,8 +136,8 @@ flowchart TB
```
当前复用的是 Runtime API、权威事实和 owner;Web 额外使用 App Server wire,Embedded TUI
-当前使用 App Server、目标改为 direct Runtime adapter,Shared TUI 的共同 Runtime 行为使用
-private v17。TUI 管理面由 composition 按 domain 注入 owner service/provider,不随 Runtime
+已使用 direct Runtime,Shared TUI 的共同 Runtime 行为使用 private v18。TUI 管理面由
+`TuiManagementOwners` 按 domain 注入具体 owner provider,不随 Runtime
port 进入 Shared wire。各入口不复用 renderer、CLI 参数、SDK wire、远程认证或平台窗口生命周期。
任何新能力必须先进入既有 Runtime 或 owner service,再由 App Server、direct adapter 或其他独立
adapter 映射,禁止在 Embedded、Shared 或其他入口复制业务实现。
@@ -157,16 +161,16 @@ flowchart LR
- Core product assembly 创建事件 source,并维持旧消费队列的排空 task;第一方产品入口不再获得第二个订阅 API。
- App Server server 从注入的 `AgentEventSource` 转发需要连接边界的 Rich Client 权威事件;这些 client 不得绕过 App Server 订阅 Core `EventQueue`。
-- Phase 5 的 Embedded direct adapter 将通过 `AgentRuntime` 提供的 typed event/Permission subscription 直接订阅同一 Runtime owner,再映射为 `TuiRuntimePort` semantic event;它不得创建第二个 Core `EventQueue` owner 或把 Runtime 内部 receiver 暴露给 TUI。
+- Phase 5 的 Embedded direct path 已通过 `AgentRuntime` 提供的 typed event/Permission subscription 直接订阅同一 Runtime owner,再由 `CliAgentRuntimeClient` 映射为 TUI semantic event;restore 前先建立订阅,随后读取同一个 owner 的 coherent state/transcript/pending UserInput snapshot。它不得创建第二个 Core `EventQueue` owner 或把 Runtime 内部 receiver 暴露给 TUI。
- Headless CLI、Peer Host、ACP 和 SDK Host 从各自独立 Runtime adapter 订阅,不能直接持有 Core-specific event source。
- `bitfun-core` 的旧 event-source/builder API 仅保留为 deprecated 源码兼容 facade;它们委托给同一个 Core owner,不形成第二套运行时或第一方调用路径。
- 各 adapter 继续拥有自己的失败投影:TUI 标记当前视图不可信,Headless CLI 返回非成功终态,Peer Host 中断其拥有的 turns,ACP 取消 turn 并返回协议错误,SDK Host 终结 Query 并提供 `RestartHost` recovery。
- 当前 App Server 为每条 connection/stream 发送单调 sequence 和 connection-local cursor;`app/syncEvents` 返回当前连接的 cursor
- 与 pending Permission snapshot,`session/sync` 恢复 Session state、transcript、workspace binding 和 pending Permission。它没有跨连接
+ 与 pending Permission snapshot,v4 `session/sync` 从同一次权威 restore snapshot 恢复 Session state、transcript、workspace binding、pending Permission 和 pending UserInput。Rich Client 用 Session、active Turn 与 tool id 精确投影 pending UserInput,不从历史 `Started` 事件推断回答 channel 已就绪。Shared Runtime IPC Server 在执行 restore 前预订阅该 Session,使 snapshot 捕获期间产生的事件先进入连接 buffer。它没有跨连接
持久化 replay/resume:重连后的旧 cursor 不能继续消费,client 必须重新 initialize 并执行权威 sync。
-- Shared Runtime IPC v17 不复用 App Server cursor。它按自己的有界队列规则处理 lag/closed:Agent 流失效后 fail closed;Permission lag
+- Shared Runtime IPC v18 不复用 App Server cursor。它按自己的有界队列规则处理 lag/closed:Agent 流失效后 fail closed;Permission lag
尝试从 Runtime 的 pending 集合重建,重建失败或流关闭时取消当前 Turn 并退出。任何路径都不能把流失效伪装成透明恢复。
-- 当前旧 Embedded App Server 链路位于 Embedded 进程;direct-runtime 目标会删除 private in-memory transport、App Server task/thread 和其 JSON-RPC 编解码,不增加 SDK Host、跨进程 IPC 或后台进程依赖。
+- 旧 Embedded App Server 链路已经删除;当前 direct-runtime 不创建 private in-memory transport、App Server task/thread 或其 JSON-RPC 编解码,也不增加 SDK Host、跨进程 IPC 或后台进程依赖。
## 4. Process View · Level 1
@@ -277,11 +281,11 @@ sequenceDiagram
end
```
-当前私有协议(v17)只覆盖 TUI 已有用户旅程需要的窄操作:
+当前私有协议(v18)只覆盖 TUI 已有用户旅程需要的窄操作:
| 已支持 | 明确不支持 |
|---|---|
-| Health、只读 workspace-scoped main Agent 摘要、Session list/create、原子 restore(含 transcript 与 pending Permission)、删除未被控制的空闲 Session、当前 Session fork(含 transcript)、rename、Agent mode/model update、声明式上下文 reload、根 Session lineage 查询与后代 transcript 读取 | Session archive、跨 workspace attach、transcript 分页、模型目录/默认值和完整 Agent/Subagent 管理 |
+| Health、只读 workspace-scoped main Agent 摘要、Session list/create、coherent restore(含 state、transcript、pending Permission/UserInput)、删除未被控制的空闲 Session、当前 Session fork(含 transcript)、rename、Agent mode/model update、声明式上下文 reload、根 Session lineage 查询与后代 transcript 读取 | Session archive、跨 workspace attach、transcript 分页、模型目录/默认值和完整 Agent/Subagent 管理 |
| Turn submit/cancel、当前 Session 手动 context compaction、lineage 成员校验后的单个后代执行子树取消 | replay、cursor、resume event stream、独立的根级批量后代取消 API |
| pending/respond Permission、submit UserInput answers、只读 workspace diff | observer、通用 controller transfer、多 Session multiplex |
| 连接断开清理、Session-filtered events | detach/observer/通用 controller transfer、SDK callbacks、GUI/Remote/Peer/ACP/Headless wire |
@@ -305,13 +309,23 @@ sequenceDiagram
- v15 为后代 transcript 读取增加 `required_settled_turn_ids` 一致性前置条件:Runtime 必须确认这些 Turn 已由 owner 持久化为终态,否则返回 `outcome_unknown`,由 TUI 在同一绝对期限内退避重试;TUI 只保留事件投影和该读屏障,不合并或重写权威 transcript。后代取消同时携带用户实际看到的 `expected_active_turn_id`,并在 owner 锁内拒绝已经切换的 Turn,避免迟到操作取消后续执行。lineage 查询和 transcript 读取是每连接至多一个的可抢占推测读取;更新的请求会取消旧读取,使后代取消和 Session 切换不会排在慢 transcript I/O 之后。该行为不放宽 controller 校验,不引入 observer 或通用多路复用。
- v16 增加只读、workspace-scoped main Agent 摘要,用于 Shared TUI 与 Runtime host 的 selector 投影一致。启动页以 Runtime 启动工作区查询且不取得 Session lease;已有 Session 由 Runtime owner 解析其执行工作区并要求当前 controller。响应只包含逻辑 ID、描述、可选固定 model ID 与 ecosystem-neutral 的 external-source 分类;发现、审批、冲突消解、generation 与执行仍由既有 Agent Registry 和 external-source owner 负责,不经 IPC 暴露安装、变更、激活、Subagent 管理或 runtime lifecycle API。
- v17 扩展原子 restore,使响应带回 Runtime Session state;增加结构化 Session usage、等待指定 Turn settlement,以及记录本地命令
- transcript turn 的 operation。它补齐当前 Shared TUI 的行为,并为 Phase 5 将冻结的 `TuiRuntimePort` 提供 operation 基线;没有增加
+ transcript turn 的 operation。它补齐当前 Shared TUI 的行为,并成为 Phase 5 已交付 `TuiRuntimePort` 的 operation 基线;没有增加
replay、observer、通用 controller transfer、多 Session multiplex 或公开 SDK 能力。
+- v18 在原子 restore 中增加 Runtime 权威的 pending UserInput,并以 post-registration
+ `UserInputRequested` 与 exact-registration `UserInputResolved` 事件闭合 snapshot/订阅和迟到终态
+ 窗口。该字段改变 `SessionRestored` 的严格 wire shape,两个事件与 `SubmitUserAnswers` 也携带 Runtime owner 分配的
+ `session_id + turn_id + registration_sequence + tool_id` 完整身份。Shared adapter 按 sequence 单调合并
+ restore/live 授权事实,并在完整身份匹配后才调用 owner;旧 snapshot 不能覆盖同 tool id 的较新 ready,
+ 迟到的旧 resolved/terminal 也不能撤销较新的回答授权。
+ 这些字段改变严格 wire shape,因而旧 v17 client 必须在握手阶段被拒绝,不能在同一版本下容忍到
+ 运行期反序列化失败。Remote Connect 的 tracker、移动端和 IM Bot 复用相同完整身份;其
+ `answer_question` 是第一方私有命令且当前没有版本协商,因此旧移动端不能继续仅发送 `tool_id`,
+ 必须与 Desktop 同步升级,身份缺失或过期时一律 fail closed。
- 一个连接最多控制一个 Session、同时最多提交一个活动 Turn;一个 Session 同时只有一个 controller。create/restore/fork 在完整结果通过大小检查后才原子切换控制权,失败时保留原 Session。fork 只接受当前 controller 的空闲 Session;无选中 Turn 时复制到最新持久化 Turn,指定 `before_turn_id` 时只复制该 Turn 之前的历史。活动 Turn 期间不能切换或 fork Session,也不能修改其名称、Agent mode 或 model;删除只作用于非当前且未被任何连接控制的 Session。
- Submit 与手动 context compaction 都使用调用方已有的 `turn_id` 标识不确定结果;若操作超时,返回 `outcome_unknown`、关闭连接并按该 ID 取消。手动 compaction 要求当前 controller 且 Session 空闲,由 Core 通过与普通对话 Turn 共用的原子准入路径创建一个可审计 maintenance Turn,并在取得所有权后读取压缩上下文:planning 阶段允许取消,atomic commit 开始后忽略晚到取消并保持 Processing 直至终态持久化完成。maintenance Turn 保留在权威 transcript 中但不进入模型上下文,live/restored payload 使用同一 compression ID 和 `applied` 事实;commit 后的持久化故障发布明确失败终态而不是遗留 Processing。断连取消只有得到确认后才释放 Session 控制权;无法确认时继续隔离该 Session,直到 Runtime 进程退出。
- Session delete/rename 和 Agent mode/model update 复用既有 Runtime 端口和校验,Runtime 对最终结果保持权威并拒绝无效目标。它们都是有副作用操作;发送前编码或 frame 上限失败表示请求未执行,连接仍可使用。rename 写入失败时恢复旧 metadata:确认恢复后返回明确失败,无法确认时返回 `outcome_unknown`。Shared Client 在请求写入后响应超时或丢失连接时也返回 `outcome_unknown` 并断开连接。两种情况都不自动重试:rename 由用户恢复 Session 并核对当前值;delete 由用户重新打开 `/sessions` 核对目标是否仍存在。模型目录以及完整 Agent/Subagent 管理仍是同版本第一方产品事实,不加入 IPC;v16 的 main Agent 摘要只是 host-owned selector 所需的最小只读投影。
- 声明式上下文 reload 只失效当前 Session 的 instructions 缓存,并按目标复用 Skill Registry 刷新;它可在活动 Turn 中执行但不改写该 Turn,generation 保护保证下一条消息重建上下文。它不引入 watcher、热替换或第二套 Runtime owner。
-- v17 保留 `update current Session model` operation 及其 controller/idle/unknown-outcome 合同,但模型目录和默认值不进入该 wire。Phase 3 移除 TUI controller 对本机产品配置 owner 的直连后,`SharedTuiBackend` 通过其持有的具体 `AppManagementService` 保留模型选择和配置;Phase 5 再拆成 Host 注入的 owner service/provider adapter。该 capability 只描述当前本机 Shared CLI adapter,不伪装成 v17 或 Remote capability。
+- v18 保留 `update current Session model` operation 及其 controller/idle/unknown-outcome 合同,但模型目录和默认值不进入该 wire。模型选择和配置由 CLI Host 的 `TuiManagementOwners::model` provider 提供,current-Session model mutation 则由 `CliAgentRuntimeClient` 的 Shared 分支提交给 v18 Runtime owner。该 capability 只描述当前本机 Shared CLI adapter,不伪装成 v18 或 Remote capability。
- Agent 事件流 lag/closed 后 fail closed;Permission lag 先从 Runtime 权威 pending 集合重建,重建失败或流关闭时取消当前 Turn 并退出。路由到父 Session 的嵌套 Permission 与 AskUserQuestion 复用现有 TUI 交互,不新增第二套 UI 状态。
- Windows Shared Runtime 在初始化前把自身放入 kill-on-close Job;Unix 仅在应用内优雅退出路径中通过受管子进程组回收后代。Runtime 被 `SIGTERM`、`SIGKILL` 或崩溃直接终止后的 Unix 后代回收不在当前保证内。两者都只负责生命周期,不是安全沙箱。
- 最后一个连接离开后等待 30 秒再退出;新连接会取消 idle 退出。退出只删除自己发布的 discovery;Unix 下继任 owner 会在持有实例锁后清理同一 identity 的陈旧 socket。
@@ -334,8 +348,8 @@ flowchart LR
| 路径 | 数据边界 | 性能约束 |
|---|---|---|
-| Embedded Rich Client(目标) | TUI backend composition 通过 `TuiRuntimePort` 和 owner service/provider adapter 调用同进程 Agent Runtime | 不初始化 App Server client/server、in-memory transport、跨进程 IPC 或后台进程;Runtime port 保持与 Shared/Web 相同的行为、错误、权限和事件语义,但不要求共享 JSON-RPC;管理 capability 单独按 provider 可用性验证 |
-| Embedded App Server(当前迁移基线) | 迁移前由旧 `AppServerClient` 通过 private in-memory transport 调用同进程 App Server | 仅在 Phase 5 切换前有效;切换到 direct adapter 后删除,不保留回滚路径 |
+| Embedded interactive TUI(当前) | `CliAgentRuntimeClient` 通过 `TuiRuntimePort::Embedded(AgentRuntime)` 调用同进程 Runtime,管理面通过 `TuiManagementOwners` 的具体 provider 调用 owner | 不初始化 App Server client/server、in-memory transport、跨进程 IPC 或后台进程;Runtime 行为与 Shared v18 保持同一语义合同,但不共享 wire;管理 capability 单独按 provider 可用性验证 |
+| Embedded App Server(已退役基线) | Phase 5 前由旧 `AppServerClient` 通过 private in-memory transport 调用同进程 App Server | 已从 CLI 删除,不保留回滚路径 |
| Embedded non-Rich Client | Headless、ACP、Peer 和 SDK Host 的独立 adapter 以 Rust 类型调用 Runtime API | 不因 Rich Client 合同承担 App Server wire;保持各自协议和生命周期 |
| Shared request | Client 将 operation 编码一次并写入一个长度前缀 frame | 请求保持 128 KiB 上限;业务层只接收类型化 operation |
| Shared response/event | Server 将结果或事件编码一次后写出 | 响应/事件保持 8 MiB 上限;超限使事件流明确失效,不能无界分配 |
@@ -352,10 +366,11 @@ flowchart LR
flowchart TB
GUI["Desktop GUI"] --> DesktopAdapter["Desktop / Tauri adapter"]
Web["Web UI"] --> AppServer["App Server"]
- EmbeddedTUI["Embedded TUI"] --> AppServer
- SharedTUI["Shared TUI"] --> SharedCompat["Runtime IPC v17 compatibility"]
+ EmbeddedTUI["Embedded TUI"] --> Client["CliAgentRuntimeClient · direct"]
+ SharedTUI["Shared TUI"] --> SharedCompat["Runtime IPC v18 compatibility"]
DesktopAdapter --> API["Agent Runtime API / owner ports"]
AppServer --> API["Agent Runtime API / owner ports"]
+ Client --> API
SharedCompat --> API
CLI["Headless CLI adapter"] --> API
SDK["SDK Host adapter"] --> API
@@ -377,32 +392,32 @@ flowchart TB
```mermaid
flowchart LR
- TUI["Interactive TUI · Phase 5 target"] --> Composition["TuiBackend composition"]
- Composition --> Port["TuiRuntimePort"]
- Port -->|"Embedded default"| Direct["DirectRuntimeTuiRuntime"]
+ TUI["Interactive TUI · Phase 5 delivered"] --> Client["CliAgentRuntimeClient"]
+ Client --> Port["TuiRuntimePort"]
+ Port -->|"Embedded default"| Direct["Embedded(AgentRuntime)"]
Direct --> Runtime["AgentRuntime typed API"]
- Composition --> Management["owner service/provider interfaces"]
- Port -->|"Shared compatibility"| SharedAdapter["SharedIpcTuiRuntime"]
- SharedAdapter --> IPC["adapters/agent-runtime-ipc v17"]
+ TUI --> Management["TuiManagementOwners"]
+ Management --> Providers["concrete owner providers"]
+ Port -->|"Shared compatibility"| SharedAdapter["Shared(RuntimeIpcClient)"]
+ SharedAdapter --> IPC["adapters/agent-runtime-ipc v18"]
IPC --> Handler["CLI Shared handler"]
Handler --> Runtime
```
-第二张图只描述已批准但尚未交付的 Phase 5 TUI composition 目标。当前生产接线仍以第一张图为准:
-Embedded 使用 `AppServerTuiBackend`,Shared 使用 `SharedTuiBackend -> Runtime IPC v17`。Phase 5 中,
-CLI Host 将负责命令解析、TUI 状态、错误文案、direct Runtime adapter 选择和生命周期;
-backend composition 将由 `TuiRuntimePort` 承载共同 Runtime 行为,并按 domain 注入 owner service/provider
-接口承载管理面。Direct adapter 负责 typed request/result/event 映射,Shared adapter 负责私有 IPC
+第二张图描述 Phase 5 已交付的 TUI composition。CLI Host 负责命令解析、TUI 状态、错误文案、
+direct/shared Runtime 选择和生命周期;`CliAgentRuntimeClient` 内部的 `TuiRuntimePort` 承载共同
+Runtime 行为,`TuiManagementOwners` 按 domain 组合具体 owner provider 承载管理面。Embedded
+分支负责 typed request/result/event 映射,Shared 分支负责私有 IPC
的协议与连接控制;Agent Runtime 与 owner 负责 Session 校验、持久化和权威结果。TUI 业务代码
-不根据部署形态复制业务分支,Shared 是否替换 v17 仍按独立门槛决定。这里不定义一个总括性的
+不根据部署形态复制业务分支,Shared 是否替换 v18 仍按独立门槛决定。这里不定义一个总括性的
`TuiManagementPort`。
- CLI 不依赖 SDK Host,GUI/TUI 也不依赖公开 SDK package。
-- Phase 5 完成后,交互式 TUI 的启动页和会话页将复用 app-local backend composition;Embedded Runtime 调用使用 `DirectRuntimeTuiRuntime`,Shared Runtime 调用经 `SharedIpcTuiRuntime` 映射 private Runtime IPC v17。TUI 不直接依赖 Rust Runtime SDK、Core/Service owner 或 IPC operation。
-- 管理面由 composition 按 domain 直接注入 owner-owned 的稳定 service/provider trait;原始接口若暴露内部类型,或需要 TUI DTO、权限/上下文和 capability 裁剪,才抽取薄 facade。不得创建 `TuiManagementPort` 总接口,也不得把 App Server 的 `AppManagementService` 原样迁入 CLI/TUI。
-- Web 当前独立使用自己的 loopback WebSocket App Server Host,不进入 TUI backend composition。Shared 当前只有 private Runtime IPC v17;Shared App Server 只存在于第 1.3 节的 Phase 6 candidate 图中。
+- 交互式 TUI 的启动页和会话页复用 app-local `CliAgentRuntimeClient`;Embedded Runtime 调用使用 `Embedded(AgentRuntime)`,Shared Runtime 调用经 `Shared(RuntimeIpcClient)` 映射 private Runtime IPC v18。TUI view/controller 不直接依赖 Runtime/Core 或 IPC operation。
+- 管理面由 `TuiManagementOwners` 按 domain 组合 Model、Registry、MCP、Account、Settings Sync、Worktree、Hook、External Source/Command provider。不得创建 `TuiManagementPort` 总接口,也不得把总括性的 App Server management service 迁入 CLI/TUI。
+- Web 当前独立使用自己的 loopback WebSocket App Server Host,不进入 CLI Runtime/management composition。Shared 当前只有 private Runtime IPC v18;Shared App Server 只存在于第 1.3 节的 Phase 6 candidate 图中。
- Headless CLI 和 Peer Host 使用同一 Runtime 订阅入口,但分别保留确定性退出与 Peer fanout 语义;共享订阅入口不等于共享 renderer 或产品生命周期。
-- TUI 不是 Server;Phase 5 目标中的 Embedded Host 在同进程直接调用 Runtime,是否连接 Shared deployment 是部署选择,不改变 TUI 的 renderer/键位职责或行为合同。
+- TUI 不是 Server;Embedded Host 在同进程直接调用 Runtime,是否连接 Shared deployment 是部署选择,不改变 TUI 的 renderer/键位职责或行为合同。
- Agent SDK Host 只服务外部 SDK 合同,不成为第一方 rich-client 的通用底座。
- Headless CLI 默认继续 Embedded;CI 或测试可保持独立进程和独立 workspace,不承担后台实例成本。
- Tauri 仍负责窗口和桌面能力,并逐步收窄为 product Host adapter;Embedded 产品请求可由 direct Runtime adapter 承载,需要连接边界时再使用 App Server,未来也可以管理 Shared process 的启动/重连,但不拥有 Agent Runtime 业务生命周期。
@@ -414,8 +429,8 @@ backend composition 将由 `TuiRuntimePort` 承载共同 Runtime 行为,并按
```mermaid
flowchart TB
subgraph Embedded["Embedded · current"]
- TUI["Interactive TUI"] --> AppServer["in-process App Server"]
- AppServer --> Runtime["in-process Agent Runtime"]
+ TUI["Interactive TUI"] --> Client["CliAgentRuntimeClient"]
+ Client --> Runtime["in-process Agent Runtime"]
Headless["Headless / CI"] --> Runtime
end
subgraph Shared["Shared · current explicit --shared"]
@@ -425,40 +440,39 @@ flowchart TB
SharedRuntime --> Data
```
-当前默认交互式 TUI 通过同进程 App Server,Headless CLI 和 CI 通过各自 adapter 调用同进程
+当前默认交互式 TUI 通过 `CliAgentRuntimeClient` 直接调用同进程 Runtime,Headless CLI 和 CI 通过各自 adapter 调用同进程
Runtime。只有显式 `--shared` 的交互式 TUI 进入 Shared;同一 workspace 的两种部署互斥。
多开 TUI 增加 Client 进程和有界连接,不按 Client 数量复制 Runtime、Session owner 或 Plugin Host。
-#### 5.2.2 Approved Phase 5 Embedded target
+#### 5.2.2 Delivered Phase 5 Embedded path
```mermaid
flowchart LR
- TUI["Embedded interactive TUI"] --> Direct["Direct Runtime adapter · not yet delivered"]
+ TUI["Embedded interactive TUI"] --> Direct["CliAgentRuntimeClient · Embedded(AgentRuntime)"]
Direct --> Runtime["in-process Agent Runtime"]
Runtime --> Data["workspace + Session storage"]
```
-Phase 5 只把 Embedded interactive TUI 从当前 App Server 一次性切到 direct Runtime adapter,并删除
-旧 in-process App Server;Headless/CI 保留独立 adapter,Shared 继续使用前一张 Current 图中的
-private Runtime IPC v17。旧 App Server 不作为回滚配置保留。
+Phase 5 已把 Embedded interactive TUI 从 App Server 一次性切到 direct Runtime,并删除旧
+in-process App Server;Headless/CI 保留独立 adapter,Shared 继续使用前一张 Current 图中的
+private Runtime IPC v18。旧 App Server 不作为回滚配置保留。
-### 5.3 Scenario (+1) · Phase 5 target: rename current Session
+### 5.3 Scenario (+1) · current: rename current Session
-该场景将尚未交付的 Embedded direct adapter 与当前 Shared v17 行为放在同一等价目标中;它不表示
-Embedded Phase 5 已完成。
+该场景展示已交付的 Embedded direct path 与当前 Shared v18 共享同一行为合同。
```mermaid
sequenceDiagram
participant U as User
participant T as TUI adapter
- participant B as TuiBackend
- participant E as Embedded direct Runtime adapter
- participant S as Shared Runtime IPC v17 adapter
+ participant B as CliAgentRuntimeClient
+ participant E as Embedded(AgentRuntime)
+ participant S as Shared(RuntimeIpcClient) / v18
participant R as Agent Runtime
U->>T: /rename Auth refactor
T->>T: trim + require idle Session
- T->>B: typed TuiBackend request
+ T->>B: typed Runtime request
alt Embedded
B->>E: typed Runtime request
E->>R: AgentRuntime method
@@ -466,7 +480,7 @@ sequenceDiagram
R-->>E: applied / failed / outcome_unknown
E-->>B: mapped typed result
else Shared compatibility
- B->>S: Runtime IPC v17 request
+ B->>S: Runtime IPC v18 request
S->>R: owner port call
R->>R: validate ownership + persist
R-->>S: applied / failed / outcome_unknown
@@ -478,20 +492,20 @@ sequenceDiagram
Embedded 和 Shared 最终调用同一 `AgentRuntime::rename_session`。Runtime 只有在确认旧名称已保留时才返回明确失败;持久化恢复无法确认时,两种部署都返回 `outcome_unknown`。Shared 还会在请求已发送但权威响应丢失时返回该结果并关闭连接。用户恢复 Session、检查当前名称后再决定是否重试。
-### 5.4 Scenario (+1) · Phase 5 target: delete an idle Session
+### 5.4 Scenario (+1) · current: delete an idle Session
```mermaid
sequenceDiagram
participant U as User
participant T as TUI adapter
- participant B as TuiBackend
- participant E as Embedded direct Runtime adapter
- participant S as Shared Runtime IPC v17 adapter
+ participant B as CliAgentRuntimeClient
+ participant E as Embedded(AgentRuntime)
+ participant S as Shared(RuntimeIpcClient) / v18
participant R as Agent Runtime
U->>T: /sessions then Ctrl+D
T->>T: reject current or active target
- T->>B: typed TuiBackend request
+ T->>B: typed Runtime request
alt Embedded
B->>E: typed Runtime request
E->>R: AgentRuntime method
@@ -499,7 +513,7 @@ sequenceDiagram
R-->>E: applied / failed / outcome_unknown
E-->>B: mapped typed result
else Shared compatibility
- B->>S: Runtime IPC v17 request
+ B->>S: Runtime IPC v18 request
S->>R: owner port call
R->>R: existing delete owner
R-->>S: applied / failed / outcome_unknown
@@ -509,7 +523,7 @@ sequenceDiagram
T-->>U: remove only after applied
```
-Embedded 和 Shared 最终调用同一个 Agent Runtime。Shared Runtime Host 通过 v17 handler 调用 Runtime;它不是 Shared App Server。Shared Runtime Host 只在请求方没有活动 Turn、目标 Session 未被任何 Client 控制时调用 Runtime owner;`session_in_use` 和 `not_found` 保持结构化错误。TUI 复用现有单个 Session 异步任务槽位,不阻塞事件循环,也不自动重试结果不确定的删除。
+Embedded 和 Shared 最终调用同一个 Agent Runtime。Shared Runtime Host 通过 v18 handler 调用 Runtime;它不是 Shared App Server。Shared Runtime Host 只在请求方没有活动 Turn、目标 Session 未被任何 Client 控制时调用 Runtime owner;`session_in_use` 和 `not_found` 保持结构化错误。TUI 复用现有单个 Session 异步任务槽位,不阻塞事件循环,也不自动重试结果不确定的删除。
## 6. 隔离和生命周期原则
@@ -564,11 +578,11 @@ Session/Turn、事件恢复、Permission/UserInput、Controller、配置管理
## 9. 不变量
- 只有一套 Agent Runtime 业务实现;部署差异不能产生第二套 Session、Tool、Permission 或 MCP owner。
-- 当前入口使用第 1.1 节列出的 adapter;Embedded TUI 的下一步是从 App Server 切换到 direct Runtime adapter,Web/需要连接边界的 Rich Client 继续使用 App Server。
+- 当前入口使用第 1.1 节列出的 adapter;Embedded TUI 已切换到 direct Runtime,Web/需要连接边界的 Rich Client 继续使用 App Server。
- Client、窗口、Session 或 workspace 数量不会自动等量增加 Runtime 或 Plugin Host 进程。
- 当前 Shared Runtime IPC 是第一方 TUI 的 private compatibility transport,不成为公开 SDK、Remote、Peer、HTTP 或浏览器协议;是否由 App Server Shared transport 替换仍待评审。
-- Shared TUI 的 Model、Skill、Subagent、MCP、External Source V1 和 Hook 管理当前由 `SharedTuiBackend` 委托其持有的具体 `AppManagementService`;Phase 5 再拆成 CLI Host 显式装配的 owner service/provider adapter。这些管理 capability 不进入 Phase 5 的 `TuiRuntimePort` 或 v17 wire。Account/Settings Sync、Worktree 和后续 External Application V2 未由当前 Shared Host 提供并返回 typed unsupported。这不扩展 v17,不改变 Shared Runtime 对 Session/chat 的权威性,也不能用于 Remote workspace 的控制端本机回退。MCP service 的进程状态和 tool registry 只属于当前 CLI 进程,不即时重配已经运行的 Shared Runtime Host;跨进程 MCP 管理需要单独的同步/restart contract。
-- 默认 GUI/TUI/Headless CLI、ACP 与 SDK Host 保持 Embedded;Phase 5 完成后,只有 Embedded TUI 产品请求切换为直接调用同进程 Runtime,并删除旧 Embedded App Server。Desktop direct Runtime 是独立的已批准迁移步骤,不由 TUI Phase 5 的完成状态代替;Headless CLI、ACP 与 SDK Host 继续使用各自 adapter。只有交互式 TUI 的显式 `--shared` 选择 Shared。互斥按 `workspace + product` 生效,不再按入口名称缩窄。
+- TUI 管理由 CLI Host 的 `TuiManagementOwners` 显式组合具体 Model、Registry、MCP、Account、Settings Sync、Worktree、Native/External Hook、External Source/Command provider。这些 capability 不进入 `TuiRuntimePort` 或 v18 wire;provider 对当前 deployment/Remote scope 发布真实 availability 或 typed unsupported,不得回落控制端本机。Embedded MCP service 属于当前 CLI 进程;Shared MCP service 属于 Shared Runtime Host,当前 controller 不启动重复 service,也不提供跨进程管理。修改 Shared MCP 配置必须退出 Shared clients、在 Embedded 模式完成后重启 Shared Runtime;未来若需在线管理,应单独设计同步/restart contract。
+- 默认 GUI/TUI/Headless CLI、ACP 与 SDK Host 保持 Embedded;Embedded TUI 已直接调用同进程 Runtime,并删除旧 Embedded App Server。Desktop direct Runtime 是独立的已批准迁移步骤,不由 TUI Phase 5 的完成状态代替;Headless CLI、ACP 与 SDK Host 继续使用各自 adapter。只有交互式 TUI 的显式 `--shared` 选择 Shared。互斥按 `workspace + product` 生效,不再按入口名称缩窄。
- Account/session cloud sync 仍使用既有 Core compatibility 边界,不属于 Shared Runtime 支持。
- Remote workspace 的文件、凭据、进程和 Runtime 位于目标执行域,禁止静默回落本机。
- 未经真实 consumer 验证的接口不进入 wire;当前 wire 只包含表中列出的 Shared TUI 操作。
diff --git a/docs/architecture/agent-runtime-services-design.md b/docs/architecture/agent-runtime-services-design.md
index 671b5a611c..28af08e03f 100644
--- a/docs/architecture/agent-runtime-services-design.md
+++ b/docs/architecture/agent-runtime-services-design.md
@@ -68,7 +68,7 @@ Agent Runtime API 的逻辑归属与物理部署分离:相同归属模块可
私有 SDK Host 或目标机器 Runtime 中。任何 Rust 部署都只管理自己进程树内的服务与 Node/Bun Plugin Host;不能因为多个
GUI/TUI/Remote Client 连接就复制 Runtime 状态模块,或按 Client/Workspace 创建 Plugin Host。
-Rust Runtime SDK 以 `AGENT_RUNTIME_SDK_API_VERSION` 标记兼容边界。当前接口版本为 v6 preview:
+Rust Runtime SDK 以 `AGENT_RUNTIME_SDK_API_VERSION` 标记兼容边界。当前接口版本为 v7 preview:
小版本更新允许增加可选 builder hook、有默认实现的端口方法或注册表查询能力,但不得向外部可用
Rust 结构体字面量(struct literal)构造的 DTO 直接增加字段,也不得改变既有端口语义、错误分类、session / turn 标识含义或
默认 feature 依赖。任何需要调用方改写现有嵌入代码的变更,必须提升接口版本并提供兼容迁移路径。
@@ -89,6 +89,27 @@ v6 将完整 Rust Runtime SDK 从空默认编译面移入 `agent-runtime` owner
`deep-research` 或 `native-hook-settings`,不需要继承完整 Runtime。仓库内最小 SDK example 通过
`required-features = ["agent-runtime"]` 明确记录这一版本边界。
+v7 将声明式上下文刷新纳入可选的 `AgentContextReloadPort`。builder 未注入该端口时仍返回
+`MissingContextReloadPort`,已有 Runtime owner 的 session/turn/permission/error 语义不变;第一方
+Embedded TUI 通过这一稳定端口刷新 skills 或 instructions,不再反向依赖 Core 兼容 facade。
+同一版本还在 `AgentSessionRestoreResult` 增加 Runtime 权威的 `pending_user_inputs`,从 SDK
+re-export `PendingUserInput`,并在稳定 `ToolEventData` 中增加 post-registration
+`UserInputRequested` 与 exact-registration `UserInputResolved`。现有 Rust embedder 迁移时必须处理
+新增 restore 字段,并为 exhaustive event match 增加这两个 variant;前者只表示响应 channel 已注册,
+不得在更早的 `Started` 事件上开放回答,后者只撤销完整注册身份匹配的授权,不能让迟到的旧终态
+关闭同一 tool id 的较新问题。
+`AgentUserAnswersRequest` 同时改为携带 Runtime owner 分配的
+`session_id + turn_id + registration_sequence + tool_id` 完整身份;`register` 返回的 sequence 必须先写入
+pending fact,随后才能发布 ready 事件。v6 embedder 不能只按 `tool_id` 提交答案,迁移到 v7 时必须保存
+ready/restore 返回的完整身份;身份不匹配会 fail closed,且不会消费当前回答 channel。
+App Server v4 的 `session/sync` 也从同一次 restore snapshot 投影 pending UserInput,供 Rich Client
+在 late attach 或重连后恢复回答就绪状态;旧的 `session/restore` method 不承担这一同步合同。
+Remote Connect 的 Desktop tracker、Mobile Web、HarmonyOS 和 IM Bot 同样只把
+`UserInputRequested` 或 poll snapshot 中的完整身份视为可回答授权;`Started` 只负责展示工具调用。
+`answer_question` 命令因此携带同一组 `session_id + turn_id + registration_sequence + tool_id` 字段,
+旧的仅 `tool_id` 命令会 fail closed。Remote Connect 目前没有独立版本协商,第一方移动端必须与
+Desktop 同步更新;过期客户端需要刷新或升级后才能回答,服务端不得回退到按 tool id 查找最新 channel。
+
只要外部调用方仍必须导入 `bitfun-core`、启用 `product-full`、持有具体服务管理器、读取产品命令
注册表、理解 ACP/内部端口或依赖全局可变状态,公开 SDK 发布边界就不成立。公开 SDK 的完整
术语、能力等价和版本要求以 [`agent-sdk-product-architecture.md`](agent-sdk-product-architecture.md) 为准。
@@ -432,7 +453,7 @@ impl AgentRuntime {
该 Rust 接口是内部产品入口复用的当前形态,不是公开 Python/TypeScript SDK 的目标 API。它必须只接收
已组装的类型化部件,不负责创建
文件系统、终端、MCP、AI 客户端、Remote 提供方或产品命令。
-当前 v6 preview 接口以 message / attachment / metadata、默认标准执行目标和活动 Turn 文本 steer 作为最小输入形态;若把
+当前 v7 preview 接口以 message / attachment / metadata、默认标准执行目标、活动 Turn 文本 steer 和可选声明式上下文刷新作为最小输入形态;若把
model-round cancellation token、结构化 AgentInput 或更复杂的事件游标纳入公开 SDK,
必须分别评审 Rust Runtime SDK、SDK Host protocol 和公开 SDK API 的版本,并保留旧路径兼容。
diff --git a/docs/architecture/agent-sdk-product-architecture.md b/docs/architecture/agent-sdk-product-architecture.md
index 5d72ba2275..d7eea9e0f5 100644
--- a/docs/architecture/agent-sdk-product-architecture.md
+++ b/docs/architecture/agent-sdk-product-architecture.md
@@ -206,7 +206,7 @@ flowchart TB
与运行第三方 JS/TS 的 Node/Bun Plugin Host 不同;三者不能共享名称或业务归属。
当前代码已经交付显式启用的 Shared TUI 最小切片,包含本机 IPC、身份、握手、Session/Turn、当前 Session 的 name/Agent mode/model、Permission/UserInput、
-ownership 和生命周期治理;Model、Skill、Subagent 和 MCP 管理暂由 Shared CLI adapter 的本地 compatibility provider 保留,不属于 v17 或公开 SDK 合同。GUI、Headless CLI、ACP、SDK Host、Server/Remote 仍没有 Shared consumer。该图中的多入口逻辑复用是
+ownership 和生命周期治理;Model、Skill、Subagent 管理暂由 Shared CLI adapter 的本地 compatibility provider 保留,不属于 v18 或公开 SDK 合同。MCP 进程属于 Shared Runtime Host;controller 不启动重复 service,Shared TUI 的 MCP 管理当前返回 typed unsupported,并要求重启 Shared Runtime 后生效。GUI、Headless CLI、ACP、SDK Host、Server/Remote 仍没有 Shared consumer。该图中的多入口逻辑复用是
当前事实,除 Shared TUI 外的跨进程 Shared deployment 仍是目标架构。
### 4.3 各形态能做什么
diff --git a/docs/architecture/app-server-architecture.md b/docs/architecture/app-server-architecture.md
index 8cf3a63e62..bc8af1dc65 100644
--- a/docs/architecture/app-server-architecture.md
+++ b/docs/architecture/app-server-architecture.md
@@ -1,13 +1,13 @@
# App Server 架构设计
-> 状态:Embedded direct-runtime 已确定为下一步实现方向;Shared App Server 仍是待评审提案。
+> 状态:Embedded TUI direct-runtime Phase 5 实现切换已交付;统一跨部署 fixture、性能与升级兼容证据仍是后续门槛;Shared App Server 仍是待评审提案。
>
-> 基线日期:2026-08-13。
+> 基线日期:2026-08-14。
>
> 本文记录 Embedded direct-runtime 决策,以及需要连接边界时的 App Server 约束和 Shared transport 提案。具体 TUI 迁移阶段、接口盘点和当前缺口见
> [`tui-app-server-decoupling-refactor-plan.md`](../plans/tui-app-server-decoupling-refactor-plan.md);Agent Runtime 的进程、所有权和实例隔离见
> [`agent-runtime-deployment-design.md`](agent-runtime-deployment-design.md);产品 owner 与分层依赖见
-> [`product-architecture.md`](product-architecture.md)。迁移完成前,当前调用路径仍以已接线代码为准;Shared transport 未通过独立评审前继续使用 v17。
+> [`product-architecture.md`](product-architecture.md)。当前调用路径以已接线代码为准;Shared transport 未通过独立评审前继续使用 v18。
## 1. Decision and remaining proposal
@@ -19,20 +19,21 @@ wire。
- Embedded Host 直接持有产品组装得到的 `AgentRuntime` 和必要的 owner/provider
facade,通过 Rust 类型调用 Runtime 方法;不创建 `BitfunAppServer`、
`AppServerClient` 或 in-memory transport。
-- Embedded TUI 仍只依赖 app-local backend composition。TUI 不直接依赖 Runtime、Core
- singleton、具体 Service 实现或私有 IPC operation;composition 内部由窄的
- `TuiRuntimePort` 承载跨 Embedded/Shared 的 Runtime 行为,并由
- `DirectRuntimeTuiRuntime` 完成 direct request/result/event/error 映射。
+- Embedded TUI 只依赖 app-local `CliAgentRuntimeClient`。TUI view/controller 不直接依赖
+ Runtime、Core singleton、具体 Service 实现或私有 IPC operation;client 内部私有的
+ `TuiRuntimePort` 以 `Embedded(AgentRuntime)` / `Shared(RuntimeIpcClient)` 隐藏部署差异。
- Model、Skill、Subagent、MCP、Account、Settings Sync、Worktree、External Source 和
Hook 等管理面不进入 `TuiRuntimePort`,也不定义总括性的 `TuiManagementPort`。它们由
- backend composition 按 domain 直接注入 owner-owned 的稳定 service/provider trait;只有
- 原始接口暴露内部类型,或需要 TUI DTO、权限/上下文和 capability 裁剪时,才增加最薄的
- owner/provider facade。`AppManagementService` 继续是 App Server wiring,不原样迁移到 CLI。
+ `TuiManagementOwners` 按 domain 组合具体的 Model、Registry、MCP、Account、Settings Sync、
+ Worktree、Hook、External Source/Command provider。App Server 不保留无生产消费者的管理
+ service;当前通用 Server Host 未注入 management owner,因而已注册的管理方法返回 structured
+ unsupported,不能宣称 Web 管理已接线。未来只有真实 Host 消费者具备 Host-bound scope/auth
+ contract 后,才可增加按 owner 注入的窄 adapter。
- Web UI / WebSocket Host 继续使用 App Server,因为它们需要连接、transport、
认证、作用域和事件转发边界。
-- Shared TUI 继续使用 private Runtime IPC v17,直到 Shared App Server 通过
+- Shared TUI 继续使用 private Runtime IPC v18,直到 Shared App Server 通过
独立的鉴权、controller/lease、恢复、取消、背压、限制、性能和回滚门槛;本次
- Embedded 重构不自动替换或删除 v17。
+ Embedded 重构不自动替换或删除 v18。
- Headless CLI/CI、ACP、Peer Host 和公开 Agent SDK 继续使用各自经评审的
adapter,不因 Rich Client 的 App Server 合同被强制改用 App Server。
- App Server 仍是协议适配层,不接管 Agent Runtime、Service 或 Product Domain
@@ -48,47 +49,47 @@ Runtime owner、错误语义、事件语义和持久化规则。
| --- | --- | --- | --- | --- |
| A. App Server-first Rich Clients | Desktop、Web、Embedded/Shared TUI 复用一个 wire 与 typed client | 跨 Rich Client 合同和 fixture 最集中 | Embedded 编解码与 runtime/thread 成本;Shared 必须重新交付连接治理 | 仅适用于确实需要进程/网络边界的 Host;不再作为 Embedded 默认方案 |
| B. Deployment-specific product adapters(Embedded 采用) | Embedded 直接调用 Runtime API;Web/Remote/Shared 按部署选择 transport adapter,共享 owner ports | 消除同进程编解码和 server task,保持 Host 生命周期简单 | DTO、错误、恢复和行为 fixture 可能分叉;需要统一行为合同 | Direct adapter 无 owner 复制,且通过跨入口行为等价测试 |
-| C. Shared Runtime use cases with separate wires(保留) | 提取稳定用例/结果,Embedded 使用 Rust adapter,Shared 保留 v17 或后继 wire,Web 使用 App Server | 业务语义集中,同时允许 deployment-specific framing、安全和性能 | 需要清晰区分 use-case DTO 与 wire DTO;client 不能假装同一协议 | Shared transport 只有通过独立门槛后才可替换 v17 |
+| C. Shared Runtime use cases with separate wires(保留) | 提取稳定用例/结果,Embedded 使用 Rust adapter,Shared 保留 v18 或后继 wire,Web 使用 App Server | 业务语义集中,同时允许 deployment-specific framing、安全和性能 | 需要清晰区分 use-case DTO 与 wire DTO;client 不能假装同一协议 | Shared transport 只有通过独立门槛后才可替换 v18 |
-Embedded 已选择 B/C 的受限组合,A 不再是 Embedded 默认方案。Shared 仍可选择经门槛验证的 App Server transport,或把 private v17/后继协议保留为部署专用 wire;已有 DTO 或 adapter 不能替代该评审。
+Embedded 已选择 B/C 的受限组合,A 不再是 Embedded 默认方案。Shared 仍可选择经门槛验证的 App Server transport,或把 private v18/后继协议保留为部署专用 wire;已有 DTO 或 adapter 不能替代该评审。
### 1.2 Costs of the preferred candidate
- Embedded direct adapter 需要维护 Runtime typed API 与 TUI-facing contract 的映射;必须以行为 fixture 证明它没有复制 Session、Permission、Config 或 capability 状态。
-- 迁移前继续使用现有 Embedded App Server 作为行为基线;Phase 5 切换到 direct-runtime 后删除旧路径,不保留回滚 adapter,也不能在 direct adapter 返回 unsupported 后静默回退。
-- Shared App Server 需要重新交付 v17 已有的 framing、方向性 limits、鉴权、实例身份、controller/lease、断连取消、未知结果和空闲退出,不能只复用 method/DTO。
+- Phase 5 已删除旧 Embedded App Server 路径且不保留回滚 adapter;direct path 返回 unsupported 后也不得静默回退。
+- Shared App Server 需要重新交付 v18 已有的 framing、方向性 limits、鉴权、实例身份、controller/lease、断连取消、未知结果和空闲退出,不能只复用 method/DTO。
- Desktop 迁移必须划清 controller-local capability、Tauri 生命周期和工作区 Host capability;Web/Remote 扩展还需要独立的认证、授权和多租户资源治理。
### 1.3 当前实现状态
-目标架构与已交付能力必须分开描述:
+当前交付与后续目标必须分开描述:
| 范围 | 当前状态 | 目标 |
| --- | --- | --- |
-| Embedded TUI | 当前仍通过私有 `BitfunAppServer`、in-memory transport 和 `AppServerClient` 运行 | 切换为 backend composition,由 `TuiRuntimePort`、同进程 `AgentRuntime` 和 owner service/provider 直接提供用例 |
-| Shared TUI | 仍通过私有 Runtime IPC v17 连接独立 Runtime Host | 保留 v17;是否迁入 Shared App Server 由可靠性、安全、性能和回滚证据决定 |
+| Embedded TUI | `CliAgentRuntimeClient -> TuiRuntimePort::Embedded(AgentRuntime)` 直接调用同进程 Runtime;管理面由 `TuiManagementOwners` 组合具体 owner provider | 保持 direct-runtime,不恢复 CLI App Server 或总管理接口 |
+| Shared TUI | 仍通过私有 Runtime IPC v18 连接独立 Runtime Host | 保留 v18;是否迁入 Shared App Server 由可靠性、安全、性能和回滚证据决定 |
| Desktop GUI | 主要仍使用 Tauri command 和桌面事件投影 | Embedded 时使用 direct Runtime adapter;需要连接边界时使用 App Server,Tauri 保留平台能力 |
| Web Host | 当前 Server 已组装 Embedded Runtime,WebSocket 直接承载 `BitfunAppServer`;仅适用于 loopback 单用户模式 | 补齐连接身份、作用域绑定和 Host allowlist 后才能扩展部署范围 |
| App Server protocol/client | 已拆为 behavior-light crate,已有版本、能力、限制、错误和部分事件恢复类型 | 补齐 Host 注入能力、可靠性语义及跨 transport 合同测试 |
| App Server server | 已注册 app、agent、session、permission、TUI/workspace、git、config 和 i18n handler | 按真实 owner 和 Host 装配收窄能力,不以已存在 DTO 代替可用性证据 |
Shared TUI 继续使用 Runtime IPC 是当前 compatibility boundary。Embedded direct-runtime
-迁移不改变该边界。只有 Shared App Server 通过替换门槛后,才评审迁移或删除 v17;候选
-B/C 允许将 private v17 或后继协议保留为受控的长期物理 wire。
+迁移不改变该边界。只有 Shared App Server 通过替换门槛后,才评审迁移或删除 v18;候选
+B/C 允许将 private v18 或后继协议保留为受控的长期物理 wire。
-### 1.4 Migration and replacement gates
+### 1.4 Delivered invariants and replacement gates
-Embedded direct-runtime 完成迁移前必须满足:
+Embedded direct-runtime 当前必须持续满足:
| 门槛 | 必需证据 |
| --- | --- |
| Typed facade 与 owner | direct adapter 只调用稳定 Runtime/owner-provider facade;不暴露内部类型,不复制 Session、Permission、Config、capability 或事件状态 |
| 上下文与能力 | workspace、execution domain、permission、remote facts 和 capability 来自真实 Host/Runtime 组装;不从 UI 猜测,不静默本机回退 |
| 行为与事件 | 请求结果、事件顺序、pending Permission、取消、`unsupported`、lag/closed 和 `outcome_unknown` 与既有行为合同等价 |
-| 生命周期与性能 | 不创建 App Server client/server、in-memory transport 或额外 Runtime;订阅和任务可回收,并记录启动、延迟和内存对比 |
-| 迁移与删除 | 同一 TUI fixture 覆盖 direct 与旧 App Server;升级/降级读取兼容,旧路径仅作为迁移基线并在切换后删除 |
+| 生命周期与性能 | 不创建 App Server client/server、in-memory transport 或额外 Runtime;订阅和任务可回收。启动、延迟和内存对比仍需独立测量,不能从依赖闭包推断 wall-clock 收益 |
+| 迁移与删除 | direct 与 Shared v18 的 deployment-specific focused tests 覆盖关键合同;统一跨部署 fixture 与升级/降级读取验证仍是后续证据门槛,旧路径仅留在历史基线且已删除 |
-Shared App Server 只有满足以下连接治理门槛后才可替换 v17:
+Shared App Server 只有满足以下连接治理门槛后才可替换 v18:
| 门槛 | 必需证据 |
| --- | --- |
@@ -99,7 +100,7 @@ Shared App Server 只有满足以下连接治理门槛后才可替换 v17:
| 取消与未知结果 | disconnect/shutdown 取消、迟到响应、operation identity、`outcome_unknown` 查询/恢复和禁止盲重试 |
| Host capability | Desktop local effect 与工作区 capability 边界、provider 注入、Remote unsupported 和 Web/Remote auth 已定稿 |
| 生命周期与性能 | discovery、startup、idle exit、crash cleanup、延迟、吞吐和内存有预算与测量 |
-| 迁移与回滚 | 同一第一方 consumer 完成 opt-in 双栈 parity;升级/降级和 v17 rollback 可重复验证;删除条件有明确 owner 批准 |
+| 迁移与回滚 | 同一第一方 consumer 完成 opt-in 双栈 parity;升级/降级和 v18 rollback 可重复验证;删除条件有明确 owner 批准 |
## 2. 问题与目标
@@ -141,7 +142,7 @@ BitFun 的目标是让 direct adapter 与连接型 App Server adapter 共享可
| Rich Client | 需要持续会话、交互事件和产品管理面的第一方 GUI/Web/TUI | Headless automation、ACP、公开 SDK |
| Host | 组装 direct Runtime adapter 或 App Server、选择 transport、注入能力并管理生命周期的产品入口 | 新业务层、普通用户必须管理的 Server 产品 |
| Embedded direct Runtime | 与 Rich Client Host 同进程、由 `AgentRuntime` typed API 和 owner/provider facade 提供用例的部署方式 | 第二套 Runtime、App Server wire、跨进程后台服务 |
-| Embedded App Server | 迁移前 TUI 使用的同进程私有 App Server 实例和 in-memory transport | Embedded 的目标默认路径、网络 Server、共享后台进程;Phase 5 完成后删除 |
+| Embedded App Server | Phase 5 前 TUI 使用、现已删除的同进程私有 App Server 实例和 in-memory transport | 当前 Embedded 路径、网络 Server、共享后台进程 |
| Shared App Server | 由独立本机 Host 承载、允许多个已认证第一方 client 使用的 App Server 实例 | 公网 API、Agent SDK Host、每个 client 一个 Runtime |
| Runtime owner | 持有 Session、Turn、Permission、Tool/MCP、Hook、事件和持久化事实的既有模块 | App Server handler 或 UI read model |
| Host capability | 当前 Host 确实组装并允许调用的产品能力 | schema 中存在的方法全集 |
@@ -161,7 +162,7 @@ flowchart LR
Web --> Host
TUI --> Host
Host --> Route{"Deployment route"}
- Route -->|"Embedded direct · approved target"| Direct["Direct Runtime adapter"]
+ Route -->|"Embedded direct · current TUI"| Direct["CliAgentRuntimeClient"]
Route -->|"Web / candidate Shared · Phase 6"| Client["App Server Client"]
Client --> Transport["Host-selected transport"]
Transport --> Server["App Server"]
@@ -177,8 +178,8 @@ capability/allowlist 和平台能力。App Server handler 负责 method 合同
持久化和权威状态仍由对应 owner 提交。
图中的 Web 分支是当前 loopback WebSocket App Server 路径;Shared 分支仅表示 Phase 6
-candidate,不是当前或已批准的必经链路。当前 Shared TUI 仍只使用 private Runtime IPC v17,
-Web 也不经过 TUI backend composition。
+candidate,不是当前或已批准的必经链路。当前 Shared TUI 仍只使用 private Runtime IPC v18,
+Web 也不经过 CLI Runtime/management composition。
### 5.1 四层合同
@@ -189,24 +190,24 @@ Web 也不经过 TUI backend composition。
| Host 合同 | transport、可用能力、限制、身份、作用域、生命周期和 controller-local provider | 复制业务规则或权威状态 |
| Owner 合同 | Runtime/Service/Product Domain 的业务事实、校验和提交 | JSON-RPC、Tauri、WebSocket、Ratatui |
-行为合同是 Embedded direct-runtime 与 Shared wire 等价的核心;迁移前的 Embedded App Server
-仅作为 direct-runtime 切换前的行为基线,不构成迁移后的第三条运行路径。其中,Phase 5 将引入的
-`TuiRuntimePort` 是 Shared IPC operation 集合在 TUI 侧的窄语义边界;管理 service/provider
+行为合同是 Embedded direct-runtime 与 Shared wire 等价的核心;已删除的 Embedded App Server
+仅是 direct-runtime 切换前的行为基线,不构成第三条运行路径。其中,Phase 5 已交付的
+`TuiRuntimePort` 是 `CliAgentRuntimeClient` 内部基于 Shared IPC operation 集合形成的窄语义边界;管理 service/provider
不是 Shared Runtime wire 的组成部分。行为合同不要求三者共享 JSON;只要 Runtime port
在断连、超时、事件落后、权限、取消和 unknown outcome 上保持明确且可验证的语义,
即可共享同一 Runtime 用例合同。管理面按各自 service/provider 的可用性单独验证。
-## 6. Phase 5 Embedded interactive TUI target
+## 6. Phase 5 Embedded interactive TUI(实现切换已交付)
-本节只定义交互式 TUI 的 Phase 5 目标路径。它不描述 Desktop 或 Web 的迁移完成状态;
+本节定义交互式 TUI 的 Phase 5 当前路径。它不描述 Desktop 或 Web 的迁移完成状态;
Desktop direct Runtime 是独立的已批准迁移步骤,尚未实施。
```text
Embedded interactive TUI
-> Host adapter
- -> TuiBackend composition
- -> TuiRuntimePort -> DirectRuntimeTuiRuntime -> AgentRuntime typed API
- -> owner-owned service/provider interfaces (management only)
+ -> CliAgentRuntimeClient
+ -> TuiRuntimePort::Embedded(AgentRuntime) -> AgentRuntime typed API
+ -> TuiManagementOwners -> concrete owner providers (management only)
-> Runtime API / owners
```
@@ -220,20 +221,20 @@ Embedded Host 必须:
不创建第二个 Core `EventQueue` 订阅或第二份 read model 权威状态。
4. 将 Runtime/domain error 映射为 Runtime port 或对应 owner service 的 TUI error,保留 `unsupported`、取消、
`outcome_unknown` 等可观察语义。
-5. 在 Host 退出时取消并回收由 direct adapter 创建的订阅和任务,并使用与 Shared
- 和 Web 路径相同的行为 fixture。
+5. 在 Host 退出时取消并回收由 direct adapter 创建的订阅和任务。当前 direct 与 Shared
+ 分别使用 focused behavior tests;统一跨部署 fixture 仍是后续验证门槛,不能用源码形状断言替代。
Embedded 可以省略只对跨进程多客户端有意义的机制:endpoint discovery、进程 token、
外部实例锁、多客户端 controller lease、frame 编解码和空闲后台退出。省略这些机制
不能改变请求结果、事件顺序、取消结果、权限边界或 capability 语义。
-迁移窗口内可以用现有 Embedded App Server 与 direct adapter 做行为对照,但不保留可选的
-rollback adapter,也不得在 direct adapter 返回 unsupported 后静默回退。完成 direct-runtime
-行为、性能和升级兼容验证后,Phase 5 必须删除旧路径。
+Phase 5 已删除旧 Embedded App Server 和可选 rollback adapter;direct path 返回 unsupported
+后不得静默回退。后续统一行为 fixture、性能测量和升级兼容验证只覆盖 current direct path 与
+Shared v18,不恢复第三条运行路径;这些证据尚未全部完成,不得写成当前交付结果。
## 7. Shared deployment
-当前 Shared deployment 由独立 Runtime Host 通过 private Runtime IPC v17 服务交互式 TUI,不运行 App Server。若后续采用 Shared App Server,则目标拓扑为一个本机 App Server Host 承载一个 Runtime owner,多个第一方 Rich Client 通过受控 Pipe、UDS 或等价私有 transport 连接:
+当前 Shared deployment 由独立 Runtime Host 通过 private Runtime IPC v18 服务交互式 TUI,不运行 App Server。若后续采用 Shared App Server,则目标拓扑为一个本机 App Server Host 承载一个 Runtime owner,多个第一方 Rich Client 通过受控 Pipe、UDS 或等价私有 transport 连接:
```mermaid
flowchart LR
@@ -257,7 +258,7 @@ flowchart LR
- 无客户端且无活动任务时的受控空闲退出。
- 副作用请求在超时或断连后的 `outcome_unknown` 结果。
-当前 Runtime IPC v17 已具有 128 KiB request、8 MiB response/event、token、实例身份、controller/lease、断连取消、有界事件流、`outcome_unknown` 和空闲退出等合同。在 App Server Shared transport 逐项获得等价测试前,该 IPC 可以作为 Shared TUI 的兼容 adapter 保留;不得先切换 transport 再以功能回退换取表面统一。
+当前 Runtime IPC v18 已具有 128 KiB request、8 MiB response/event、token、实例身份、controller/lease、断连取消、有界事件流、`outcome_unknown` 和空闲退出等合同。在 App Server Shared transport 逐项获得等价测试前,该 IPC 可以作为 Shared TUI 的兼容 adapter 保留;不得先切换 transport 再以功能回退换取表面统一。
## 8. Desktop GUI 与 Tauri
@@ -322,7 +323,9 @@ WebSocket 是 App Server 的一种 transport,不是另一套业务 API。Web H
4. transport limits 必须反映当前连接的真实限制;不能把 server 内部默认值宣传为所有 transport 的通用事实。
5. method 级 allowlist 必须是 capability 声明的子集,fallback handler 不能扩大可调用面。
-当前实现中通用 App Server 初始化声明 16 MiB frame,而 WebSocket Host 接收上限为 256 KiB,Shared Runtime IPC 又区分 128 KiB request 与 8 MiB response/event。目标合同需要表达方向和 transport 的真实限制;在扩展 schema 前,Host 至少必须返回不超过底层 transport 的有效上限。
+当前 App Server 的 Permission capability 由注入 Runtime 是否实际持有 Permission request manager 决定;缺少 owner 时初始化返回 `Unavailable`,Permission snapshot 与 command 均 fail closed,不用空结果伪装可用。
+
+当前通用 App Server 默认声明 16 MiB frame;WebSocket Host 会覆盖为其实际 256 KiB 接收上限,避免客户端信任超过底层 transport 的数值。Shared Runtime IPC 仍区分 128 KiB request 与 8 MiB response/event;若后续协议需要分别表达请求/响应方向,应扩展稳定 schema,而不是再次在入口硬编码不一致上限。
## 11. 事件、恢复与取消
@@ -336,6 +339,8 @@ WebSocket 是 App Server 的一种 transport,不是另一套业务 API。Web H
- `closed`、`lagged`、`invalidated` 和 `recoverable` 的明确区分。
- snapshot/sync 或要求重新装载 Session 的恢复指令。
+当前 Permission sync 是连接内的 at-least-once 合同。Runtime owner 先把 request 放入 pending,再完成 audit 并发布 `Asked`;因此 sync snapshot 已包含某个 `request_id` 时,同一请求仍可能在返回 watermark 之后作为事件到达。App Server 必须保留这种重复并保证请求不会落入 snapshot/event 空窗,client 按 `request_id` 幂等合并。该合同不表示跨连接持久 replay 或 exactly-once delivery。
+
client 落后、frame 超限或连接中断时不能假装事件完整。可恢复流从 server 确认的 cursor/snapshot 继续;不可恢复流进入 invalidated,UI 必须停止基于旧 read model 提交依赖状态的新操作,直到 resync 完成。
取消属于行为合同:
@@ -388,6 +393,16 @@ Embedded direct invocation 可以依赖同进程构造身份,但仍必须传
- server wiring 可以依赖生产 handler 所需的明确 owner feature,但禁止选择 `bitfun-core/product-full`。
- 新 domain 只能增加真实 handler 所需的最窄 owner feature,并通过边界检查证明依赖方向。
- protocol DTO 不复制 Runtime 内部对象;只暴露 Rich Client 需要的稳定字段和 read model。
+- App Server protocol v4 将 post-registration `UserInputRequested` 与 exact-registration
+ `UserInputResolved` 纳入稳定 Agent event wire,
+ 并在 ready event 与 `agent/submitUserAnswers` 中传递 Runtime owner 分配的
+ `session_id + turn_id + registration_sequence + tool_id` 完整身份,使所有首方 Rich Client 只在
+ Runtime 注册回答 channel 后开放提交,并仅在完整身份匹配时撤销授权;身份不匹配不会消费当前 channel,
+ 迟到的旧终态不会关闭同 tool id 的较新问题。该 event/request 增量会破坏旧
+ client 的严格反序列化,因此 `PROTOCOL_VERSION` 与 `MIN_PROTOCOL_VERSION` 同步升至 4;
+ connection 在成功 `app/initialize` 前不转发业务请求或事件,v2/v3 client 必须在协商阶段
+ fail closed。v4 `session/sync` 使用同一次权威 restore snapshot 返回 transcript 与 pending
+ UserInput,使 late attach/reconnect 的 Rich Client 不依赖已经错过的 ready 事件。
- `app-server-protocol` 是 TypeScript wire schema 的唯一导出 owner;`app-server/ts`
仅保留为兼容转发。owner 对象到 wire read model 的转换留在 server adapter,不能为了
生成类型把 Core、Agent Runtime 或 Service 实现依赖下沉到 protocol。
@@ -398,34 +413,33 @@ Embedded direct invocation 可以依赖同进程构造身份,但仍必须传
- transport 实现留在 Host/adapter 边界,generic role/transport helper 保持 schema-free。
- App Server 不持有第二份 Session、Permission、Config 或 capability 权威状态。
-## 15. 迁移顺序
+## 15. 已交付迁移与后续顺序
迁移按行为闭环推进,不按 method 数量推进:
- 1. **锁定 Runtime 行为合同**:按当前 Shared IPC v17 operation 集合冻结窄
- `TuiRuntimePort`,稳定 Runtime request/response/event 类型、错误、能力、取消和事件语义;
- 为 direct Embedded 和 Shared 增加同一 Runtime 行为 fixture,迁移前可用旧 App Server 建立行为基线。
- 2. **拆分 TUI backend composition**:将当前单体 `TuiBackend` 拆为 `TuiRuntimePort` 与按
- domain 注入的 owner service/provider 接口;不定义总括性的 `TuiManagementPort`。管理 service
- 能直接复用稳定 owner trait 的直接注入;只有需要 TUI DTO、权限/上下文或 capability 裁剪时
- 才增加薄 facade。
- 3. **迁移 Embedded TUI**:实现 `DirectRuntimeTuiRuntime`,将 Runtime 调用从
- `AppServerTuiBackend` 移出;再移除 in-memory transport、Embedded `BitfunAppServer`、
- server thread 和 TUI-facing App Server client 依赖。管理面使用 owner-owned service/provider,
- 不把具体 `AppManagementService` 原样搬入 CLI/TUI。
+1. **已交付 Runtime seam**:`CliAgentRuntimeClient` 内部以私有 `TuiRuntimePort` 组合
+ `Embedded(AgentRuntime)` 与 `Shared(RuntimeIpcClient)`,统一 Runtime request/result/event、
+ 错误、取消和事件语义;Shared 分支继续使用 private Runtime IPC v18。
+2. **已交付管理 owner 组合**:CLI 通过 `TuiManagementOwners` 显式组合具体 domain provider;
+ 不定义总括性的 `TuiManagementPort`,也不把总括性的 App Server management service 搬入 CLI。
+3. **已交付 Embedded TUI direct-runtime**:已移除 CLI in-memory transport、Embedded
+ `BitfunAppServer`、server thread、TUI-facing App Server client 和旧 `TuiBackend` family;
+ 不保留 rollback adapter。
4. **迁移 Desktop GUI**:Embedded 时使用 direct Runtime adapter;Web/Shared 或需要连接治理
的场景保留 App Server。Tauri 继续承载平台能力与生命周期。
5. **补齐 Shared 语义**:把 authentication、instance identity、controller/lease、framing、
背压、断连取消、idle exit、event recovery 和 `outcome_unknown` 纳入 App Server Host/transport。
6. **评审 Shared TUI 迁移**:Shared App Server 只有通过 1.4 节门槛后才可替换 Runtime IPC
- compatibility adapter;v17 是否删除由独立评审和验证证据决定。若选择 B/C,
- 记录 v17 的长期 owner、版本和删除条件。
+ compatibility adapter;v18 是否删除由独立评审和验证证据决定。若选择 B/C,
+ 记录 v18 的长期 owner、版本和删除条件。
7. **收紧 Web Host**:由 Host 注入 allowlist、作用域和真实 limits;完成安全绑定前保持 loopback
单用户限制。
-8. **删除旁路**:移除 Rich Client 的重复 Runtime 事件投影、旧 Embedded App Server route 和
- 无生产消费方的兼容代码。
+8. **继续删除旁路**:不恢复 Rich Client 的重复 Runtime 事件投影、旧 Embedded App Server
+ route 或无生产消费方的兼容代码。
-迁移期间不得在 App Server 返回 unsupported 后静默调用旧 Tauri/Core/IPC 路径。需要暂存旧路径时,必须由 Host 在启动时明确选择完整 adapter,且 UI 只看到一个 `TuiBackend` 或 frontend infrastructure 接口。
+任何入口不得在 App Server 或 direct owner provider 返回 unsupported 后静默调用旧
+Tauri/Core/IPC 路径。Host 必须在启动时明确选择完整 adapter;CLI UI 只看到
+`CliAgentRuntimeClient` 和具体 management provider 投影。
## 16. 验证与完成标准
@@ -433,8 +447,8 @@ Embedded direct invocation 可以依赖同进程构造身份,但仍必须传
- protocol serialization、版本上下界、未知字段和类型化错误合同测试。
- 同一用例在 Embedded direct-runtime、Shared process transport 和 WebSocket App Server(适用时)
- 的行为等价测试;迁移前旧 Embedded App Server 仅用于建立基线,不作为持续测试路径。
-- `TuiRuntimePort` coverage:当前 Shared IPC v17 的每个 Runtime operation 都有对应的
+ 的行为等价测试;旧 Embedded App Server 仅存在于历史基线,不作为持续测试路径。
+- `TuiRuntimePort` coverage:当前 Shared IPC v18 的每个 Runtime operation 都有对应的
port 行为测试;管理 service/provider 则单独验证 capability、权限、unsupported 和 Remote
fail-closed,不以 Runtime port parity 代替管理面验证。
- Host capability/provider/allowlist 组合测试,以及真实 transport limit 测试。
@@ -450,21 +464,21 @@ Embedded direct invocation 可以依赖同进程构造身份,但仍必须传
只有交互式 TUI 同时满足以下条件,TUI/App Server 解耦才算完成;这一定义不涵盖
Desktop 的独立 direct Runtime 迁移:
-1. Embedded TUI 的产品请求和订阅经过 `TuiAgentClient` 的 backend composition;Runtime
- 行为经过 `TuiRuntimePort -> DirectRuntimeTuiRuntime`,管理能力经过对应 owner service/provider,
+1. Embedded TUI 的产品请求和订阅经过 `CliAgentRuntimeClient`;Runtime
+ 行为经过 `TuiRuntimePort::Embedded(AgentRuntime)`,管理能力经过 `TuiManagementOwners` 的具体 owner provider,
TUI view/reducer 不执行 backend I/O。
2. direct adapter 只调用既有 Runtime API、owner-owned service/provider 和必要的薄 facade,
不复制 Session、Permission、Config、capability 或事件权威状态,也不定义 `TuiManagementPort`。
-3. Embedded direct-runtime 与 Shared v17 的行为合同覆盖请求结果、事件顺序、
+3. Embedded direct-runtime 与 Shared v18 的行为合同覆盖请求结果、事件顺序、
取消、权限、unsupported、断连和 unknown outcome。
4. capability、作用域和 remote facts 来自真实 Host/Runtime 组装;Remote workspace 不存在
controller-local fallback。
5. Embedded 不创建 App Server client/server、in-memory transport 或额外 Runtime 进程;
direct adapter 的任务和订阅在 Host 退出时可回收。
6. App Server 仍可被 Web/Shared Host 使用,且 handler 不持有业务权威状态或复制 owner 策略。
-7. 旧 Embedded App Server 路径已删除且不再作为 rollback adapter;70 方法单体 `TuiBackend`
- 不再作为稳定接口边界。
-8. Shared transport 若要替换 v17,仍需单独满足鉴权、lease、取消、背压、限制、失效和生命周期
+7. 旧 Embedded App Server 路径已删除且不再作为 rollback adapter;旧单体 `TuiBackend`
+ family 不再作为稳定接口边界。
+8. Shared transport 若要替换 v18,仍需单独满足鉴权、lease、取消、背压、限制、失效和生命周期
等价门槛。
9. 上述合同、行为、安全、依赖、升级兼容和跨入口测试全部通过。
@@ -492,5 +506,5 @@ Desktop 的独立 direct Runtime 迁移:
这些待决项会影响 Shared transport 的选择,不能被实现默认值或迁移进度替代。Shared 评审结论必须记录
所选 transport、拒绝其他方案的理由、门槛 owner、验证证据和回滚/删除条件。Embedded direct-runtime
-迁移完成后,旧 Embedded App Server 必须删除且不保留回滚路径;WebSocket App Server 与 Shared v17
+迁移完成后,旧 Embedded App Server 必须删除且不保留回滚路径;WebSocket App Server 与 Shared v18
按各自部署合同继续有效。
diff --git a/docs/architecture/product-architecture.md b/docs/architecture/product-architecture.md
index 7be8042776..6e64dc67f8 100644
--- a/docs/architecture/product-architecture.md
+++ b/docs/architecture/product-architecture.md
@@ -22,8 +22,8 @@ Headless CLI 与各产品入口的统一心智见
状态共享、隔离、容量与 Plugin Host 关系见
[`agent-runtime-deployment-design.md`](agent-runtime-deployment-design.md);Desktop GUI、Web UI 和交互式 TUI 的产品后端边界、
Embedded direct-runtime、Shared App Server 及迁移约束见
-[`app-server-architecture.md`](app-server-architecture.md)。Embedded direct-runtime 已确定为下一步实现方向;Shared App Server
-仍是待评审提案。在迁移或决策门槛通过前,当前调用路径和稳定 owner 边界仍以本文及已接线代码为准。其他已批准的详细设计与本文件冲突时,以本文件为准。
+[`app-server-architecture.md`](app-server-architecture.md)。Embedded TUI direct-runtime Phase 5 已交付;Shared App Server
+仍是待评审提案。当前调用路径和稳定 owner 边界以本文及已接线代码为准。其他已批准的详细设计与本文件冲突时,以本文件为准。
Cargo feature、第三方依赖 owner、测试目标和本地/CI 验证分工见
[`rust-build-dependency-boundaries.md`](rust-build-dependency-boundaries.md)。该文档补充本架构的构建视图,不改变本文定义的运行时 owner 和分层依赖方向。
@@ -309,7 +309,7 @@ flowchart LR
### 2.4 Physical View · Level 0
Physical View 展示当前生产环境中可执行单元到设备、主机和存储的映射。Desktop、CLI、ACP 和 SDK Host 使用 Embedded Runtime;
-Embedded 交互式 TUI 当前仍在同一 CLI 进程内通过私有 App Server 使用 Runtime;交互式 TUI 也可以显式连接当前 Shared Runtime IPC。
+Embedded 交互式 TUI 当前在同一 CLI 进程内通过 `CliAgentRuntimeClient` 直接使用 Runtime;交互式 TUI 也可以显式连接当前 Shared Runtime IPC。
Desktop GUI 当前仍使用 Tauri adapter;独立 direct Runtime 迁移尚未实施。当前 loopback Web Server 已承载 Embedded Runtime 和 WebSocket App Server;Relay Server
不承载 Agent Runtime。
@@ -375,7 +375,7 @@ flowchart LR
| Deployment unit | Main contents |
|---|---|
| Desktop App | Web UI、Tauri Host、embedded Agent Runtime;当前 Desktop 产品请求使用现有 Tauri adapter |
-| CLI App | 交互式 TUI 当前通过 in-process App Server 使用 Embedded Runtime;Headless、Peer 保留独立 adapter;可显式使用 Shared TUI |
+| CLI App | 交互式 TUI 由 `CliAgentRuntimeClient` 选择 direct Embedded Runtime 或显式 Shared Runtime IPC v18,管理面由 `TuiManagementOwners` 组合具体 provider;Headless、Peer 保留独立 adapter |
| Shared Runtime | 私有本机 IPC;当前只有交互式 TUI consumer;是否迁入 Shared App Server transport 仍待评审与等价证据 |
| ACP | Embedded Agent Runtime、ACP 协议生命周期 |
| SDK Host | 私有跨进程 adapter;公开 SDK 产品尚未交付 |
@@ -423,18 +423,18 @@ flowchart TB
## 3. 接口边界
BitFun 只保留四个稳定业务接口边界;工具、事件和权限作为归属子接口被复用,不在插件层重复定义。App Server
-是 Agent Runtime API 和其他 owner 接口面向当前 Web/Embedded TUI,以及未来确实需要连接边界的 Rich Client 的版本化 wire adapter,不新增第五个
-业务 owner 或能力分类。Embedded TUI 的目标改为 direct Runtime adapter;Shared 是否使用 App Server 由 4.3 节所述评审决定。本文使用
+是 Agent Runtime API 和其他 owner 接口面向当前 Web,以及未来确实需要连接边界的 Rich Client 的版本化 wire adapter,不新增第五个
+业务 owner 或能力分类。Embedded TUI 已改为 direct Runtime;Shared 是否使用 App Server 由 4.3 节所述评审决定。本文使用
“接口”描述可被调用或依赖的能力面;只有描述跨进程消息封装、结构化 schema、序列化对象或强兼容约束时才使用
“契约”;只读状态视图表示从权威状态派生出的查询结果。
-Phase 5 将为 Embedded/Shared TUI 冻结窄的 `TuiRuntimePort`,其范围按 Shared IPC v17
+Phase 5 已在 `CliAgentRuntimeClient` 内部交付窄的 `TuiRuntimePort`,其范围按 Shared IPC v18
实际承载的 Session、Turn、Permission/UserInput、Workspace、lineage、usage/settlement、
model/mode 更新、agent mode catalog 和事件订阅确定。Model/Skill/Subagent/MCP、Account、
Settings Sync、Worktree、External Source 和 Hook 等管理面不组成一个 `TuiManagementPort`,
-也不因为存在 TUI 用例就进入 Shared Runtime wire;它们由 TUI backend composition 按 domain
-直接依赖 owner-owned 的稳定 service/provider trait。只有需要 TUI DTO、权限/上下文适配、
-内部类型隔离或 capability 裁剪时,才增加薄 facade。
+也不因为存在 TUI 用例就进入 Shared Runtime wire;它们由 `TuiManagementOwners` 按 domain
+组合具体 Model、Registry、MCP、Account、Settings Sync、Worktree、Hook、External Source/Command
+provider。只有需要 TUI DTO、权限/上下文适配、内部类型隔离或 capability 裁剪时,才增加薄 facade。
| 接口边界 | 谁使用 | 提供 | 不包含 |
|---|---|---|---|
@@ -488,8 +488,9 @@ client 或未来 CLI/HarmonyOS 计划,不能证明同名 Rust transport adapte
前后端契约按能力语义归属,不按 Tauri command 名称归属。稳定的请求、响应、状态事实和类型化错误放在对应
`contracts/*`、Agent Runtime API 或能力归属模块。当前 Desktop GUI 仍使用 Tauri adapter,Web UI 使用 loopback WebSocket
-App Server,Embedded TUI 当前使用 in-process App Server,Shared TUI 通过 `TuiBackend` 映射 private Runtime IPC v17。目标是让
-Embedded GUI/TUI 通过 Host-owned direct adapter 调用 Runtime typed API,需要连接边界的 Web/Shared Rich Client 使用 App Server;
+App Server,Embedded TUI 通过 `CliAgentRuntimeClient -> TuiRuntimePort::Embedded(AgentRuntime)` 直接调用 Runtime,Shared TUI 通过
+`TuiRuntimePort::Shared(RuntimeIpcClient)` 映射 private Runtime IPC v18。Desktop GUI 的后续目标是通过 Host-owned direct adapter 调用
+Runtime typed API;需要连接边界的 Web/Shared Rich Client 才使用 App Server;
Tauri 和各 Rich Client Host 负责 adapter、transport、平台能力及生命周期。ACP、Headless CLI、Peer Host 与公开 SDK 继续由各自
adapter 映射到稳定 owner 接口,不因该目标复用 App Server wire。该规则降低框架耦合,但不要求把 controller-local Desktop DTO
搬进共享 crate。
@@ -499,7 +500,7 @@ adapter 映射到稳定 owner 接口,不因该目标复用 App Server wire。
| 能力归属模块 / Agent Runtime API | 字段明确的请求和响应、状态事实、权限/取消规则、与框架无关的用例方法 | `tauri::State`、`AppHandle`、窗口/菜单对象、command 宏、HTTP/WebSocket/ACP/SDK Host 消息结构 |
| Desktop Tauri / product Host adapter | 当前组装 Tauri adapter;目标按部署组装 direct Runtime adapter 或 App Server transport、注入真实 capability 与平台 provider、管理窗口和桌面生命周期、投递 typed Runtime/App Server notification 或桌面专属事件 | 复制业务校验、持有第二份权威状态、在目标迁移完成后为同一能力保留第二条 Runtime 旁路、把 Tauri 类型传入下层 |
| Server / Remote adapter | 路由鉴权、协议消息、连接生命周期、流量控制与取消转换 | 为同一能力另建业务含义不同的 DTO 或 handler |
-| GUI / Web / TUI frontend | 当前依赖各自 infrastructure 或 `TuiBackend`;目标依赖 frontend/app infrastructure,由其组合 `TuiRuntimePort`、owner service/provider adapter 和需要时的 App Server client;各自保留渲染状态 | 在 UI component/view 中直接依赖 Runtime/Core/Service、公开 Python/TypeScript SDK、Tauri 业务 command 或私有 Shared IPC |
+| GUI / Web / TUI frontend | 依赖各自 frontend/app infrastructure;CLI 由 `CliAgentRuntimeClient` 组合 Direct/Shared Runtime,并由 `TuiManagementOwners` 组合具体 owner provider;Web 在需要连接边界时使用 App Server client;各自保留渲染状态 | 在 UI component/view 中直接依赖 Runtime/Core/Service、公开 Python/TypeScript SDK、Tauri 业务 command 或私有 Shared IPC |
本文其他章节和历史设计中出现的“Runtime SDK”,如果指 `agent-runtime::sdk`,统一称为
**Rust Runtime SDK(当前 preview)**;它是共享 **Agent Runtime API** 的当前 Rust 入口。只有
@@ -547,8 +548,8 @@ Desktop command 使用的序列化对象继续留在 `src/apps/desktop`;即使
## 4. 运行协作细节
-本节在 Process View Level 0 之下展开产品入口、插件调用和平台能力。Current 图只描述当前已接线请求路径;Approved Embedded target
-和 Optional Shared proposal 分别描述已批准但未交付的 Embedded direct-runtime,以及仍待评审的 Shared App Server。三者都只描述组件协作,
+本节在 Process View Level 0 之下展开产品入口、插件调用和平台能力。Current 图只描述当前已接线请求路径;Delivered Embedded path
+和 Optional Shared proposal 分别描述已交付的 Embedded direct-runtime,以及仍待评审的 Shared App Server。三者都只描述组件协作,
不构成新的 4+1 视图。
### 4.1 Current product entry paths
@@ -557,23 +558,25 @@ Desktop command 使用的序列化对象继续留在 `src/apps/desktop`;即使
flowchart LR
Desktop["Desktop GUI"] --> Tauri["Desktop / Tauri adapter"]
Web["Web UI"] --> WebHost["loopback WebSocket App Server"]
- TUI["Interactive TUI"] --> Backend["TuiBackend"]
- Backend -->|"Embedded current"| EmbeddedAS["in-process App Server"]
- Backend -->|"--shared"| SharedIPC["private Runtime IPC v17"]
+ TUI["Interactive TUI"] --> Client["CliAgentRuntimeClient"]
+ Client -->|"Embedded default"| Embedded["TuiRuntimePort::Embedded(AgentRuntime)"]
+ Client -->|"--shared"| SharedIPC["TuiRuntimePort::Shared(RuntimeIpcClient)
private Runtime IPC v18"]
+ TUI --> Management["TuiManagementOwners
concrete owner providers"]
Other["Headless CLI · ACP · Server · Remote"] --> Adapter["独立入口适配器"]
SDK["Rust Runtime SDK / SDK Host preview"] --> SDKAdapter["独立 SDK adapter"]
Tauri --> API["Runtime API / owner ports"]
WebHost --> API
- EmbeddedAS --> API
+ Embedded --> API
SharedIPC --> API
+ Management --> API
Adapter --> API
SDKAdapter --> API
API --> Runtime["共享 Runtime"]
```
-当前 Embedded TUI 核心路径经过 in-process App Server,Shared TUI 通过 private Runtime IPC v17;Web UI 通过 loopback WebSocket App Server,
+当前 Embedded TUI 核心路径直接调用同进程 Runtime,Shared TUI 通过 private Runtime IPC v18;Web UI 通过 loopback WebSocket App Server,
Desktop GUI 通过 Tauri adapter。Headless CLI/CI、ACP、Peer Host 和 SDK Host 保留独立 adapter。所有路径最终消费同一 Runtime API 或 owner
-port,部署选择不能进入业务 owner。目标路径不在本图中展开。
+port,部署选择不能进入业务 owner。
Server bootstrap 和产品组装只创建对象并注入依赖,不是客户端请求的第二条旁路:
@@ -586,20 +589,20 @@ flowchart LR
图中的虚线全部表示启动期 composition;业务请求仍只沿前一张 Current 图中的实线进入 Runtime API 或 owner port。
-### 4.2 Approved Embedded target
+### 4.2 Delivered Embedded path
```mermaid
flowchart LR
- TUI["Embedded interactive TUI"] --> Composition["TuiBackend composition"]
- Composition --> RuntimePort["TuiRuntimePort"]
- RuntimePort --> DirectRuntime["Direct Runtime adapter"]
- DirectRuntime --> API["Runtime API / owner ports"]
- Composition --> Management["owner service/provider adapters"]
+ TUI["Embedded interactive TUI"] --> Client["CliAgentRuntimeClient"]
+ Client --> RuntimePort["TuiRuntimePort::Embedded(AgentRuntime)"]
+ RuntimePort --> API["Runtime API / owner ports"]
+ TUI --> Management["TuiManagementOwners"]
+ Management --> Providers["concrete owner providers"]
API --> Runtime["共享 Runtime"]
```
-这是已批准但尚未交付的 Embedded direct-runtime 目标,属于 Phase 5。迁移完成前,Embedded TUI 继续使用 Current 图中的 in-process
-App Server;管理能力按 domain 由 composition 注入 owner service/provider,不组成 `TuiManagementPort`。
+这是 Phase 5 已交付的 Embedded direct-runtime 路径。CLI 不再组装 in-process App Server;
+管理能力按 domain 由 `TuiManagementOwners` 组合具体 owner provider,不组成 `TuiManagementPort`。
### 4.3 Optional Shared App Server proposal
@@ -612,8 +615,8 @@ flowchart LR
Runtime --> Storage["Workspace / Session storage"]
```
-这是 Phase 6 的待评审提案,不是当前 Shared TUI 的必经链路,也不改变 Current 图中的 private Runtime IPC v17。只有完成鉴权、实例身份、
-controller/lease、事件恢复、取消、限制、性能和回滚门槛后,才可评审是否替换 v17;评审也可以决定长期保留 v17。Web UI 不经过 TUI
+这是 Phase 6 的待评审提案,不是当前 Shared TUI 的必经链路,也不改变 Current 图中的 private Runtime IPC v18。只有完成鉴权、实例身份、
+controller/lease、事件恢复、取消、限制、性能和回滚门槛后,才可评审是否替换 v18;评审也可以决定长期保留 v18。Web UI 不经过 TUI
composition,而是继续通过自己的 loopback WebSocket App Server 入口。
### 4.4 插件调用
diff --git a/docs/plans/core-decomposition-plan.md b/docs/plans/core-decomposition-plan.md
index 69faf6c53c..de138fbb1c 100644
--- a/docs/plans/core-decomposition-plan.md
+++ b/docs/plans/core-decomposition-plan.md
@@ -25,7 +25,7 @@
| CLI / Desktop / ACP | CLI 与 ACP 已分别提交对应 `DeliveryProfile`、消费 Runtime Parts/SDK,并选择经过评审的 Core owner feature closure;Desktop 仍启用 `bitfun-core/product-full`,主交互已消费由现有 owner 构造的窄口径 SDK 接口 | 三个入口仍复用单一 Core runtime owner;完整 Desktop profile 和剩余兼容操作仍需逐项迁移 |
| Server | 当前生产路由只形成 health/info/ping 基线 | 没有插件状态或独立产品组装完整流程 |
| Server / Remote / Web / Mobile Web / SDK profile | 当前为空计划、未接入入口或仅有 preview 测试 | 不得据枚举值宣称产品能力已交付 |
-| Agent Runtime SDK | 已有无 `bitfun-core` 依赖的 v6 preview 接口和 smoke test | 发布边界仍需真实嵌入方证明 |
+| Agent Runtime SDK | 已有无 `bitfun-core` 依赖的 v7 preview 接口和 smoke test | 发布边界仍需真实嵌入方证明 |
| 插件运行时 | 现有路径只覆盖 BitFun 原生包和 OpenCode custom tool 静态名称预览 | 不能据通用消息结构或静态候选扩张稳定 ABI |
| Relay | room/device 状态、account/sync 存储、asset store 与 HTTP/WebSocket router 已归属 `services/relay-service`,standalone 与 embedded 入口同向消费;embedded bind、静态 fallback 和任务生命周期由 Desktop 窄宿主端口持有 | Cargo metadata 门禁覆盖 workspace、独立 manifest、normal/build/dev 依赖及 optional/target 变体;宿主归位已完成并由生命周期与边界测试保护 |
| CLI CI | 独立 Linux job 运行 CLI test,通用三平台 workspace check 覆盖 CLI 编译;Linux PTY 与 Windows ConPTY 有启动页生命周期及本地确定性流式模型夹具驱动的活动 turn 进程测试,发布归档上传前校验 SHA-256 并解压执行 | 参数/序列化/前置失败和组装已有 focused contract;本地模型 HTTP 403 授权拒绝、流中断后的重试失败、Linux PTY/Windows ConPTY Chat resize/取消、`exec` Ctrl+C 及 Patch I/O 失败已有分层回归,真实供应商审批交互、macOS 活动 PTY 与 OS 级终端故障注入仍需补齐 |
diff --git a/docs/plans/tui-app-server-decoupling-refactor-plan.md b/docs/plans/tui-app-server-decoupling-refactor-plan.md
index 3f0b96f22b..c95f80a07b 100644
--- a/docs/plans/tui-app-server-decoupling-refactor-plan.md
+++ b/docs/plans/tui-app-server-decoupling-refactor-plan.md
@@ -1,8 +1,8 @@
# TUI 与 App Server 解耦重构计划
-> 状态:Phase 0-4 已完成当前定义的边界、协议基础、核心聊天、配置管理和外部集成接口迁移;Phase 5 Embedded direct-runtime 迁移待实现,Shared App Server 目标待评审。
+> 状态:Phase 5 实现切换已完成;统一跨部署 fixture、性能与升级兼容证据仍是后续门槛,Shared App Server 目标待评审。
>
-> 当前状态基线:2026-08-13。一次性的运行证据保留在对应 PR/Actions 记录中;本文不绑定会因 rebase 失效的提交 SHA。
+> 当前状态基线:2026-08-14。一次性的运行证据保留在对应 PR/Actions 记录中;本文不绑定会因 rebase 失效的提交 SHA。
>
> 本文只记录当前差距、阶段和完成证据。稳定架构约束见相邻架构文档;Phase 0 的历史盘点已失效,不再作为当前能力清单。
@@ -18,8 +18,8 @@
本计划只迁移交互式 TUI 的产品后端调用:
1. TUI 保留终端输入、状态、渲染和 controller-local effect。
-2. TUI 当前通过 app-local `TuiBackend` 使用产品后端;Phase 5 再把它拆成 Runtime port 与按 domain 注入的管理接口。`TuiAgentClient` 不直接依赖 Core、Runtime 实现、具体 Service、全局 singleton 或私有 IPC operation;view/reducer 也不执行 backend I/O。
-3. Embedded TUI 当前使用 `AppServerTuiBackend`;Shared TUI 当前使用 `SharedTuiBackend` 映射 private Runtime IPC v17。Phase 5 才引入 `TuiRuntimePort`、`DirectRuntimeTuiRuntime` 和 Shared IPC Runtime adapter,并把管理用例移到 backend composition 的 owner service/provider 接口。
+2. TUI 当前通过 app-local `CliAgentRuntimeClient` 使用 Runtime;其内部私有 `TuiRuntimePort` 组合 `Embedded(AgentRuntime)` 与 `Shared(RuntimeIpcClient)`。view/reducer 不直接依赖 Core、Runtime 实现、具体 Service、全局 singleton 或私有 IPC operation,也不执行 backend I/O。
+3. 管理面由 `TuiManagementOwners` 按 domain 组合具体 Model、Registry、MCP、Account、Settings Sync、Worktree、Native/External Hook、External Source/Command provider;不建立总括性的 `TuiManagementPort`,也不把总括性的 App Server management service 搬入 CLI。
4. App Server 只适配稳定合同,不接管 Runtime、Service 或 Product Domain 的业务所有权。
5. Headless `exec`、ACP、Peer Host 和公开 SDK 保留各自经评审的 adapter。
@@ -31,75 +31,69 @@
- 为旧 Web Server 私有协议建立长期兼容层。
- 把 clipboard、editor、terminal raw mode 等 controller-local effect 下沉到工作区 Host。
-## 2. 当前路径与目标路径
+## 2. 当前路径与后续提案
### 2.1 Current
-当前 head 有两条交互式 TUI 后端路径:
+当前 head 有两条交互式 TUI Runtime 部署路径:
```text
Embedded TUI(当前)
- -> TuiAgentClient
- -> TuiBackend
- -> AppServerTuiBackend
- -> AppServerClient
- -> private in-memory transport
- -> BitfunAppServer
+ -> CliAgentRuntimeClient
+ -> TuiRuntimePort::Embedded(AgentRuntime)
-> Runtime API / owners
Shared TUI (--shared)
- -> TuiAgentClient
- -> TuiBackend
- -> SharedTuiBackend compatibility adapter
- -> private Runtime IPC v17
+ -> CliAgentRuntimeClient
+ -> TuiRuntimePort::Shared(RuntimeIpcClient)
+ -> private Runtime IPC v18
-> Shared Runtime Host process
-> Runtime API / owners
```
-当前两条路径共用的是包含 Runtime 与管理方法的单体 `TuiBackend`。`AppServerTuiBackend`
-把 Embedded 请求委托给 App Server client;`SharedTuiBackend` 把 Session/chat 请求映射到
-private Runtime IPC v17,并直接持有具体 `AppManagementService` 承接 Host-local 管理能力。
-Phase 3/4 已让 controller 使用 typed backend API,不代表 Phase 5 的 port 拆分或 adapter 接线已经完成。
+两条路径共用 `CliAgentRuntimeClient` 的 typed Runtime API,但部署差异只存在于 client 私有的
+`TuiRuntimePort`。管理调用不经过该 port:controller 使用 `TuiManagementOwners` 的具体
+provider,provider 再调用真实 domain owner,并按 deployment/Remote scope 返回 capability 或
+typed unsupported。CLI 不再构造 App Server client/server、in-memory transport 或旧 backend family。
-### 2.2 Approved Embedded target
+### 2.2 Delivered Phase 5 composition
-Phase 5 将当前单体 backend 拆成下面的 app-local composition:
+Phase 5 已交付下面的 app-local composition:
```text
-TuiAgentClient
- -> TuiBackend composition
- -> TuiRuntimePort
- -> DirectRuntimeTuiRuntime
- -> SharedIpcTuiRuntime
- -> owner-owned service/provider interfaces (management only)
+TUI controller
+ -> CliAgentRuntimeClient
+ -> TuiRuntimePort::Embedded(AgentRuntime)
+ -> TuiRuntimePort::Shared(RuntimeIpcClient) -> Runtime IPC v18
+ -> TuiManagementOwners
+ -> concrete domain owner providers (management only)
```
-这是已批准但尚未交付的 Phase 5 目标。`TuiRuntimePort` 将只覆盖 Embedded 和 Shared
-都需要、且当前 private Runtime IPC v17 已经承载的 Runtime 行为:initialize/health、
-Session、Turn、Permission/UserInput、shell、compact/undo/redo/reload、usage/settlement、
+`TuiRuntimePort` 只覆盖 Embedded 和 Shared
+都需要、且当前 private Runtime IPC v18 已经承载的 Runtime 行为:Session、Turn、
+Permission/UserInput、shell、compact/undo/redo/reload、usage/settlement、
workspace reference/diff、lineage、fork、当前 Session 的 model/mode 更新、agent mode
-catalog 和事件订阅。Phase 5 完成后,`DirectRuntimeTuiRuntime` 将 direct Runtime 映射到
-该 port,Shared IPC adapter 将 v17 的结果和事件映射到同一组 TUI semantic types;后者
-不运行 `BitfunAppServer`,也不是 Shared App Server transport。
+catalog 和事件订阅。Embedded 分支将 direct Runtime 映射到
+该 port,Shared 分支将 v18 的结果和事件映射到同一组 TUI semantic types;后者
+不运行 `BitfunAppServer`,也不是 Shared App Server transport。initialize/health 属于
+transport lifecycle:Embedded 不再伪造该握手;Shared v18 的协议与实例校验只发生在
+`RuntimeIpcClient` 建连过程中,不进入 TUI Runtime port。
Model catalog/CRUD、Skill、Subagent、MCP、Account、Settings Sync、Worktree、External
Source 和 Hook 不因为被 TUI 使用就进入 `TuiRuntimePort`,也不需要一个总括性的
-`TuiManagementPort`。backend composition 按 domain 注入各 owner 已有的稳定 service/provider
-trait;Embedded 直接使用同进程 owner service,Shared 使用 Host-local service/provider
-adapter。只有原始 service 接口暴露内部类型、无法表达 TUI 所需的权限/上下文/unsupported,
-或需要跨部署稳定 DTO 时,才在 owning crate 或 adapter 内增加最薄的 facade。不得把
-`AppManagementService` 整体搬入 direct adapter,也不得让 controller/view 直接依赖具体
-service 实现。缺少 provider 的 Shared/Remote 场景返回 typed unsupported,禁止静默回落控制端本机。
-
-Phase 3 已将 Mode/Model、Skill、Subagent 和 MCP 管理面迁移到 TUI-facing typed API。Phase 4 进一步迁移了 External Source、native/external Hook、Account、Settings Sync 和 Worktree 管理面。当前这些方法仍位于单体 `TuiBackend`;Embedded 经 App Server 的 `AppManagementService` wiring 调用 owner,Shared 则由 `SharedTuiBackend` 委托其持有的具体 `AppManagementService`。Phase 5 将按 owner 归属把管理用例拆到 service/provider 接口,不建立管理总接口;TUI controller 仍不直接访问 compatibility owner。
-
-Phase 5 中,`DirectRuntimeTuiRuntime` 将实现 `TuiRuntimePort`,可以依赖 Rust Runtime SDK 暴露的稳定 typed facade,但不得把 Runtime 内部类型、Core singleton 或事件队列 owner 暴露给 TUI。管理面 backend 可以直接调用 owner-owned 的稳定 service/provider trait;若该接口暴露内部类型,或需要 TUI-specific DTO、权限、上下文和 capability 裁剪,才在 owning crate 抽取薄 facade。不得把 `AppManagementService` 原样搬到 CLI,也不得由 direct adapter 复制其业务状态或策略。
+`TuiManagementPort`。`TuiManagementOwners` 组合各 domain 的稳定 provider;只有原始 owner
+接口暴露内部类型、无法表达 TUI 所需的权限/上下文/unsupported,或需要稳定 projection 时,
+才在 owning crate 或 CLI adapter 内增加最薄 facade。不得把总括性的 App Server management service 搬入 CLI,
+也不得让 controller/view 直接依赖具体 service 实现。缺少 provider 的 Shared/Remote 场景返回
+typed unsupported,禁止静默回落控制端本机。
-当前 Shared 的 Session/chat/mode authority 由 `SharedTuiBackend` 映射 v17;Phase 5 完成后才由 Shared IPC Runtime adapter 实现 `TuiRuntimePort`。Host 实际提供的本机管理 capability 当前由 `SharedTuiBackend` 委托具体 `AppManagementService`,Phase 5 再改为 backend composition 按 domain 调用 Host-local service/provider adapter。当前 Shared Host 提供 Phase 4 的 External Source V1 和 Hook 管理,但不注入 Account/Settings Sync 或 Worktree owner;这些能力返回 typed unsupported。Remote workspace 对所有 controller-local management capability fail closed,不回落到控制端本机。Phase 4 完成表示 typed API 和 wiring 已迁移,不表示所有 deployment 的 capability 完全相同,也不表示 Phase 5 已完成。Phase 4 之后新增的 External Application V2 控制面目前只在 Embedded App Server 接线,Shared Runtime 明确 unsupported,不重新打开 Phase 4 的旧 owner 直连预算。
+Shared 的 Session/chat/mode authority 由 `CliAgentRuntimeClient` 的 Shared 分支映射 v18;
+Host-local 管理 capability 由 `TuiManagementOwners` 的具体 provider 提供。Phase 4 之后新增的
+External Application V2 控制面在 Shared Runtime 明确 unsupported,不重新打开旧 owner 直连预算。
### 2.3 Optional Shared App Server proposal
-Web 当前继续通过自己的 loopback WebSocket App Server 入口,不经过 TUI composition:
+Web 当前继续通过自己的 loopback WebSocket App Server 入口,不经过 CLI Runtime/management composition:
```text
Web UI -> Web Host -> loopback WebSocket App Server
@@ -113,62 +107,62 @@ Shared Rich Client(Phase 6 candidate)
```
Shared App Server 仍是 Phase 6 待评审提案,不是 Phase 5 的既定结果。Private Runtime IPC
-v17 在候选 transport 的鉴权、实例身份、controller/lease、事件恢复、断连取消、
+v18 在候选 transport 的鉴权、实例身份、controller/lease、事件恢复、断连取消、
`outcome_unknown`、frame 限制和空闲退出达到行为等价前继续保留;评审也可以决定长期保留
-v17。Embedded direct-runtime 不替换 v17,也不要求 direct facade 与 wire DTO 相同;两者只需
+v18。Embedded direct-runtime 不替换 v18,也不要求 direct facade 与 wire DTO 相同;两者只需
满足同一行为合同。
## 3. 当前能力矩阵
状态定义:
-- **已交付**:生产 handler/client 已接线,并被当前 Embedded TUI 路径使用。
-- **兼容映射**:Shared TUI 通过 Runtime IPC v17 和 `SharedTuiBackend` 提供与 Embedded 对照的 TUI Runtime 用例,但没有经过 App Server wire。
+- **已交付**:生产 Runtime client/provider 已接线,并被当前 Embedded TUI 路径使用。
+- **兼容映射**:Shared TUI 通过 `CliAgentRuntimeClient` 和 Runtime IPC v18 提供与 Embedded 对照的 TUI Runtime 用例,但没有经过 App Server wire。
- **部分交付**:已有合同或 handler,但 Host 能力、恢复、安全或 TUI 调用路径仍不完整。
- **未迁移**:当前 TUI 仍使用既有 compatibility owner 路径,或尚无生产接口。
- **本地保留**:属于 TUI 或 controller-local effect,不迁移。
-本矩阵的 Embedded 列记录 Phase 5 之前的 App Server 基线。Phase 5 完成后,Runtime 用例由
-`DirectRuntimeTuiRuntime` 调用 Runtime typed facade;管理用例由 backend composition 调用
-对应 owner service/provider。表中的行为合同和 Shared 对照场景保持不变。
+本矩阵的 Embedded 列记录 Phase 5 当前 direct-runtime。Runtime 用例由
+`CliAgentRuntimeClient -> Embedded(AgentRuntime)` 调用 Runtime typed facade;管理用例由
+`TuiManagementOwners` 调用对应 owner provider。表中的 Shared 对照场景继续使用 v18。
### 3.1 核心聊天与 Session
-| TUI 用例 | Embedded App Server | Shared v17 compatibility | 当前结论 |
+| TUI 用例 | Embedded direct Runtime | Shared v18 compatibility | 当前结论 |
| --- | --- | --- | --- |
-| 初始化、版本、健康 | `app/initialize`、`app/health` | adapter 根据 v17 握手结果合成 TUI-facing initialize/health | Embedded 已交付;Shared 尚不是 App Server connection |
-| Agent、Permission 事件 | `agent/event`、`agent/permissionEvent` | IPC 事件桥映射为 `AppServerEvent` | 两边均可驱动当前核心 TUI;底层恢复合同不同 |
-| Config 事件 | `config/event` | 当前 Shared bridge 不投影 Config 事件 | Embedded 已接线;Shared 的 TUI-facing 管理 capability 当前由 `SharedTuiBackend` 委托其具体 `AppManagementService`,不代表 v17 已有 Config 事件;Phase 5 再拆成 owner service/provider adapter |
-| 流失效与重同步 | `app/eventStreamState`、`app/syncEvents`、`session/sync` | adapter 投影 connection-local cursor、invalidation/resync 和 closed | 已有连接内 cursor/sync;没有跨连接持久 replay/resume |
-| Session list/create/sync | `agent/listSessions`、`agent/createSession`、`session/sync` | list/create/atomic restore operation | 已交付;sync 包含 Runtime 状态、transcript、workspace binding 和 pending Permission |
-| Session delete/rename/fork | typed App Server methods | v17 controller-scoped operations | 已交付或兼容映射;Shared 继续执行 controller/idle 规则 |
-| Model/mode update | `session/updateModel`、`session/updateMode` | v17 current-controller operations | Session update wire 已覆盖;当前 Embedded 经 `AppServerTuiBackend` 提交,Shared 经 `SharedTuiBackend -> Runtime IPC v17` 提交;Phase 5 完成后再分别由 Direct/Shared Runtime adapter 实现 `TuiRuntimePort` |
-| Submit/cancel/steer | typed Agent methods | v17 Turn operations | 已交付或兼容映射 |
-| User Shell/UserInput | `agent/runUserShellCommand`、`agent/submitUserAnswers` | v17 typed operations | 已交付或兼容映射;执行和权限仍由 Runtime owner 持有 |
-| Permission pending/respond | typed Permission methods/events | v17 pending/respond and event stream | 已交付或兼容映射 |
-| Transcript/local command record | `session/readTranscript`、`session/recordLocalCommandTurn` | v17 transcript/record operation | 已交付或兼容映射 |
-| Compact/undo/redo/reload | typed Session methods | v17 current-controller operations | 已交付或兼容映射 |
-| Usage/settlement | `session/usage`、`session/waitForSettlement` | v17 usage/settlement operations | 已交付或兼容映射 |
-| Workspace references/diff | typed Workspace methods | v17 reference/diff operations | 已交付或兼容映射 |
-| Lineage query/inspect/cancel | typed Session methods | v17 root-controller operations | 已交付或兼容映射 |
+| 初始化、版本、健康 | 不建立 TUI-facing transport 握手;从 direct Runtime context 构造 client | `RuntimeIpcClient` 建连时完成 v18 协议/实例校验,不向 TUI 暴露 initialize/health | transport lifecycle 不进入 `TuiRuntimePort`;Shared 尚不是 App Server connection |
+| Agent、Permission 事件 | `AgentRuntime` typed subscriptions | IPC 事件桥映射为同一 TUI semantic event | 两边均可驱动当前核心 TUI;底层恢复合同不同 |
+| Config / management 状态 | `TuiManagementOwners` 具体 provider | 同一 Host-local provider 组合,按 capability/scope fail closed | 不进入 v18 event wire;provider 状态由真实 owner 投影 |
+| 流失效与重同步 | direct event/Permission subscriptions 与 typed session sync | adapter 投影 connection-local cursor、invalidation/resync 和 closed | 已有连接内 cursor/sync;没有跨连接持久 replay/resume |
+| Session list/create/sync | direct Runtime list/create/restore methods | list/create/coherent restore operation | 已交付;restore 在 Session mutation 边界内返回 Runtime 状态与 transcript,并携带 workspace binding、pending Permission 和 Runtime 注册顺序的 pending UserInput |
+| Session delete/rename/fork | direct Runtime methods | v18 controller-scoped operations | 已交付或兼容映射;Shared 继续执行 controller/idle 规则 |
+| Model/mode update | `CliAgentRuntimeClient` direct Runtime methods | v18 current-controller operations | Direct/Shared 分支实现同一 `TuiRuntimePort` 行为;model catalog/CRUD 仍属于 `TuiManagementOwners::model` |
+| Submit/cancel/steer | typed Agent methods | v18 Turn operations | 已交付或兼容映射 |
+| User Shell/UserInput | direct Runtime shell/answers methods | v18 typed operations | 已交付或兼容映射;执行和权限仍由 Runtime owner 持有 |
+| Permission pending/respond | typed Permission methods/events | v18 pending/respond and event stream | 已交付或兼容映射 |
+| Transcript/local command record | direct Runtime transcript/record methods | v18 transcript/record operation | 已交付或兼容映射 |
+| Compact/undo/redo/reload | typed Session methods | v18 current-controller operations | 已交付或兼容映射 |
+| Usage/settlement | direct Runtime usage/settlement methods | v18 usage/settlement operations | 已交付或兼容映射 |
+| Workspace references/diff | typed Workspace methods | v18 reference/diff operations | 已交付或兼容映射 |
+| Lineage query/inspect/cancel | typed Session methods | v18 root-controller operations | 已交付或兼容映射 |
### 3.2 事件恢复的准确边界
-当前 App Server 已发送带 `connection_id + stream + sequence` 的 cursor,并在 server receiver lag/closed 时提供明确 resync directive。`session/sync` 可恢复 Session、Runtime 状态、transcript、workspace binding 和 pending Permission;`app/syncEvents` 返回所请求 stream 的当前 connection-local cursor 与 pending Permission snapshot,但当前不提供 Agent 或 Config snapshot。
+当前 Embedded direct path 在 restore 前先挂载 Runtime typed subscriptions,再从同一个 owner restore snapshot 恢复 Session、Runtime 状态、transcript、workspace binding、pending Permission 和 pending UserInput,不使用 App Server cursor。Shared IPC Server 同样在执行 restore 前预订阅对应 Session,执行期间事件先进入连接 buffer;restore snapshot 在 Session mutation 边界内一次捕获 state 与 transcript,避免 response/subscription 窗口丢事件。Web App Server 仍使用自己的 `connection_id + stream + sequence` 与 connection-local resync 合同;它不是 CLI 的恢复路径。
-当前未交付的是跨连接持久化 cursor、历史事件 replay 和断线后的透明 resume。Shared Runtime IPC v17 仍按自己的 lag/closed、断连取消和 controller 隔离规则工作;`SharedTuiBackend` 只为当前 TUI connection 投影单调 cursor,不能把该投影描述为底层 IPC 已有 replay。
+当前未交付的是跨连接持久化 cursor、历史事件 replay 和断线后的透明 resume。Shared Runtime IPC v18 仍按自己的 lag/closed、断连取消和 controller 隔离规则工作;`CliAgentRuntimeClient` 的 Shared 分支只为当前 TUI connection 投影单调 cursor,不能把该投影描述为底层 IPC 已有 replay。
### 3.3 管理面状态
| Domain | 当前状态 | 当前结论 / 后续 |
| --- | --- | --- |
-| Mode/Model 管理 | Embedded App Server 提供 typed mode catalog 和 model list/get/add/update/delete/default API;read DTO 只含 secret configured metadata,mutation 使用 preserve/replace/clear | Phase 3 已完成 typed API 与 wiring;Shared mode catalog 来自 Runtime Host,model 目录/管理由 `SharedTuiBackend` 持有的具体 `AppManagementService` 提供,Session model mutation 由 `SharedTuiBackend` 提交给 v17 owner;Phase 5 再拆分 owner service/provider 与 Runtime port |
-| Skill/Subagent | 当前 `TuiBackend` 提供 typed list/toggle API 和 visible/manageable read model;Embedded 经 App Server wiring,Shared 由 `SharedTuiBackend` 委托其具体 `AppManagementService` | Phase 3 已完成 typed API 与 wiring;Shared capability 明确属于本机 CLI compatibility scope,Phase 5 再拆成 owner service/provider adapter |
-| MCP | 当前 `TuiBackend` 提供 typed catalog/status/toggle/add/delete/external decision/conflict API;read projection 与 Debug 输出不暴露凭据 | Phase 3 已完成当前定义;Shared 由 `SharedTuiBackend` 委托当前 CLI 进程的具体 `AppManagementService`,以本地 MCP compatibility service 保留迁移前管理行为。该 service 的 MCP 进程状态和 tool registry 不会即时重配已经运行的 Shared Runtime Host;要取得 Host 侧新状态仍需显式的同步/restart contract,不能把本地 toggle 描述成 v17 远端控制 |
-| External Source/Tool/Command/Agent | 当前 `TuiBackend` 提供 typed snapshot/control/review、conflict choice、command expansion 和事件接口;Embedded 经 App Server wiring,Shared V1 由 `SharedTuiBackend` 委托其具体 `AppManagementService` | Phase 4 当前定义已完成;Shared 保留 V1 本机 compatibility,V2 明确 unsupported,Remote 不回落本机;Phase 5 再拆成 owner service/provider adapter |
-| Hooks | 当前 `TuiBackend` 提供 typed native overview 与 external snapshot/plan/apply/mutate API;Embedded 经 App Server wiring,Shared 由 `SharedTuiBackend` 委托其具体 `AppManagementService` | Phase 4 已完成 typed API 与 wiring;native user hooks、compiled-in `post_call_hooks` 和 external hook catalog 继续分离,Remote 明确 unsupported;Phase 5 再拆成 owner service/provider adapter |
-| Account/Settings Sync | typed snapshot/login/finalize/logout 与 sync start/snapshot/cancel/local-changed 已接线;凭据不进入 read model 或 Debug 输出 | Phase 4 接口迁移已完成;Embedded Host 注入共享 `AccountRuntime`,App Server 直接做 domain-to-wire 适配;当前 Shared Host 未注入并返回 typed unsupported |
-| Worktree | typed repository status、bind/release 和 operation identity 已接线 | Phase 4 接口迁移已完成;Embedded Host 注入 Worktree owner,当前 Shared Host 与 Remote workspace 明确 unsupported |
+| Mode/Model 管理 | `TuiManagementOwners::model` 提供 secret-safe model catalog/CRUD/default projection;mode catalog 和 current-Session mutation 属于 Runtime client | Phase 5 已拆开管理 provider 与 Runtime port;Shared Session mutation仍由 v18 owner提交 |
+| Skill/Subagent | `TuiManagementOwners::registry` 提供 typed list/toggle 与 visible/manageable projection | capability 属于当前 CLI Host scope,不进入 v18 wire |
+| MCP | `TuiManagementOwners::mcp` 提供 typed catalog/status/toggle/add/delete/external decision/conflict projection | provider 的 MCP 进程状态和 tool registry 属于当前 CLI 进程;不能把本地 toggle 描述成 v18 远端控制 |
+| External Source/Tool/Command/Agent | `external_source` 与 `external_command` provider 提供 typed snapshot/control/review、conflict choice、command expansion 和事件接口 | Shared V1 只发布实际可用 capability;V2 与 Remote unsupported 不回落本机 |
+| Hooks | `native_hook` 与 `external_hook` provider 提供 typed overview/snapshot/plan/apply/mutate API | native user hooks、compiled-in `post_call_hooks` 和 external hook catalog 继续分离,Remote 明确 unsupported |
+| Account/Settings Sync | `account` / `settings_sync` provider 提供 typed snapshot/login/finalize/logout 与 sync start/snapshot/cancel/local-changed;凭据不进入 read model 或 Debug 输出 | provider 按当前 deployment/scope 调用真实 owner或返回 typed unsupported |
+| Worktree | `worktree` provider 提供 typed repository status、bind/release 和 operation identity | Embedded 调用 Worktree owner;Shared/Remote 不可用时明确 unsupported |
| Desktop/Web Host 安全 | WebSocket Host 仅为 loopback 单用户;Desktop 当前仍使用 Tauri adapter,独立 direct Runtime 迁移尚未实施 | Host allowlist、身份/作用域、真实 limits 与平台 capability provider |
### 3.4 本地保留
@@ -195,7 +189,7 @@ v17。Embedded direct-runtime 不替换 v17,也不要求 direct facade 与 wir
| `src/crates/interfaces/app-server-protocol` | behavior-light method、DTO、wire error、event envelope 和角色定义 |
| `src/crates/interfaces/app-server-client` | 类型化请求、事件分发和 host-supplied transport 抽象 |
| `src/crates/interfaces/app-server` | server 生命周期、生产 handler 注册、Runtime/domain 与 wire 转换、错误映射 |
-| `src/apps/cli` | 当前拥有 `TuiAgentClient`、单体 `TuiBackend`、`AppServerTuiBackend`、`SharedTuiBackend`、transport 和进程生命周期、TUI-local effect;Phase 5 再引入 Runtime port 与按 domain 注入的 owner service/provider composition |
+| `src/apps/cli` | 拥有 `CliAgentRuntimeClient`、私有 `TuiRuntimePort`、Shared transport/进程生命周期、`TuiManagementOwners` 具体 provider 组合和 TUI-local effect;不拥有 App Server 或业务 owner |
| Runtime/Service/Product Domain owners | Session、Turn、Permission、Workspace、配置和其他业务权威事实 |
边界规则:
@@ -204,9 +198,9 @@ v17。Embedded direct-runtime 不替换 v17,也不要求 direct facade 与 wir
- `bitfun-app-server` 可依赖生产 handler 所需的明确 owner feature,但禁止选择 `bitfun-core/product-full`。
- Host 负责 transport、认证、作用域、真实 capability/limits、平台能力和进程生命周期。
- handler 只做合同校验、DTO 转换和错误映射,不持有第二份业务权威状态。
-- Phase 5 引入的 `TuiRuntimePort` 只抽取 Embedded/Shared 共同需要的 Runtime 行为;不定义总括性的 `TuiManagementPort`。
-- Phase 5 将管理面按 domain 拆到 owner-owned 的稳定 service/provider trait;只有需要 DTO、权限/上下文
- 适配或 capability 裁剪时才增加薄 facade,不能把具体 service 实现或 `AppManagementService`
+- Phase 5 已交付的 `TuiRuntimePort` 只抽取 Embedded/Shared 共同需要的 Runtime 行为;不定义总括性的 `TuiManagementPort`。
+- `TuiManagementOwners` 已将管理面按 domain 组合到具体 owner provider;只有需要 DTO、权限/上下文
+ 适配或 capability 裁剪时才增加薄 facade,不能把具体 service 实现或总括性的 App Server management service
整体暴露给 TUI。
- DTO 提取不代表 Runtime owner 迁移。
@@ -218,20 +212,19 @@ v17。Embedded direct-runtime 不替换 v17,也不要求 direct facade 与 wir
| --- | --- | --- | --- | --- |
| Phase 0:边界 | `TuiBackend`、behavior-light protocol/client crate、source/Cargo guard 已建立 | Core boundary tests 和 dependency checks | 已完成 | [PR #2034 checks](https://github.com/GCWing/BitFun/pull/2034/checks) |
| Phase 1:协议基础 | initialize/health、typed events、connection-local cursor、resync、稳定错误和 Embedded connection 已接线 | App Server protocol/client/server focused tests | 已完成 | [PR #2034 checks](https://github.com/GCWing/BitFun/pull/2034/checks) |
-| Phase 2:核心聊天(旧路径) | Embedded 核心用例经 App Server;Shared 经同一 `TuiBackend` 映射 v17;TUI 核心不引用 Runtime SDK/IPC operation | CLI、App Server、Runtime IPC 和 boundary focused tests | 已完成当前定义,作为迁移基线 | [PR #2034 checks](https://github.com/GCWing/BitFun/pull/2034/checks) |
+| Phase 2:核心聊天(旧路径) | Embedded 核心用例经 App Server;Shared 经同一 `TuiBackend` 映射当时的 Runtime IPC;TUI 核心不引用 Runtime SDK/IPC operation | CLI、App Server、Runtime IPC 和 boundary focused tests | 已完成当前定义,作为迁移基线 | [PR #2034 checks](https://github.com/GCWing/BitFun/pull/2034/checks) |
| Phase 3:配置管理 | TUI controller 不再访问 config/registry/MCP compatibility owner;secret-safe typed APIs 完成,CLI Host adapter 可保留显式 compatibility forwarding | owner tests、App Server contract tests、CLI behavior tests | 已完成当前定义 | 本变更的 protocol/client/server/CLI focused tests 与 Core boundary checks |
| Phase 4:外部集成 | External Source、Hook、Account、Settings Sync、Worktree 管理面经 typed backend;remote 不回落本机 | owner/remote/security contract tests | 已完成当前定义 | [PR #2146 checks](https://github.com/GCWing/BitFun/pull/2146/checks)、zero-budget contract 与 Core boundary checks |
-| Phase 5:Embedded direct-runtime | `TuiRuntimePort` 与 owner service/provider 接入边界拆分完成;Embedded TUI 删除 App Server client/server 与 in-memory transport,改用 direct Runtime adapter;管理面不进入 Shared IPC,旧路径随切换删除 | Runtime port 对 Shared v17 operation 的覆盖测试;direct-runtime 与 Shared v17 的 Runtime 行为测试;各管理 service/provider 的 capability/unsupported 测试 | 待实现 | - |
-| Phase 6:Shared App Server | Shared Host 达到 v17 治理等价,opt-in 双栈验证完成,并有回滚与删除证据 | 跨 transport parity、故障、性能和安全测试 | 未开始,目标待评审 | - |
+| Phase 5:Embedded direct-runtime | `TuiRuntimePort` 与 owner provider 接入边界拆分完成;Embedded TUI 删除 App Server client/server 与 in-memory transport,改用 direct Runtime;管理面不进入 Shared IPC,旧路径随切换删除 | Runtime client/port focused tests;direct-runtime 与 Shared v18 的 deployment-specific 行为测试;各管理 provider 的 capability/unsupported 测试;Core boundary checks;统一跨部署 fixture 作为后续证据门槛 | 实现切换完成;统一 fixture 与性能/升级证据待补 | 本变更的 CLI focused tests 与 Core boundary checks;不把源码形状断言计作跨部署行为证据 |
+| Phase 6:Shared App Server | Shared Host 达到 v18 治理等价,opt-in 双栈验证完成,并有回滚与删除证据 | 跨 transport parity、故障、性能和安全测试 | 未开始,目标待评审 | - |
-### 5.1 Phase 0-2 已交付摘要
+### 5.1 Phase 0-2 历史迁移基线
-- `TuiAgentClient`、Startup 和 `ChatMode` 只消费 app-local `TuiBackend`;Runtime 调用和管理调用均不下沉到 view/reducer。
-- Embedded Host 当前在专用 OS 线程的 current-thread Tokio runtime + `LocalSet` 中运行 private `BitfunAppServer`,TUI 保持在原多线程 runtime;这是 direct-runtime 迁移前的基线。
-- `AppServerTuiBackend` 通过正式 `AppServerClient` 和 in-memory transport 完成核心用例,后续由 direct Runtime composition 替换。
-- `SharedTuiBackend` 将相同 Runtime 行为映射到 private Runtime IPC v17;TUI client/controller 不引用 IPC operation。将这些方法抽取为 Runtime port 属于 Phase 5。
+- Phase 0-2 曾由 app-local `TuiBackend` 隔离 controller 与后端,Runtime 调用和管理调用不下沉到 view/reducer;该接口 family 已在 Phase 5 删除。
+- Embedded Host 曾在专用 OS 线程运行 private `BitfunAppServer`;这是已退役的行为基线,不是当前路径。
+- `AppServerTuiBackend` / `SharedTuiBackend` 曾分别映射 App Server 与当时的 private Runtime IPC;Phase 5 已由 `CliAgentRuntimeClient` 的 Direct/Shared 分支替换。
- App Server 核心 handler 覆盖 sync、turn、Permission、revert、context、usage、settlement、Workspace 和 lineage;Config 事件也已在 Embedded connection 接线。
-- Runtime IPC v17 为当前 parity 增加 restore Runtime 状态、usage、settlement 和本地命令 transcript 记录;没有增加 replay、observer、通用 controller transfer 或公开 SDK 能力。
+- Runtime IPC v17 为当前 parity 增加 restore Runtime 状态、usage、settlement 和本地命令 transcript 记录;v18 再增加权威 pending UserInput restore 与 post-registration ready 事件。两者都没有增加 replay、observer、通用 controller transfer 或公开 SDK 能力。
- capability 声明列出当前注册方法,但 Host-specific availability 和方向性 limits 仍是后续收紧项。
### 5.2 Phase 3
@@ -244,15 +237,15 @@ v17。Embedded direct-runtime 不替换 v17,也不要求 direct facade 与 wir
- 模型、Mode、Skill、Subagent 和 MCP 使用 owner-specific typed APIs。
- secret 不出现在 read model、日志或 generic config payload 中。
-- capability 由 Host 注入的 management service、授权和健康状态决定。
-- management service unavailable 时返回明确 unsupported;Shared 的本机 compatibility forwarding 必须显式装配并发布真实 capability,不能在 Remote workspace 静默回落控制端本机。
+- capability 由真实 Host-bound owner provider、授权和健康状态决定;当前通用 App Server Host 没有管理 owner。
+- owner 未注入时返回明确 structured unsupported;Shared 的本机 compatibility forwarding 必须显式装配并发布真实 capability,不能在 Remote workspace 静默回落控制端本机。
交付摘要:
- `app-server-protocol` 提供 Mode、Model、Skill、Subagent 和 MCP 的 owner-specific DTO 与 method;model read model 不返回 secret 值,model mutation 使用 preserve/replace/clear 语义。
-- App Server 由 Host 显式注入具体 `AppManagementService`,按 `tui.modes`、`tui.models`、`tui.skills`、`tui.subagents` 和 `tui.mcp` 发布真实 availability;service 缺失或 unavailable 时返回带 capability id 的 structured unsupported。
-- `AppManagementService` 位于 App Server server wiring,复用现有 config、registry、MCP 和 external-source owner,不成为第二个业务 owner;Startup 与 Chat controller 只调用 `TuiBackend` 的 typed 方法。Phase 5 才把这些方法按 domain 拆到 owner service/provider,且不会建立 direct TUI 的总管理接口。
-- `SharedTuiBackend` 继续映射 v17 mode catalog,并将 Model、Skill、Subagent 和 MCP 管理委托其持有的具体 `AppManagementService`。v17 不承载这些目录、CRUD 或 defaults;Shared 发布的是 adapter-scoped 本地 capability,current-Session model update 仍按 v17 的 controller/idle/outcome-unknown 合同提交给 Runtime Host。Phase 5 再以 owner service/provider adapter 和 Shared IPC Runtime adapter 替换这段单体 wiring。Shared MCP service 的运行态只属于当前 CLI 进程,不宣称可以即时控制已经运行的 Shared Runtime Host。
+- App Server 不保留无生产消费者的管理 service;当前通用 Server Host 未注入 management owner,相关方法返回带 capability id 的 structured unsupported。未来只有真实 Host 消费者具备 Host-bound scope/auth contract 后,才可增加按 owner 注入的窄 adapter,不能把 test-only seam 写成已交付 Web 管理能力。
+- CLI Phase 5 已改由 `TuiManagementOwners` 的具体 provider 调用真实 owner,不建立 direct TUI 总管理接口,也不复制一套 App Server 业务编排。
+- `CliAgentRuntimeClient` 的 Shared 分支继续映射 v18 mode catalog 和 current-Session model mutation;Model、Skill、Subagent 管理由 `TuiManagementOwners` 的具体 provider 提供。v18 不承载这些目录、CRUD 或 defaults。MCP 进程属于 Shared Runtime Host,controller 不启动重复 service;当前 Shared TUI 的 MCP 管理返回 typed unsupported,并要求退出 Shared clients、在 Embedded 模式管理后重启 Shared Runtime。
- Core boundary budgets 已移除 Phase 3 owner 直连债务,并要求 Startup 的 Subagent 管理继续使用 typed backend。
### 5.3 Phase 4
@@ -270,32 +263,39 @@ v17。Embedded direct-runtime 不替换 v17,也不要求 direct facade 与 wir
交付摘要:
-- `app-server-protocol`、client 和 production handlers 已提供 External Source、native/external Hook、Account、Settings Sync 与 Worktree 的 owner-specific typed API;side-effecting 请求使用 operation identity,External Source 与 Hook mutation 保留 owner revision/stale 合同,Settings Sync 提供显式取消与 snapshot。
-- `TuiAgentClient`、Startup 和 Chat controller 只经单体 `TuiBackend` 的 typed API 调用这些用例;Embedded 由 `AppServerTuiBackend` 委托 App Server wiring,Shared 由 `SharedTuiBackend` 委托 v17 或其具体 `AppManagementService`。Phase 4 涉及的 `bitfun_core`、account/account-sync compatibility marker 已从 controller 文件移除,对应 Core boundary budget 固定为零;Runtime port 与按 domain 的 management composition 仍留给 Phase 5。
-- Embedded Host 显式注入共享 `AccountRuntime` 并启用 App Server 内建的本机 Worktree 映射;App Server management service 直接适配 owner,不定义 `AccountManagementHost` 或持有第二份账户、同步、外部来源、Hook、Worktree 权威状态。CLI 的窄 `AccountRuntimeHost` 只实现 daemon、Relay/Peer 路由宿主效果,Session 备份通过独立端口读取 Agent Runtime compatibility owner。
+- `app-server-protocol` 与 client 保留 External Source、native/external Hook、Account、Settings Sync 和 Worktree 的 owner-specific typed method;通用 Host 注册的 management handlers 当前只返回 structured unsupported。未来 scoped Host adapter 的 side-effecting 请求仍必须使用 operation identity,并保留 owner revision/stale、显式取消与 snapshot 合同。
+- Startup 和 Chat controller 只经 `CliAgentRuntimeClient` 或 `TuiManagementOwners` 的 typed API 调用这些用例。Phase 4 涉及的 `bitfun_core`、account/account-sync compatibility marker 已从 controller 文件移除,对应 Core boundary budget 固定为零;Phase 5 已完成 Runtime port 与按 domain 的 management composition。
+- 已退役的 Embedded App Server 注入路径不再承担账户或 Worktree 管理;Direct CLI 通过具体 provider 复用共享 `AccountRuntime` 与 Worktree owner。未来真实 Host 若接线 App Server management,必须先接收 Host-bound scope/auth,并且只能直接适配这些 owner;不得定义 `AccountManagementHost` 或持有第二份账户、同步、外部来源、Hook、Worktree 权威状态。CLI 的窄 `AccountRuntimeHost` 只实现 daemon、Relay/Peer 路由宿主效果,Session 备份通过独立端口读取 Agent Runtime compatibility owner。
- Shared adapter 只发布 Host 实际可用的 capability。External Source V1 与 Hook 管理可使用当前本机 compatibility service;Account/Settings Sync、Worktree、Remote workspace 和后续未接线的 External Application V2 返回 typed unsupported,不静默回落本机。
-- Phase 4 未扩展 private Runtime IPC v17,也未改变 Phase 6 的 Shared transport 评审门槛。
+- Phase 4 未扩展当时的 private Runtime IPC v17,也未改变 Phase 6 的 Shared transport 评审门槛。
### 5.4 Phase 5
-Embedded direct-runtime Phase 5 建议顺序:
+Embedded direct-runtime Phase 5 已完成当前定义:
-1. 按当前 Shared IPC v17 operation 集合冻结窄 `TuiRuntimePort`,定义统一的 TUI semantic request/result/event/error;direct facade 与 v17 wire 不要求共享 DTO。
-2. 将当前包含 Runtime 和管理面方法的单体 `TuiBackend` 拆为 backend composition:Runtime 调用进入 `TuiRuntimePort`,Model/Skill/Subagent/MCP/Account/Settings/Worktree/External Source/Hook 按 domain 进入各自 owner service/provider 接口;不创建 `TuiManagementPort` 总接口。
-3. 逐项检查管理 service 的暴露面:能直接复用稳定 owner-owned trait 的直接注入;暴露内部类型或需要 TUI DTO、权限/上下文、capability 裁剪的,才抽取最薄 facade。`AppManagementService` 仅保留为 App Server wiring,不原样迁入 CLI。
-4. 为 `DirectRuntimeTuiRuntime` 和 Shared IPC adapter 实现 Runtime port;再将 Embedded Host 从 `EmbeddedAppServerHost` 切换为 direct Runtime Host,删除 in-memory transport、App Server thread 和 initialize/health wire handshake。
-5. 按 Chat、Session、Permission/UserInput、Workspace、Config/Management 垂直切片迁移并验证事件订阅、pending Permission、取消、unknown outcome、workspace/execution binding、错误映射和 Host shutdown 回收。
-6. 完成 direct Runtime 的性能、升级兼容和跨入口行为验证后删除旧 App Server;不保留 rollback adapter,direct adapter 不支持的能力必须返回 typed unsupported。
+1. `CliAgentRuntimeClient` 内部的窄 `TuiRuntimePort` 以 `Embedded(AgentRuntime)` 与
+ `Shared(RuntimeIpcClient)` 统一 TUI semantic request/result/event/error;direct facade 与 v18
+ wire 不共享 DTO。
+2. Runtime 与管理边界已拆开:Runtime 调用进入 client 私有 port;Model/Skill/Subagent/MCP/
+ Account/Settings/Worktree/External Source/Hook 按 domain 进入 `TuiManagementOwners` 的具体 provider,
+ 不创建 `TuiManagementPort` 总接口。
+3. App Server 不保留无生产消费者的 management service;相关方法保持 structured unsupported,
+ CLI provider 复用真实 owner use case 或最薄 projection,不复制业务状态和策略。
+4. Embedded Host 已切换为 direct Runtime,CLI 的 in-memory transport、App Server thread、
+ initialize/health wire handshake 与旧 backend family 已删除。
+5. Chat、Session、Permission/UserInput、Workspace 和 Management 使用 focused tests 与 boundary rules
+ 锁定事件、取消、unknown outcome、workspace/execution binding、错误映射和 shutdown 行为。
+6. 旧 App Server 不作为 rollback adapter;缺少的 deployment/provider capability 返回 typed unsupported。
-Shared App Server Phase 6 不以“删除 v17”为起点。建议顺序:
+Shared App Server Phase 6 不以“删除 v18”为起点。建议顺序:
1. 在 Shared Host 中增加默认关闭的 App Server local transport。
2. 两条 transport 复用同一 Host-scoped connection authority、controller registry、Session 事件过滤、operation identity/deadline/cancel 和未知结果登记。
3. 使用一个第一方 Rich Client 进行 opt-in 双栈验证,覆盖跨 transport 竞争、断连、迟到结果、Host 崩溃和回滚。
4. 记录 startup、延迟、内存、frame/queue 上限和长期维护成本。
-5. 只有行为、安全、恢复和性能达到完成门槛后,才评审是否切换 `--shared` 默认实现并删除 v17。
+5. 只有行为、安全、恢复和性能达到完成门槛后,才评审是否切换 `--shared` 默认实现并删除 v18。
-保留 private v17 作为稳定终态也是允许的:只要业务用例和 owner 仍统一,物理 wire 不必为了形式统一而提前收敛。
+保留 private v18 作为稳定终态也是允许的:只要业务用例和 owner 仍统一,物理 wire 不必为了形式统一而提前收敛。
## 6. 验证
@@ -323,19 +323,19 @@ Phase 0-2 的具体命令结果和 CI 状态保留在 [PR #2034 checks](https://
| Workspace | binding、references、diff、remote facts |
| Lineage | tree、descendant transcript、settlement、targeted cancellation |
| Failure | unsupported、lag、invalidated、disconnect、deadline、`outcome_unknown` |
-| Deployment | 当前覆盖 Embedded App Server 与 Shared v17 compatibility;Phase 5 切换到 direct-runtime 并删除旧 Embedded App Server |
+| Deployment | 当前分别覆盖 Embedded direct Runtime 与 Shared v18 compatibility 的关键路径;旧 Embedded App Server 不再是测试或回滚路径 |
-Embedded direct-runtime 实现后,同一 fixture 必须覆盖 direct Runtime 和 Shared v17;迁移前可用旧 Embedded App Server 建立基线,但不维护第三条回滚路径。Shared App Server 实现后再增加候选 transport 路径。
+当前 focused tests 分别覆盖 direct Runtime 和 Shared v18 的关键行为;统一跨部署 fixture 尚未完成,仍是后续验证门槛。旧 Embedded App Server 只保留在历史证据中,不维护第三条回滚路径;Shared App Server 实现后再增加候选 transport 路径。
## 7. 完成定义
只有同时满足以下条件,才能宣布 TUI/App Server 解耦完成:
-1. Phase 3/4 当前定义的管理面已迁移;Phase 5 direct-runtime 已完成;后续新增 capability 也不得绕过 backend composition 或恢复旧 owner 直连。
-2. TUI 产品请求和订阅经过 `TuiAgentClient` 的 backend composition;Runtime 行为经过 `TuiRuntimePort`,管理能力经过对应 owner service/provider,TUI view/reducer 不执行 backend I/O。
+1. Phase 3/4 当前定义的管理面已迁移;Phase 5 direct-runtime 已完成;后续新增 capability 也不得绕过 `CliAgentRuntimeClient` / `TuiManagementOwners` composition 或恢复旧 owner 直连。
+2. TUI 产品请求和订阅经过 `CliAgentRuntimeClient`;Runtime 行为经过其私有 `TuiRuntimePort`,管理能力经过 `TuiManagementOwners` 的具体 owner provider,TUI view/reducer 不执行 backend I/O。
3. protocol/client 和 TUI-facing 依赖闭包不包含 Core、Runtime/Service 实现、`product-full` 或 private IPC operation;只有 CLI Host/backend composition 可以按 owner 注入已审核的 service/provider。
4. capability、limits、身份和作用域来自真实 Host/transport,而不是通用 protocol 默认值;管理 service/provider 缺失时返回 typed unsupported。
-5. 事件、断线、恢复、权限、取消和 unknown outcome 有明确合同与故障测试;Runtime port 的每个 Shared v17 operation 都有对应覆盖证据。
+5. 事件、断线、恢复、权限、取消和 unknown outcome 有明确合同与故障测试;Runtime port 的每个 Shared v18 operation 都有对应覆盖证据。
6. remote workspace 不存在 controller-local fallback。
-7. 重复 DTO、无效 handler、70 方法单体管理 trait 和无生产消费方的旁路已删除或不再属于稳定边界。
-8. 旧 Embedded App Server 已删除且不再作为 rollback adapter;若采用 Shared App Server,迁移满足 Phase 6 的双栈、回滚、性能、安全和删除门槛;否则文档明确 v17 是保留的私有 compatibility transport。
+7. 重复 DTO、无效 handler、旧单体 `TuiBackend` family 和无生产消费方的旁路已删除或不再属于稳定边界。
+8. 旧 Embedded App Server 已删除且不再作为 rollback adapter;若采用 Shared App Server,迁移满足 Phase 6 的双栈、回滚、性能、安全和删除门槛;否则文档明确 v18 是保留的私有 compatibility transport。
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 19d9efbb99..6165e76ee6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -179,6 +179,9 @@ importers:
vite:
specifier: ^7.0.4
version: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
+ vitest:
+ specifier: ^4.1.0
+ version: 4.1.0(@types/node@22.19.7)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
ws:
specifier: ^8.19.0
version: 8.19.0
diff --git a/scripts/check-core-boundaries.test.mjs b/scripts/check-core-boundaries.test.mjs
index 9f5b3bd30e..b704373879 100644
--- a/scripts/check-core-boundaries.test.mjs
+++ b/scripts/check-core-boundaries.test.mjs
@@ -1461,10 +1461,9 @@ const CLI_REVIEWED_CORE_FEATURES = [
];
const APP_SERVER_REVIEWED_CORE_FEATURES = [
- 'external-sources',
+ 'agent-runtime',
'git',
'i18n-runtime',
- 'remote-connect',
];
test('SDK Host Core capability closure keeps every reviewed owner', () => {
@@ -1609,6 +1608,30 @@ test('App Server Core capability closure keeps its production Git owner', () =>
]);
});
+test('App Server Core capability closure keeps its Agent configuration owner', () => {
+ const core = packageAt('bitfun-core', 'src/crates/assembly/core/Cargo.toml');
+ const appServer = packageAt(
+ 'bitfun-app-server',
+ 'src/crates/interfaces/app-server/Cargo.toml',
+ [pathDependency('src/crates/assembly/core', {
+ name: 'bitfun-core',
+ usesDefaultFeatures: false,
+ features: APP_SERVER_REVIEWED_CORE_FEATURES.filter(
+ (feature) => feature !== 'agent-runtime',
+ ),
+ })],
+ );
+
+ const violations = findProductEntrypointCoreFeatureViolations(
+ [appServer, core],
+ { root: TEST_ROOT, crateLayoutRules },
+ );
+
+ assert.deepEqual(violations.map((violation) => violation.message), [
+ 'bitfun-app-server Core capability closure must include agent-runtime',
+ ]);
+});
+
test('App Server Core capability closure keeps its backend i18n runtime', () => {
const core = packageAt('bitfun-core', 'src/crates/assembly/core/Cargo.toml');
const appServer = packageAt(
diff --git a/scripts/core-boundaries/cargo-dependency-boundaries.mjs b/scripts/core-boundaries/cargo-dependency-boundaries.mjs
index 523c3ab192..c6f0f96dfa 100644
--- a/scripts/core-boundaries/cargo-dependency-boundaries.mjs
+++ b/scripts/core-boundaries/cargo-dependency-boundaries.mjs
@@ -914,10 +914,9 @@ export function findProductEntrypointCoreFeatureViolations(
]],
['bitfun-acp', [...new Set([...acpClientCoreFeatures, ...acpServerCoreFeatures])]],
['bitfun-app-server', [
- 'external-sources',
+ 'agent-runtime',
'git',
'i18n-runtime',
- 'remote-connect',
]],
['bitfun-sdk-host-app', coreCompatibilityReviewedFeatures],
]);
diff --git a/scripts/core-boundaries/rules/source/forbidden-rules.mjs b/scripts/core-boundaries/rules/source/forbidden-rules.mjs
index f298e2cb39..5817329151 100644
--- a/scripts/core-boundaries/rules/source/forbidden-rules.mjs
+++ b/scripts/core-boundaries/rules/source/forbidden-rules.mjs
@@ -19,19 +19,6 @@ export const forbiddenContentRules = [
},
],
},
- {
- path: 'src/apps/cli/src/tui_backend.rs',
- reason:
- 'The CLI-local TUI backend may consume App Server client and wire contracts but must not depend on backend implementations, Runtime, services, or private IPC',
- patterns: [
- {
- regex:
- /\b(?:bitfun_core|bitfun_agent_runtime|bitfun_agent_runtime_ipc|bitfun_services_core|bitfun_services_integrations|bitfun_runtime_services|bitfun_product_capabilities|bitfun_external_sources|bitfun_app_server)::/,
- message:
- 'CLI-local TuiBackend must stay on the App Server client/protocol boundary',
- },
- ],
- },
{
path: 'src/crates/interfaces/app-server/Cargo.toml',
reason: 'App Server must select explicit bitfun-core owner features',
diff --git a/scripts/core-boundaries/rules/source/public-api-rules.mjs b/scripts/core-boundaries/rules/source/public-api-rules.mjs
index 387c542529..bfd0d4ac1a 100644
--- a/scripts/core-boundaries/rules/source/public-api-rules.mjs
+++ b/scripts/core-boundaries/rules/source/public-api-rules.mjs
@@ -1126,6 +1126,15 @@ export const externalSourceCorePublicApiEntries = [
'BitFun CLI and desktop host APIs',
),
),
+ ...['ExternalSourceReviewAction', 'review_external_source'].map((symbol) => ({
+ ...externalSourceEntry(
+ symbol,
+ 'bitfun-core external source review use-case owner',
+ 'BitFun CLI management provider',
+ ),
+ verification:
+ 'core external source mutation tests plus CLI management provider tests',
+ })),
externalReferenceEntry(
'workspace_reference_snapshot',
'bitfun-core workspace Reference composition facade',
diff --git a/scripts/core-boundaries/rules/source/required-rules.mjs b/scripts/core-boundaries/rules/source/required-rules.mjs
index 7ee0352d83..a981c5c783 100644
--- a/scripts/core-boundaries/rules/source/required-rules.mjs
+++ b/scripts/core-boundaries/rules/source/required-rules.mjs
@@ -3055,8 +3055,12 @@ export const requiredContentRules = [
message: 'missing user input manager answer regression',
},
{
- regex: /\buser_input_manager_cancel_closes_receiver\b/,
- message: 'missing user input manager cancel regression',
+ regex: /\bpub fn cancel_registration\b/,
+ message: 'missing exact user input registration cancellation API',
+ },
+ {
+ regex: /\buser_input_manager_cancel_closes_only_the_exact_registration\b/,
+ message: 'missing exact user input registration cancellation regression',
},
],
},
@@ -8219,64 +8223,92 @@ export const requiredContentRules = [
],
},
{
- path: 'src/crates/interfaces/app-server/src/management/service.rs',
+ path: 'src/apps/cli/src/agent/runtime_client.rs',
reason:
- 'App Server owns the concrete management adapter while Hosts retain explicit service injection and capability scope',
+ 'The CLI Agent Runtime client owns the Direct/Shared deployment choice behind one app-local Runtime seam',
patterns: [
{
- regex: /\bpub struct AppManagementService\b/,
- message: 'missing concrete App Server management service',
+ regex: /\bpub\(crate\) struct CliAgentRuntimeClient\b/,
+ message: 'missing CLI Agent Runtime client',
+ },
+ {
+ regex: /\benum TuiRuntimePort\b/,
+ message: 'missing private CLI TUI Runtime deployment port',
+ },
+ {
+ regex: /\bEmbedded\(AgentRuntime\)/,
+ message: 'missing direct Embedded Runtime deployment',
+ },
+ {
+ regex: /\bShared\(RuntimeIpcClient\)/,
+ message: 'missing Shared Runtime IPC deployment',
},
{
- regex: /\bimpl AppManagementService\b/,
- message: 'missing concrete App Server management implementation',
+ regex: /\bfn new_direct\b/,
+ message: 'missing direct Runtime client constructor',
},
{
- regex: /\bAppManagementCapabilities::available\(\)/,
- message: 'missing App Server management capability projection',
+ regex: /\bfn new_shared\b/,
+ message: 'missing Shared Runtime client constructor',
},
],
},
{
- path: 'src/apps/cli/src/shared_tui_backend.rs',
+ path: 'src/apps/cli/src/tui_management.rs',
reason:
- 'Shared TUI must retain local Model, Skill, Subagent, and MCP compatibility management without expanding Runtime IPC or leaking owners into controllers',
+ 'CLI management composes concrete domain owner providers instead of an App Server service or a total management port',
patterns: [
{
- regex: /management: Arc/,
- message: 'missing injected Shared TUI App Server management service',
+ regex: /\bpub\(crate\) struct TuiManagementOwners\b/,
+ message: 'missing CLI TUI management owner composition',
+ },
+ {
+ regex: /\bpub model: ModelProvider\b/,
+ message: 'missing CLI model owner provider',
+ },
+ {
+ regex: /\bpub registry: RegistryProvider\b/,
+ message: 'missing CLI skill and subagent registry owner provider',
+ },
+ {
+ regex: /\bpub mcp: McpProvider\b/,
+ message: 'missing CLI MCP owner provider',
+ },
+ {
+ regex: /\bpub account: AccountProvider\b/,
+ message: 'missing CLI account owner provider',
},
{
- regex: /\bfn management_service\b/,
- message: 'missing Shared TUI management capability gate',
+ regex: /\bpub settings_sync: SettingsSyncProvider\b/,
+ message: 'missing CLI settings sync owner provider',
},
{
- regex: /\bfn set_management_scope_from_binding\b/,
- message: 'missing Shared TUI Remote workspace compatibility guard',
+ regex: /\bpub worktree: WorktreeProvider\b/,
+ message: 'missing CLI worktree owner provider',
},
{
- regex: /\.list_models\(ListModelsRequest \{\}\)/,
- message: 'missing Shared TUI model compatibility delegation',
+ regex: /\bpub native_hook: NativeHookProvider\b/,
+ message: 'missing CLI native hook owner provider',
},
{
- regex: /\.list_skills\(request\)/,
- message: 'missing Shared TUI skill compatibility delegation',
+ regex: /\bpub external_hook: ExternalHookProvider\b/,
+ message: 'missing CLI external hook owner provider',
},
{
- regex: /\.list_subagents\(request\)/,
- message: 'missing Shared TUI subagent compatibility delegation',
+ regex: /\bpub external_source: ExternalSourceProvider\b/,
+ message: 'missing CLI external source owner provider',
},
{
- regex: /\.list_mcp_servers\(request\)/,
- message: 'missing Shared TUI MCP compatibility delegation',
+ regex: /\bpub external_command: ExternalCommandProvider\b/,
+ message: 'missing CLI external command owner provider',
},
{
- regex: /\bshared_management_capabilities_follow_the_local_management_service\b/,
- message: 'missing Shared TUI management capability regression',
+ regex: /\bimpl TuiManagementOwners\b/,
+ message: 'missing CLI TUI management owner composition implementation',
},
{
- regex: /\bremote_workspace_cannot_use_the_local_management_service\b/,
- message: 'missing Shared TUI Remote management scope regression',
+ regex: /\bpub\(crate\) async fn load\b/,
+ message: 'missing CLI TUI management owner composition loader',
},
],
},
diff --git a/scripts/core-boundaries/rules/tui-boundary-rules.mjs b/scripts/core-boundaries/rules/tui-boundary-rules.mjs
index 4487bb85c0..aeeaf4c877 100644
--- a/scripts/core-boundaries/rules/tui-boundary-rules.mjs
+++ b/scripts/core-boundaries/rules/tui-boundary-rules.mjs
@@ -1,7 +1,8 @@
// Ratchet for legacy backend references that still live in the CLI TUI.
// Counts may decrease without updating this file. Adding a marker to a new
// file, moving debt between files, or increasing a count requires migration
-// through the CLI-local TuiBackend/App Server boundary instead.
+// through CliAgentRuntimeClient for Runtime behavior or concrete
+// tui_management domain owner providers for management behavior instead.
export const tuiLegacyBackendMarkers = [
'bitfun_core::',
@@ -9,7 +10,6 @@ export const tuiLegacyBackendMarkers = [
'bitfun_services_',
'bitfun_runtime_services',
'bitfun_agent_runtime_ipc',
- 'CliAgentRuntimeClient',
'CliContextReloadClient',
'CoreAgentRuntimeCompatibility',
'crate::account::',
@@ -24,9 +24,9 @@ export const tuiLegacyBackendMarkers = [
export const tuiLegacyBackendBudgets = {
'src/apps/cli/src/modes/chat.rs': {
- 'bitfun_core::': 12,
- 'bitfun_agent_runtime::': 3,
- CoreAgentRuntimeCompatibility: 3,
+ 'bitfun_core::': 0,
+ 'bitfun_agent_runtime::': 0,
+ CoreAgentRuntimeCompatibility: 0,
},
'src/apps/cli/src/modes/chat/commands.rs': { 'bitfun_services_': 1 },
'src/apps/cli/src/modes/chat/external_editor.rs': {
@@ -44,36 +44,36 @@ export const tuiLegacyBackendBudgets = {
'crate::account_sync::': 0,
},
'src/apps/cli/src/modes/chat/run.rs': {
- 'bitfun_core::': 2,
- 'bitfun_agent_runtime::': 3,
+ 'bitfun_core::': 0,
+ 'bitfun_agent_runtime::': 0,
'bitfun_services_': 1,
- 'crate::account_sync::': 1,
+ 'crate::account_sync::': 0,
},
- 'src/apps/cli/src/modes/chat/session_lineage.rs': { 'bitfun_agent_runtime::': 1 },
+ 'src/apps/cli/src/modes/chat/session_lineage.rs': { 'bitfun_agent_runtime::': 0 },
'src/apps/cli/src/modes/chat/selection.rs': {
- 'bitfun_core::': 1,
- 'crate::account::': 2,
+ 'bitfun_core::': 0,
+ 'crate::account::': 0,
},
'src/apps/cli/src/modes/chat/tests.rs': {
- 'bitfun_core::': 8,
+ 'bitfun_core::': 0,
'bitfun_agent_runtime::': 3,
},
'src/apps/cli/src/modes/chat/worktree.rs': { 'bitfun_core::': 0 },
'src/apps/cli/src/ui/chat/popups.rs': {
- 'bitfun_core::': 1,
+ 'bitfun_core::': 0,
'bitfun_agent_runtime::': 2,
- 'crate::account::': 3,
- 'crate::account_sync::': 2,
+ 'crate::account::': 0,
+ 'crate::account_sync::': 0,
},
'src/apps/cli/src/ui/chat/input.rs': { 'bitfun_agent_runtime::': 4 },
'src/apps/cli/src/ui/chat/state.rs': { 'bitfun_agent_runtime::': 1 },
'src/apps/cli/src/ui/composer.rs': { 'bitfun_agent_runtime::': 2 },
'src/apps/cli/src/ui/image_paste.rs': { 'std::fs': 5 },
'src/apps/cli/src/ui/login_form.rs': {
- 'crate::account::': 1,
- 'crate::account_sync::': 1,
+ 'crate::account::': 0,
+ 'crate::account_sync::': 0,
},
- 'src/apps/cli/src/ui/prompt_command_shell_review.rs': { 'bitfun_core::': 2 },
+ 'src/apps/cli/src/ui/prompt_command_shell_review.rs': { 'bitfun_core::': 0 },
'src/apps/cli/src/ui/permission.rs': { 'bitfun_agent_runtime::': 2 },
'src/apps/cli/src/ui/session_lineage_selector.rs': { 'bitfun_agent_runtime::': 1 },
'src/apps/cli/src/ui/startup.rs': {
diff --git a/scripts/core-boundaries/self-test.mjs b/scripts/core-boundaries/self-test.mjs
index 87082f679c..87224b0405 100644
--- a/scripts/core-boundaries/self-test.mjs
+++ b/scripts/core-boundaries/self-test.mjs
@@ -1851,6 +1851,15 @@ export function runManifestParserSelfTest({
throw new Error(`external source core public API budget is missing workspace Reference symbol: ${requiredSymbol}`);
}
}
+ for (const requiredSymbol of ['ExternalSourceReviewAction', 'review_external_source']) {
+ if (!externalSourceCorePublicApiRule?.allowedSymbolEntries.some(
+ (entry) => entry.symbol === requiredSymbol
+ && entry.consumer
+ && entry.verification,
+ )) {
+ throw new Error(`external source core public API budget is missing shared review use case: ${requiredSymbol}`);
+ }
+ }
for (const requiredSymbol of [
'ExternalMcpTimeouts',
'MAX_EXTERNAL_MCP_TIMEOUT_MS',
@@ -2868,7 +2877,8 @@ export function runManifestParserSelfTest({
'get_user_input_manager',
'validate_ask_user_question_input',
'user_input_manager_delivers_answer_and_clears_channel',
- 'user_input_manager_cancel_closes_receiver',
+ 'cancel_registration',
+ 'user_input_manager_cancel_closes_only_the_exact_registration',
],
},
{
@@ -4268,25 +4278,32 @@ export function runManifestParserSelfTest({
],
},
{
- path: 'src/crates/interfaces/app-server/src/management/service.rs',
+ path: 'src/apps/cli/src/agent/runtime_client.rs',
contracts: [
- 'pub struct AppManagementService',
- 'impl AppManagementService',
- 'AppManagementCapabilities::available\\(\\)',
+ 'pub\\(crate\\) struct CliAgentRuntimeClient',
+ 'enum TuiRuntimePort',
+ 'Embedded\\(AgentRuntime\\)',
+ 'Shared\\(RuntimeIpcClient\\)',
+ 'fn new_direct',
+ 'fn new_shared',
],
},
{
- path: 'src/apps/cli/src/shared_tui_backend.rs',
+ path: 'src/apps/cli/src/tui_management.rs',
contracts: [
- 'management: Arc',
- 'fn management_service',
- 'fn set_management_scope_from_binding',
- '\\.list_models\\(ListModelsRequest \\{\\}\\)',
- '\\.list_skills\\(request\\)',
- '\\.list_subagents\\(request\\)',
- '\\.list_mcp_servers\\(request\\)',
- 'shared_management_capabilities_follow_the_local_management_service',
- 'remote_workspace_cannot_use_the_local_management_service',
+ 'pub\\(crate\\) struct TuiManagementOwners',
+ 'pub model: ModelProvider',
+ 'pub registry: RegistryProvider',
+ 'pub mcp: McpProvider',
+ 'pub account: AccountProvider',
+ 'pub settings_sync: SettingsSyncProvider',
+ 'pub worktree: WorktreeProvider',
+ 'pub native_hook: NativeHookProvider',
+ 'pub external_hook: ExternalHookProvider',
+ 'pub external_source: ExternalSourceProvider',
+ 'pub external_command: ExternalCommandProvider',
+ 'impl TuiManagementOwners',
+ 'pub\\(crate\\) async fn load',
],
},
{
diff --git a/scripts/core-boundaries/tui-boundary-ratchet.mjs b/scripts/core-boundaries/tui-boundary-ratchet.mjs
index 5e5e77ae07..32094d993c 100644
--- a/scripts/core-boundaries/tui-boundary-ratchet.mjs
+++ b/scripts/core-boundaries/tui-boundary-ratchet.mjs
@@ -54,7 +54,7 @@ export function checkTuiLegacyBackendRatchet(root) {
path: filePath,
line: 1,
message:
- `TUI backend direct-call debt must only decrease; marker ${marker} has ${actual} occurrences, budget ${allowed}. Route new backend work through the CLI-local TuiBackend and App Server`,
+ `TUI backend direct-call debt must only decrease; marker ${marker} has ${actual} occurrences, budget ${allowed}. Route Runtime behavior through CliAgentRuntimeClient and management behavior through concrete tui_management owner providers`,
});
}
}
diff --git a/src/apps/cli/AGENTS.md b/src/apps/cli/AGENTS.md
index 7098a99fbc..9bd09d0763 100644
--- a/src/apps/cli/AGENTS.md
+++ b/src/apps/cli/AGENTS.md
@@ -19,7 +19,7 @@ CLI owns only surface concerns:
- TUI state, rendering, popups, local draft history, and local effects such as
clipboard or external-editor integration
- projection of Runtime events into text, JSON, JSONL, and user diagnostics
-- Shared Runtime client/server adaptation and Peer Device host presentation
+- Direct/Shared Runtime adaptation and Peer Device host presentation
Session, turn, model round, tool execution, permissions, cancellation,
persistence, context, workspace binding, MCP, Subagent, and other product facts
@@ -35,18 +35,37 @@ runtime owner moved.
Normal interactive submissions follow:
```text
-ChatView -> TuiAgentClient -> TuiBackend -> App Server client
- -> App Server handler -> Agent Runtime owner -> ToolPipeline
+ChatView -> CliAgentRuntimeClient -> TuiRuntimePort
+ -> Embedded(AgentRuntime) -> Agent Runtime owner -> ToolPipeline
+ -> Shared(RuntimeIpcClient) -> private Runtime IPC v18
+ -> Agent Runtime owner -> ToolPipeline
```
-Embedded TUI uses an in-memory App Server connection. During the migration,
-Shared TUI uses a CLI Host compatibility adapter that implements `TuiBackend`
-over the private versioned Runtime IPC; the TUI client and controllers must not
-reference that IPC. Embedded direct-runtime migration belongs to Phase 5;
-replacing the physical Shared transport with App Server Pipe/UDS framing belongs
-to the separately reviewed Phase 6. Side-effecting operations need stable
-identities, controller/idle rules, bounded frames, and outcome-unknown handling
-before a connection can retry.
+Phase 5 is delivered: the default interactive TUI calls the in-process
+`AgentRuntime` directly, while explicit `--shared` uses the private versioned
+Runtime IPC. `TuiRuntimePort` is private to `CliAgentRuntimeClient`; views and
+controllers must not depend on Runtime, Core, or IPC deployment details. The
+retired CLI path must not reintroduce `TuiBackend`, `AppServerTuiBackend`,
+`SharedTuiBackend`, an embedded App Server, or an in-memory App Server transport.
+
+Management operations use a separate composition path:
+
+```text
+TUI controller -> TuiManagementOwners
+ -> concrete Model / Registry / MCP / Account / Settings Sync /
+ Worktree / Native Hook / External Hook / External Source /
+ External Command providers
+ -> owning domain service
+```
+
+This composition is intentionally not a total `TuiManagementPort` and does not
+route management through Runtime IPC or create CLI-local business owners or
+authoritative management state.
+Providers retain capability and Remote-workspace guards. Web/App Server remains
+a sibling product adapter. Replacing the physical Shared transport belongs to a
+separately reviewed phase. Side-effecting operations need stable identities,
+controller/idle rules, bounded frames, and outcome-unknown handling before a
+connection can retry.
Explicit Shell input follows:
diff --git a/src/apps/cli/Cargo.toml b/src/apps/cli/Cargo.toml
index e98d8add2d..d29fbe3e1c 100644
--- a/src/apps/cli/Cargo.toml
+++ b/src/apps/cli/Cargo.toml
@@ -60,9 +60,6 @@ bitfun-runtime-services = { path = "../../crates/execution/runtime-services" }
bitfun-services-core = { path = "../../crates/services/services-core", features = ["dispatch-workspace", "local-storage", "process-runtime", "runtime-ownership"] }
bitfun-agent-tools = { path = "../../crates/execution/tool-contracts" }
bitfun-product-domains = { path = "../../crates/contracts/product-domains", features = ["external-sources"] }
-bitfun-app-server = { path = "../../crates/interfaces/app-server" }
-bitfun-app-server-client = { path = "../../crates/interfaces/app-server-client" }
-bitfun-app-server-protocol = { path = "../../crates/interfaces/app-server-protocol" }
# CLI framework
clap = { workspace = true }
diff --git a/src/apps/cli/src/actions.rs b/src/apps/cli/src/actions.rs
index c85e359182..c16f3a485d 100644
--- a/src/apps/cli/src/actions.rs
+++ b/src/apps/cli/src/actions.rs
@@ -143,7 +143,7 @@ pub(crate) fn shared_tui_image_attachment_error() -> String {
format!("Image attachments are unavailable in Shared TUI. {SHARED_TUI_EMBEDDED_HANDOFF}.")
}
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 `/sessions` and Ctrl+D to delete an idle, non-current Session; use `View subagents` in the command palette to inspect this Session's subagents; use `/timeline` to navigate user messages, `/fork` to branch the current idle Session, `/rename ` to rename it, `/compact` to compact its context, `/diff` to review workspace changes, `/agent`, Tab, or Shift+Tab to change its Agent mode, `/models` and `/connect` to manage models, `/skills` to manage skills, `/mcp` to manage MCP servers, and `/reload [skills|instructions]` to refresh declarative context for the next message. Model, Skill, Subagent, and MCP management use this CLI process's local compatibility owner; MCP process state and tool registration are local to this CLI process and do not reconfigure an already-running Shared Runtime Host. Extensions, 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 `/sessions` and Ctrl+D to delete an idle, non-current Session; use `View subagents` in the command palette to inspect this Session's subagents; use `/timeline` to navigate user messages, `/fork` to branch the current idle Session, `/rename ` to rename it, `/compact` to compact its context, `/diff` to review workspace changes, `/usage` to inspect authoritative Session usage, `/agent`, Tab, or Shift+Tab to change its Agent mode, `/models` and `/connect` to manage models, `/skills` to manage skills, and `/reload [skills|instructions]` to refresh declarative context for the next message. MCP management is unavailable in Shared TUI because MCP processes belong to the Shared Runtime Host; exit Shared clients, manage MCP in Embedded mode, then restart Shared Runtime. Remote workspace Sessions fail closed instead of falling back to controller-local management. Extensions, account-sync, 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 {
@@ -168,6 +168,7 @@ impl ActionHandler {
| Self::Status
| Self::WorkspaceDiff
| Self::CompactSession
+ | Self::Usage
| Self::Editor
| Self::PromptStash
| Self::PromptStashPop
@@ -2136,13 +2137,13 @@ mod tests {
ActionHandler::NativeHooks,
ActionHandler::ExternalHooks,
ActionHandler::Login,
- ActionHandler::Usage,
] {
assert!(
!action.available_in_shared_tui(ActionContext::Chat),
"{action:?}"
);
}
+ assert!(ActionHandler::Usage.available_in_shared_tui(ActionContext::Chat));
assert!(ActionHandler::SelectModel.available_in_shared_tui(ActionContext::Startup));
assert!(ActionHandler::AddModel.available_in_shared_tui(ActionContext::Startup));
assert!(ActionHandler::Skills.available_in_shared_tui(ActionContext::Startup));
@@ -2159,10 +2160,10 @@ mod tests {
assert!(SHARED_TUI_HELP_NOTE.contains("`/reload [skills|instructions]`"));
assert!(SHARED_TUI_HELP_NOTE.contains("Ctrl+D"));
assert!(SHARED_TUI_HELP_NOTE.contains("idle, non-current Session"));
- assert!(SHARED_TUI_HELP_NOTE.contains("local compatibility owner"));
- assert!(SHARED_TUI_HELP_NOTE
- .contains("do not reconfigure an already-running Shared Runtime Host"));
- assert!(SHARED_TUI_HELP_NOTE.contains("remain Embedded"));
+ assert!(SHARED_TUI_HELP_NOTE.contains("MCP management is unavailable"));
+ assert!(SHARED_TUI_HELP_NOTE.contains("Remote workspace Sessions fail closed"));
+ assert!(SHARED_TUI_HELP_NOTE.contains("restart Shared Runtime"));
+ assert!(SHARED_TUI_HELP_NOTE.contains("`/usage`"));
}
#[test]
@@ -2320,7 +2321,7 @@ mod tests {
}
#[test]
- fn shared_tui_projections_expose_compatibility_management_actions() {
+ fn shared_tui_projections_expose_supported_and_recoverable_management_actions() {
let state = ActionState::chat(false, false).for_shared_tui();
let slash_ids = slash_actions(state)
.into_iter()
@@ -2331,10 +2332,12 @@ mod tests {
.map(|action| action.id)
.collect::>();
- for unavailable in ["tools", "extensions", "hooks", "usage"] {
+ for unavailable in ["tools", "extensions", "hooks"] {
assert!(!slash_ids.contains(&unavailable), "{unavailable}");
assert!(!palette_ids.contains(&unavailable), "{unavailable}");
}
+ assert!(slash_ids.contains(&"usage"));
+ assert!(palette_ids.contains(&"usage"));
for available in [
"new_session",
"sessions",
diff --git a/src/apps/cli/src/agent/runtime_client.rs b/src/apps/cli/src/agent/runtime_client.rs
index 906a667ffc..ced7511882 100644
--- a/src/apps/cli/src/agent/runtime_client.rs
+++ b/src/apps/cli/src/agent/runtime_client.rs
@@ -14,30 +14,31 @@ use tokio::sync::{broadcast, Mutex};
use bitfun_agent_runtime::sdk::{
AgentDialogSteerRequest, AgentDialogTurnExecution, AgentDialogTurnRequest, AgentEventReceiver,
AgentInputAttachment, AgentMessageWorkspaceReferencesRequest, AgentRuntime,
- AgentSessionCompactionRequest,
- AgentSessionCreateRequest, AgentSessionDeleteRequest, AgentSessionForkBeforeTurnRequest,
- AgentSessionForkRequest, AgentSessionForkResult, AgentSessionLineageCancellationRequest,
- AgentSessionLineageInspection, AgentSessionLineageRequest, AgentSessionLineageSnapshot,
- AgentSessionLineageTranscriptRequest, AgentSessionListRequest, AgentSessionModeUpdateRequest,
- AgentSessionModelUpdateRequest, AgentSessionRenameRequest, AgentSessionRestoreRequest,
- AgentSessionRevertRequest, AgentSessionRevertResult, AgentSessionUsageRequest,
- AgentTurnCancellationRequest, AgentTurnSettlementRequest, AgentUserAnswersRequest,
- AgentUserShellCommandRequest, AgentWorkspaceReference, AgentWorkspaceReferenceSearchRequest,
- AgentWorkspaceReferenceSearchResult, DialogSteerOutcome, PermissionReply, PermissionRequest,
- PermissionRequestEventReceiver, PortError, PortErrorKind, RuntimeError, SessionTranscript,
- SessionTranscriptRequest, SessionUsageReport, WorkspaceDiffSnapshot,
+ AgentSessionCompactionRequest, AgentSessionCreateRequest, AgentSessionDeleteRequest,
+ AgentSessionForkBeforeTurnRequest, AgentSessionForkRequest, AgentSessionForkResult,
+ AgentSessionLineageCancellationRequest, AgentSessionLineageInspection,
+ AgentSessionLineageRequest, AgentSessionLineageSnapshot, AgentSessionLineageTranscriptRequest,
+ AgentSessionListRequest, AgentSessionModeUpdateRequest, AgentSessionModelUpdateRequest,
+ AgentSessionRenameRequest, AgentSessionRestoreRequest, AgentSessionRevertRequest,
+ AgentSessionRevertResult, AgentSessionUsageRequest, AgentTurnCancellationRequest,
+ AgentTurnSettlementRequest, AgentUserAnswersRequest, AgentUserShellCommandRequest,
+ AgentWorkspaceReference, AgentWorkspaceReferenceSearchRequest,
+ AgentWorkspaceReferenceSearchResult, DialogSteerOutcome, DialogSubmitOutcome, PendingUserInput,
+ PermissionReply, PermissionRequest, PermissionRequestEventReceiver, PortError, PortErrorKind,
+ RuntimeError, SessionState, SessionTranscript, SessionTranscriptRequest, SessionUsageReport,
+ WorkspaceDiffSnapshot,
};
use bitfun_agent_runtime_ipc::{
RuntimeIpcClient, RuntimeIpcClientError, RuntimeIpcClientEvent, RuntimeIpcErrorCode,
RuntimeIpcEvent, RuntimeIpcOperation, RuntimeIpcOperationResult,
RuntimeIpcStreamInvalidationReason, RuntimeSessionForkRequest, RuntimeSessionRenameRequest,
- RuntimeSessionRestoreRequest, RuntimeUserAnswersRequest,
+ RuntimeSessionRestoreRequest, RuntimeSessionState, RuntimeUserAnswersRequest,
};
use bitfun_events::{AgenticEvent, AgenticEventEnvelope};
use bitfun_runtime_ports::{
- put_agent_workspace_references, AgentSessionSummary, AgentSessionWorkspaceBinding,
- AgentSessionWorkspaceRequest, AgentSubmissionSource, DialogSubmissionPolicy,
- SessionExecutionTarget,
+ put_agent_workspace_references, AgentContextReloadRequest, AgentSessionSummary,
+ AgentSessionWorkspaceBinding, AgentSessionWorkspaceRequest, AgentSubmissionSource,
+ DialogSubmissionPolicy, SessionExecutionTarget,
};
use crate::actions::SHARED_TUI_EMBEDDED_HANDOFF;
@@ -57,6 +58,60 @@ fn shared_restore_error(error: RuntimeIpcClientError) -> anyhow::Error {
with_session_conflict_help(error)
}
+fn shared_runtime_error(error: RuntimeIpcClientError) -> RuntimeError {
+ let kind = match &error {
+ RuntimeIpcClientError::Remote(remote) => match remote.code {
+ RuntimeIpcErrorCode::SessionInUse => PortErrorKind::SessionInUse,
+ RuntimeIpcErrorCode::InvalidRequest => PortErrorKind::InvalidRequest,
+ RuntimeIpcErrorCode::OutcomeUnknown => PortErrorKind::OutcomeUnknown,
+ RuntimeIpcErrorCode::NotFound => PortErrorKind::NotFound,
+ _ => PortErrorKind::NotAvailable,
+ },
+ RuntimeIpcClientError::Timeout => PortErrorKind::Timeout,
+ RuntimeIpcClientError::RequestEncoding(_) | RuntimeIpcClientError::InvalidTimeout => {
+ PortErrorKind::InvalidRequest
+ }
+ _ => PortErrorKind::NotAvailable,
+ };
+ RuntimeError::Port(PortError::new(kind, error.to_string()))
+}
+
+fn merge_authoritative_remote_fact(
+ label: &str,
+ requested: &mut Option,
+ authoritative: Option,
+) -> Result<()> {
+ match (requested.as_deref(), authoritative.as_deref()) {
+ (Some(requested), Some(authoritative)) if requested != authoritative => {
+ Err(anyhow::anyhow!(
+ "Session usage {label} conflicts with the authoritative Session workspace binding"
+ ))
+ }
+ (None, Some(authoritative)) => {
+ *requested = Some(authoritative.to_string());
+ Ok(())
+ }
+ _ => Ok(()),
+ }
+}
+
+fn merge_session_usage_remote_scope(
+ mut request: AgentSessionUsageRequest,
+ authoritative: (Option, Option),
+) -> Result {
+ merge_authoritative_remote_fact(
+ "remote connection",
+ &mut request.remote_connection_id,
+ authoritative.0,
+ )?;
+ merge_authoritative_remote_fact(
+ "remote SSH host",
+ &mut request.remote_ssh_host,
+ authoritative.1,
+ )?;
+ Ok(request)
+}
+
fn validated_session_summary(
sessions: &[AgentSessionSummary],
session_id: &str,
@@ -86,6 +141,16 @@ pub(crate) enum SessionMigrationNotice {
},
}
+#[derive(Debug)]
+pub(crate) struct CliSessionRestoreSnapshot {
+ pub(crate) summary: AgentSessionSummary,
+ pub(crate) workspace_binding: AgentSessionWorkspaceBinding,
+ pub(crate) migration_notices: Vec,
+ pub(crate) transcript: SessionTranscript,
+ pub(crate) active_turn_id: Option,
+ pub(crate) pending_user_inputs: Vec,
+}
+
impl SessionMigrationNotice {
pub(crate) fn user_message(&self) -> String {
let (setting, previous_id, restored_id) = match self {
@@ -143,7 +208,7 @@ impl fmt::Display for SessionOperationError {
impl std::error::Error for SessionOperationError {}
impl SessionOperationError {
- fn runtime(error: RuntimeError) -> Self {
+ pub(crate) fn runtime(error: RuntimeError) -> Self {
let outcome_unknown = matches!(
&error,
RuntimeError::Port(port_error)
@@ -156,17 +221,7 @@ impl SessionOperationError {
}
fn shared(error: RuntimeIpcClientError) -> Self {
- let outcome_unknown = matches!(
- &error,
- RuntimeIpcClientError::Remote(remote)
- if remote.code == RuntimeIpcErrorCode::OutcomeUnknown
- ) || matches!(
- &error,
- RuntimeIpcClientError::Timeout
- | RuntimeIpcClientError::Disconnected
- | RuntimeIpcClientError::UnexpectedResponse
- | RuntimeIpcClientError::Io(_)
- );
+ let outcome_unknown = shared_side_effect_outcome_is_unknown(&error);
Self {
message: error.to_string(),
outcome_unknown,
@@ -204,12 +259,60 @@ impl SessionOperationError {
}
}
+fn shared_side_effect_outcome_is_unknown(error: &RuntimeIpcClientError) -> bool {
+ matches!(
+ error,
+ RuntimeIpcClientError::Remote(remote)
+ if remote.code == RuntimeIpcErrorCode::OutcomeUnknown
+ ) || matches!(
+ error,
+ RuntimeIpcClientError::Timeout
+ | RuntimeIpcClientError::Disconnected
+ | RuntimeIpcClientError::UnexpectedResponse
+ | RuntimeIpcClientError::Io(_)
+ )
+}
+
+pub(crate) fn side_effect_error_outcome_is_unknown(error: &anyhow::Error) -> bool {
+ error
+ .downcast_ref::()
+ .is_some_and(shared_side_effect_outcome_is_unknown)
+}
+
+fn should_clear_turn_after_submission_error(error: &anyhow::Error) -> bool {
+ !error
+ .downcast_ref::()
+ .is_some_and(SessionOperationError::outcome_unknown)
+}
+
+fn runtime_side_effect_error(error: RuntimeError) -> anyhow::Error {
+ anyhow::Error::new(SessionOperationError::runtime(error))
+}
+
+fn validated_accepted_turn(
+ operation: &str,
+ expected_session_id: &str,
+ expected_turn_id: &str,
+ accepted_session_id: String,
+ accepted_turn_id: String,
+) -> Result {
+ if accepted_session_id == expected_session_id && accepted_turn_id == expected_turn_id {
+ return Ok(accepted_turn_id);
+ }
+ Err(anyhow::Error::new(SessionOperationError::unexpected(
+ anyhow::anyhow!(
+ "Runtime returned an unexpected identity for {operation}: expected session={expected_session_id}, turn={expected_turn_id}; received session={accepted_session_id}, turn={accepted_turn_id}"
+ ),
+ )))
+}
+
#[derive(Clone, Debug)]
struct CliWorkspacePaths {
project: Option,
execution: Option,
execution_target: Option,
- remote: bool,
+ remote_connection_id: Option,
+ remote_ssh_host: Option,
}
impl CliWorkspacePaths {
@@ -218,7 +321,8 @@ impl CliWorkspacePaths {
project: workspace_path.clone(),
execution: workspace_path,
execution_target: None,
- remote: false,
+ remote_connection_id: None,
+ remote_ssh_host: None,
}
}
@@ -249,7 +353,8 @@ impl CliWorkspacePaths {
self.execution = Some(execution);
self.project = Some(project);
self.execution_target = binding.execution_target.clone();
- self.remote = binding.remote_connection_id.is_some() || binding.remote_ssh_host.is_some();
+ self.remote_connection_id = binding.remote_connection_id.clone();
+ self.remote_ssh_host = binding.remote_ssh_host.clone();
}
fn reset_execution_to_project(&mut self) -> PathBuf {
@@ -258,12 +363,20 @@ impl CliWorkspacePaths {
self.execution_target = Some(SessionExecutionTarget::local(
project.to_string_lossy().to_string(),
));
- self.remote = false;
+ self.remote_connection_id = None;
+ self.remote_ssh_host = None;
project
}
+ pub(crate) fn remote_scope(&self) -> (Option, Option) {
+ (
+ self.remote_connection_id.clone(),
+ self.remote_ssh_host.clone(),
+ )
+ }
+
fn workspace_diff_unavailable_reason(&self) -> Option<&'static str> {
- if self.remote {
+ if self.remote_connection_id.is_some() || self.remote_ssh_host.is_some() {
return Some("Workspace diff is unavailable for remote Sessions");
}
let execution = self.execution();
@@ -285,10 +398,12 @@ fn same_workspace_location(left: &Path, right: &Path) -> bool {
.is_some_and(|(left, right)| left == right)
}
-/// CLI-owned client for the portable Agent Runtime SDK.
-/// Stateless regarding agent_type; callers pass it per call.
-pub(crate) struct ExecAgentRuntimeClient {
- backend: CliAgentRuntimeBackend,
+/// CLI-owned stateful client for the portable Agent Runtime SDK.
+///
+/// Interactive and non-interactive surfaces share this owner; presentation
+/// clients delegate Runtime behavior instead of rebuilding session state.
+pub(crate) struct CliAgentRuntimeClient {
+ backend: TuiRuntimePort,
approval_policy: Arc>,
workspace_paths: Arc>,
/// Session ID — uses Mutex for interior mutability
@@ -299,13 +414,16 @@ pub(crate) struct ExecAgentRuntimeClient {
shared_permission_events:
Option>,
shared_pending_permissions: Arc>>,
+ shared_event_bridge: Option>,
}
-enum CliAgentRuntimeBackend {
+enum TuiRuntimePort {
Embedded(AgentRuntime),
Shared(RuntimeIpcClient),
}
+pub(crate) type ExecAgentRuntimeClient = CliAgentRuntimeClient;
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CliAgentMode {
pub(crate) id: String,
@@ -319,7 +437,7 @@ type SharedBroadcast = Arc>>>;
impl ExecAgentRuntimeClient {
pub(crate) fn new(runtime: &CliRuntimeContext, workspace_path: Option) -> Self {
Self {
- backend: CliAgentRuntimeBackend::Embedded(runtime.agent_runtime().clone()),
+ backend: TuiRuntimePort::Embedded(runtime.agent_runtime().clone()),
approval_policy: Arc::new(RwLock::new(runtime.approval_policy())),
workspace_paths: Arc::new(RwLock::new(CliWorkspacePaths::new(workspace_path))),
session_id: Arc::new(Mutex::new(None)),
@@ -327,9 +445,14 @@ impl ExecAgentRuntimeClient {
shared_agent_events: None,
shared_permission_events: None,
shared_pending_permissions: Arc::new(RwLock::new(HashMap::new())),
+ shared_event_bridge: None,
}
}
+ pub(crate) fn new_direct(runtime: &CliRuntimeContext, workspace_path: Option) -> Self {
+ Self::new(runtime, workspace_path)
+ }
+
pub(crate) fn new_shared(client: RuntimeIpcClient, workspace_path: Option) -> Self {
let (agent_sender, _) = broadcast::channel(256);
let (permission_sender, _) = broadcast::channel(64);
@@ -337,7 +460,7 @@ impl ExecAgentRuntimeClient {
let shared_permission_events = Arc::new(RwLock::new(Some(permission_sender.clone())));
let shared_pending_permissions = Arc::new(RwLock::new(HashMap::new()));
let session_id = Arc::new(Mutex::new(None));
- spawn_shared_event_bridge(
+ let shared_event_bridge = spawn_shared_event_bridge(
client.subscribe_events(),
agent_sender,
permission_sender,
@@ -346,7 +469,7 @@ impl ExecAgentRuntimeClient {
shared_pending_permissions.clone(),
);
Self {
- backend: CliAgentRuntimeBackend::Shared(client),
+ backend: TuiRuntimePort::Shared(client),
approval_policy: Arc::new(RwLock::new(CliApprovalPolicy::Ask)),
workspace_paths: Arc::new(RwLock::new(CliWorkspacePaths::new(workspace_path))),
session_id,
@@ -354,11 +477,12 @@ impl ExecAgentRuntimeClient {
shared_agent_events: Some(shared_agent_events),
shared_permission_events: Some(shared_permission_events),
shared_pending_permissions,
+ shared_event_bridge: Some(shared_event_bridge),
}
}
pub(crate) fn is_shared(&self) -> bool {
- matches!(self.backend, CliAgentRuntimeBackend::Shared(_))
+ matches!(self.backend, TuiRuntimePort::Shared(_))
}
/// Read the main-agent catalog from the execution owner. Embedded and
@@ -366,7 +490,7 @@ impl ExecAgentRuntimeClient {
/// consults the controller process's local registry.
pub(crate) async fn available_agent_modes(&self) -> Result> {
match &self.backend {
- CliAgentRuntimeBackend::Embedded(_) => {
+ TuiRuntimePort::Embedded(_) => {
let workspace = self.workspace_path_buf();
if let Err(error) =
bitfun_core::external_sources::ensure_external_source_workspace_snapshot(Some(
@@ -390,7 +514,7 @@ impl ExecAgentRuntimeClient {
})
.collect())
}
- CliAgentRuntimeBackend::Shared(client) => {
+ TuiRuntimePort::Shared(client) => {
let session_id = self.session_id.lock().await.clone();
match client
.request(RuntimeIpcOperation::ListAgentModes { session_id })
@@ -415,8 +539,8 @@ impl ExecAgentRuntimeClient {
fn embedded_runtime(&self, operation: &str) -> Result<&AgentRuntime> {
match &self.backend {
- CliAgentRuntimeBackend::Embedded(runtime) => Ok(runtime),
- CliAgentRuntimeBackend::Shared(_) => Err(anyhow::anyhow!(
+ TuiRuntimePort::Embedded(runtime) => Ok(runtime),
+ TuiRuntimePort::Shared(_) => Err(anyhow::anyhow!(
"{operation} is not available in the first Shared TUI slice; use default Embedded `bitfun chat`"
)),
}
@@ -424,8 +548,8 @@ impl ExecAgentRuntimeClient {
pub(crate) fn subscribe_events(&self) -> std::result::Result {
match &self.backend {
- CliAgentRuntimeBackend::Embedded(runtime) => runtime.subscribe_events(),
- CliAgentRuntimeBackend::Shared(_) => shared_receiver(
+ TuiRuntimePort::Embedded(runtime) => runtime.subscribe_events(),
+ TuiRuntimePort::Shared(_) => shared_receiver(
self.shared_agent_events.as_ref(),
"Shared Runtime agent event stream is unavailable",
),
@@ -436,8 +560,8 @@ impl ExecAgentRuntimeClient {
&self,
) -> std::result::Result {
match &self.backend {
- CliAgentRuntimeBackend::Embedded(runtime) => runtime.subscribe_permission_requests(),
- CliAgentRuntimeBackend::Shared(_) => shared_receiver(
+ TuiRuntimePort::Embedded(runtime) => runtime.subscribe_permission_requests(),
+ TuiRuntimePort::Shared(_) => shared_receiver(
self.shared_permission_events.as_ref(),
"Shared Runtime permission event stream is unavailable",
),
@@ -448,8 +572,8 @@ impl ExecAgentRuntimeClient {
&self,
) -> std::result::Result, RuntimeError> {
match &self.backend {
- CliAgentRuntimeBackend::Embedded(runtime) => runtime.pending_permission_requests(),
- CliAgentRuntimeBackend::Shared(_) => Ok(self
+ TuiRuntimePort::Embedded(runtime) => runtime.pending_permission_requests(),
+ TuiRuntimePort::Shared(_) => Ok(self
.shared_pending_permissions
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
@@ -465,20 +589,23 @@ impl ExecAgentRuntimeClient {
reply: PermissionReply,
) -> Result<()> {
match &self.backend {
- CliAgentRuntimeBackend::Embedded(runtime) => runtime
+ TuiRuntimePort::Embedded(runtime) => runtime
.respond_permission(request_id, reply)
.await
- .map_err(|error| anyhow::anyhow!(error.into_message())),
- CliAgentRuntimeBackend::Shared(client) => {
+ .map_err(SessionOperationError::runtime)
+ .map_err(anyhow::Error::new),
+ TuiRuntimePort::Shared(client) => {
let session_id = self.require_session_id().await?;
- expect_unit(
+ expect_side_effect_unit(
client
.request(RuntimeIpcOperation::RespondPermission {
session_id,
request_id: request_id.to_string(),
reply,
})
- .await?,
+ .await
+ .map_err(SessionOperationError::shared)
+ .map_err(anyhow::Error::new)?,
"respond_permission",
)
}
@@ -538,6 +665,18 @@ impl ExecAgentRuntimeClient {
.clone()
}
+ fn remote_scope(&self) -> (Option, Option) {
+ self.workspace_paths
+ .read()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ .remote_scope()
+ }
+
+ pub(crate) fn is_remote_workspace(&self) -> bool {
+ let (remote_connection_id, remote_ssh_host) = self.remote_scope();
+ remote_connection_id.is_some() || remote_ssh_host.is_some()
+ }
+
fn reset_execution_to_project_workspace(&self) -> PathBuf {
self.workspace_paths
.write()
@@ -553,17 +692,18 @@ impl ExecAgentRuntimeClient {
&self,
workspace_path: &Path,
) -> Result> {
+ let (remote_connection_id, remote_ssh_host) = self.remote_scope();
let request = AgentSessionListRequest {
workspace_path: workspace_path.to_string_lossy().to_string(),
- remote_connection_id: None,
- remote_ssh_host: None,
+ remote_connection_id,
+ remote_ssh_host,
};
match &self.backend {
- CliAgentRuntimeBackend::Embedded(runtime) => runtime
+ TuiRuntimePort::Embedded(runtime) => runtime
.list_sessions(request)
.await
.map_err(|error| anyhow::anyhow!(error.into_message())),
- CliAgentRuntimeBackend::Shared(client) => match client
+ TuiRuntimePort::Shared(client) => match client
.request(RuntimeIpcOperation::ListSessions { request })
.await?
{
@@ -582,18 +722,19 @@ impl ExecAgentRuntimeClient {
&self,
root_session_id: &str,
) -> Result