Skip to content

refactor: 网络与引擎架构统一——core 唯一引擎,前后端纯网络通信 - #391

Draft
FlowerRealm wants to merge 76 commits into
Stack-Cairn:mainfrom
FlowerRealm:realm/unify-network-backend
Draft

refactor: 网络与引擎架构统一——core 唯一引擎,前后端纯网络通信#391
FlowerRealm wants to merge 76 commits into
Stack-Cairn:mainfrom
FlowerRealm:realm/unify-network-backend

Conversation

@FlowerRealm

@FlowerRealm FlowerRealm commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

没啥概要, 推荐看代码

对于使用有影响的几件事

  1. Chat(without tools)模式被砍了
  2. gateway跟webui可以部署在两个地方, 连接gateway支持输入目标ip

…tend/deleted

统一网络通信重构的第一步:划清 crates/agent-backend 的边界。

分类标准:读写数据/执行计算 → 后端;在图形界面产生可见效果 → 前端专属;
只为「桌面端 outbound 拨向公网 Gateway」拓扑而存在 → 删除。

234 = 195 后端 + 18 前端专属 + 21 删除

三个实质判断:
- 从 gateway 模块救回 8 条真实功能(workspace_watch_set、provider_usage_query/test、
  5 个 tunnel),它们与中继拓扑无关,只是恰好登记在 gateway.rs
- 隧道要重写而非照搬:后端就在工作机上,slug 注册协议与跨中继分帧不再需要
- open_chat_file_link 需拆分:解析归后端,OS 打开动作归前端

清单以纯文本维护在 command-classes/ 下,附可复现的 diff 校验命令。
清点时有两个坑已记录在文档:generate_handler 块以不带分号的 `]` 结尾;
最后一条注册 services::proxy::proxy_get_server_info 不在 commands:: 命名空间下。
…modules

阶段 2 的地基。后端代码此前把两个消费者硬编码在发事件的地方——Tauri 的
AppHandle::emit 和 GatewayController——导致 runtime/services/commands 在编译期
依赖 Tauri 和 Gateway,无法迁入 agent-core。

引入 EventSink trait + EventBus 扇出。后端只管发,谁听由壳决定:
- TauriEventSink:接 webview(桌面壳独有,不会迁入 agent-core)
- GatewayEventSink:接 Gateway(过渡设施,阶段 4 随 Gateway 一并删除)

payload 统一用 serde_json::Value——这些类型本来就要过 JSON IPC,且新架构线上
协议就是 JSON,没有第二种表示值得维护。sink 收到事件后多为重读当前状态再发布,
避免给 11 个 payload 类型逐个追加 Deserialize。

已完全解耦(零 tauri:: / 零 GatewayController):
- runtime/managed_process.rs
- services/automation/store.rs(保留 scheduler 唤醒——那是业务逻辑不是传输)
- services/workspace_watch/

顺带纠正一处所有权倒置:WorkspaceWatchService 原先由 GatewayController 创建,
现改为后端创建、controller 接收,attach_gateway 随之删除。

GatewayController 引用 65 → 51 处。670 个 Rust 测试全绿(新增 4 个总线测试)。
…coupling

chat_history 的 8 个 command 此前直接调 gateway_controller.publish_history_sync()。
改为发 history:upsert / history:delete 事件,Gateway 侧在 GatewayEventSink 里消费。

这两个事件是面向未来的:新架构下前端本身就是远程客户端,同样需要 history 变更
通知,不再是 Gateway 专属。ChatHistorySummary 因此补上 Deserialize——事件消费者
要把 payload 读回来(这是少数几个必须读 payload 的事件,重读数据库拿不到
「刚才改的是哪一条」)。

发现并记录一处阶段 1 分类缺陷:settings_save_remote 是「部分删除」,
RemoteSettingsPayload 混了 Gateway 连接字段(随 Gateway 删)和 enable_web_* 权限
开关(新架构仍需要);且 apply_config 是命令不是事件,返回 Result 传播错误,
转事件会丢错误语义。该条暂留耦合,处置方案写入 backend-boundary.md。

后端代码里的 GatewayController 引用 65 → 40 处,剩余全部集中在:
  commands/integration/gateway.rs  27(其中 20 个命令阶段 4 直接删除)
  services/tunnel/*                11(按阶段 1 结论需重写而非搬运)
  commands/config/settings/*        2(上述已记录的部分删除项)

即所有应当迁入 agent-core 的后端模块,现已全部 gateway-free。670 测试全绿。
TerminalSessionRegistry、SshLocalForwardRegistry、SftpSessionRegistry 原先各自持有
Mutex<Option<AppHandle>> 并在 broadcast 里硬编码 app_handle.emit(),同时又往
subscribers 通道发一份(Gateway 靠后者中继)。前者删除,改走 EventBus——
webview 从此只是总线上的一个普通订阅者,与 subscribers 通道地位对等。

attach_app_handle → set_event_bus。lib.rs 里的调用点相应后移到事件总线创建之后。

至此后端代码的结构性 tauri 耦合清零,仅剩两类且都符合预期:
  services/tunnel/store.rs        3 处(阶段 1 已定:隧道需重写而非搬运)
  commands/app/{app,tray,update}  6 处(前端专属命令,本就该留在壳里)

670 测试全绿。
建立 crates/agent-core 并接通 src-tauri。首个迁入模块是 events.rs(EventBus/
EventSink),它零外部依赖、是后端与壳之间的唯一事件契约,适合作为跨 crate
编译的验证载荷。

编译期防线已生效:cargo tree -p agent-core 零 tauri 依赖。这是
backend-boundary.md 所述对策的落地——把前端专属 command 挪进 agent-core 会直接
构建失败,而不是等到 headless 跑不起来才发现。

新增 docs/architecture/backend-split.md,记录两项**实验/源码核验**结论,
均与并行分析给出的说法相左:

1. include!() 不是 blocker(分析曾列为头号 blocker)。实验证明其路径相对于宏调用
   所在的物理文件,与 crate 边界无关;现有 settings/mod.rs → ssh/mod.rs → load.rs
   的嵌套 include 本身就是反证。真正的问题是 pub(crate) 跨 crate 变私有(E0603),
   而 include! 把文件摊平进一个模块放大了这个面。待迁移代码有 580 处 pub(crate),
   但壳侧只引用 29 个符号路径,故只提升跨界的那些。

2. tauri::async_runtime → tokio 可以机械替换,但前提被分析漏掉了:
   default_runtime() 会 TokioRuntime::new().unwrap() 自建 runtime,故 tauri 版在
   任何上下文可调,而 tokio::spawn 在 runtime 外会 panic。已逐站点核查后端所有
   spawn 均只从 async 上下文可达;PTY 读线程的裸 thread::spawn 不碰 async runtime。

路由约定定为命令式 POST /api/<command_name>(流式走 WS),不做 REST 化——
命令名与参数 JSON 前端现在就在用,1:1 映射让阶段 4 是机械替换;REST 化等于做
195 次独立设计决策,每次都是一个破坏前端的机会。

670 测试全绿(agent-core 4 + liveagent 666)。
把六个阶段的目标/要求落成 markdown,进度落成 csv,已完成与未完成一并记录。

docs/architecture/migration/
  README.md            总览:动机、19 项决策、代码规模基线、全局风险
  phase-{1..6}-*.md    每阶段的目标、要求、验收标准
  phase-{1..6}-*.csv   todo-list:id,task,status,blocker,verify,notes

CSV 的 verify 列强制写「怎么算做完」(可执行命令或可观察现象),不接受
「测试通过」这类空话;notes 列记踩过的坑与被推翻的假设。

103 个条目,25 done / 78 todo。已通过脚本校验:status 合法、task 与 verify
非空、依赖 id 存在、done 项不依赖未完成项。

文档同时固化了几处此前散落在对话里的关键结论:
- 阶段 1 清点的两个坑(generate_handler 块以不带分号的 ] 结尾;
  services::proxy::proxy_get_server_info 不在 commands:: 命名空间下)
- 阶段 2 推翻的两个并行分析结论(include! 不是 blocker;
  async_runtime → tokio 漏掉 default_runtime 自建 runtime 这一前提)
- settings_save_remote 是「部分删除」,三分类容不下,阶段 4 拆两半
- 事件总线把编译期错误变成运行时静默失败,P2-07 已真实踩过一次
- README 现存事实错误(技术栈表声称依赖 codex-sdk / claude-agent-sdk,
  但 package.json 里没有)记为 P6-13
… migrating set

175 spawn_blocking + 50 spawn + 1 JoinHandle。前提已在 P2-12 核验:底层就是
tokio,且后端所有 spawn 点都只从 async 上下文可达,不会撞上 tokio::spawn 的
「runtime 之外 panic」。

src-tauri 的 tokio features 补 rt / rt-multi-thread / process——原先只列了
macros/net/sync/time/io-util,靠 tauri 间接把 runtime 带进来。

services/gateway/、gateway_bridge.rs、commands/integration/gateway.rs 不动:
阶段 4 整体删除,改它们是纯粹的浪费。
实现侧(runtime/services/commands,将迁入 agent-core)现在零 tauri 属性:
  tauri::State<'_, Arc<T>>  →  &Arc<T>
  体内 x.inner()            →  x
Tauri IPC 的知识全部收敛到新的 src-tauri/src/tauri_commands/,16 个模块 177 个包装。

前端契约逐字未变,机器验过:
  - 234 个命令名与 HEAD 完全一致,无增无减
  - 234 个命令的 rename_all 属性逐命令一致(本就不统一,就该不统一)
  - 234 个命令的 JSON key(参数名)逐字一致

包装是脚本生成的,不是手写的:177 次同样的机械变换,手写只会引入手写才有的错误。

commands/app/(18 个前端专属)与 commands/integration/gateway.rs(阶段 4 删)
保持原样,不拆。
use tauri::State 5 处已无引用;State 参数变成 &Arc<T> 后 Arc::clone(&state)
多了一层借用。clippy 相对基线零新增。
runtime/ + services/(除 gateway/gateway_bridge/tunnel/tray/proxy) +
commands/(除 app/、integration/gateway.rs) 一次搬完。

## 为什么是一次搬完,不是 CSV 里写的三步

P2-16/17/18 假设 runtime → services → commands 是有序依赖。不是。runtime/ 反向
依赖上层:

    runtime/terminal/{state,ssh_connect,ssh_session,ssh_auth}.rs → commands::settings
    runtime/{task_runner,shell_runner}.rs                        → services::system_proxy
    runtime/managed_process_journal.rs                           → services::automation::db

三者互相引用,不存在无环的拆分顺序。分步搬只能靠临时垫片,下一步再删掉。
一次搬完反而让所有 crate:: 内部路径原样有效——133 个文件,零路径改写。

## 代价只有 18 个编译错误

70k 行搬完后 agent-core 只报 18 个错,分五类,都已修:

1. include_str! 指向 prompt/ —— prompt/ 是后端技能内容,跟着 skills 一起搬
2. app_version() 原先靠 src-tauri 的 build.rs 塞 env!。agent-core 编译时不知道
   自己会被装进哪个产物,改成 set_app_version() 由宿主注入,未注入返回
   0.0.0-unknown 而不是 panic——版本号只用于自报家门,不该让后端起不来
3. validate_project_folder_name 是纯路径校验,却躺在前端专属的 app/system.rs 里,
   移进 runtime/project_path.rs
4. settings_save_remote 是最后一处 GatewayController 耦合,按 P2-04~P2-08 同款
   模式切断:发 settings:remote-saved,GatewayEventSink 接住去调 apply_config
5. quick_xml 漏了依赖

壳侧 61 个符号需要 pub(crate) → pub(文档估 29)。全部由编译器 E0603 点名后提升,
没有全局提升——pub(crate) 在 crate 内依然有效,只有真正跨界的才该动。
ssh_local_forward 的两个函数是 glob 再导出,改成逐个点名而不是把整个 glob 提成 pub。

## 验证

  agent-core                69,440 行,cargo tree 零 tauri
  src-tauri                 22,480 行(原 92k)
  agent-core 里 GatewayController 引用   0
  cargo test --workspace    670 通过(541 + 129),与搬迁前逐个对齐
  234 个命令名、rename_all、JSON key 与阶段起点 8c90a42 逐字一致
P2-14 一直做不了是因为 GatewayController 持有 tauri::AppHandle,单测造不出来。

但 P2-07 真实踩过的那次回归(移除 gateway 发布调用后忘了在 sink 里接上,
cargo check 全绿而 history 同步已断)本质是**路由错误**——事件名没落到任何分支。
路由是纯函数。于是把决策(action_for,可测)和执行(emit_json,只剩转发)分开。

断言用 agent-core 导出的常量本身而不是字面量:后端改事件名,测试跟着变;
后端新增一条 Gateway 该关心的事件却忘了接,就掉进 Ignore 被抓住。

已用变异验证测试有效:删掉 HISTORY_UPSERT_EVENT 分支,测试立即失败。

顺带:agent-backend crate 骨架 + AppState 落位(P2-20 进行中)。
P2-20/21/22/23/26/27。已用 curl 不经前端验证跑通。

## 路由约定落地

POST /api/<command_name>,body 的 key 与 #[tauri::command] 的参数名逐字一致。
成功 200 {"ok": ...},业务失败 400 {"error": ...}。

错误**按原样序列化不 stringify**:多数命令的错误是 String,但 fs_* 返回结构化的
FsCommandError(code / path / didYouMean),前端现在就在读这些字段。
实测 nope.txt 返回:
  {"error":{"code":"not_found","message":"...","path":"nope.txt","workdir":"..."}}

## 各模块

- auth:密码即 Bearer(决策 7),subtle 常量时间比较,缺 header / 格式错 / 密码错
  一律 401 不给区分信息。不给 --password 就动态生成(决策 8)
- tls:--tls-cert/--tls-key 必须成对,只给一个直接报错而不是静默退化成 HTTP
- ssrf:RFC1918/loopback/link-local/CGNAT/多播/保留段全拦,NAT64 与 6to4 会把
  内嵌 IPv4 取出来递归检查。注释写明调用方必须用 reqwest 的 resolve() 钉死已校验 IP,
  否则 DNS rebinding 能绕过
- ws:/api/events。EventBus::emit_json 是在业务线程上同步遍历 sink 的,
  所以这个 sink 只 try_send,队列满就丢帧——慢客户端不能拖住正在跑 agent 的线程。
  按决策 19 不补发:没有 seq、没有 replay buffer

## 并行 agent 产出的三处真问题(已修)

1. SSRF 的两个测试互相矛盾:一个断言 239.255.255.255 被拦(对,224/4 多播),
   另一个断言它该放行(错)。255.255.255.254 同理落在 240/4。实现是对的,断言是错的
2. resolve_and_validate 解析出 0 个地址时返回 Ok(vec![])。调用方拿空列表去
   reqwest 的 resolve() 钉不住任何地址,等于把 DNS 决定权还给系统解析器。改成报错
3. 测试断言错误信息含「黑名单」字样——措辞会变。改成断言错误里点了名的那个 IP

## 实测

  /healthz            200(认证之外,探活不该要密码)
  无 token / 错密码   401 / 401
  git_status          200,返回本仓库真实 git 状态
  fs_read_text        200;不存在的路径 400 + 结构化错误
  automation_snapshot 200(带 AppState 的代表)
  settings_load_all   200(无参的代表)

## 未完成(P2-24/25/28/29/30/31)

现在只挂了 4 条代表性路由,剩余 172 条待批量生成。另有会话隔离、
状态码语义、契约测试、tunnel 重写。
三个 node 测试用 readFileSync + 正则断言 Rust 源码文本,搬迁后路径失效。
改指 agent-core。(这批脆测试本身按 P5-05 在阶段 5 重写或删除。)

phase-2-backend.md 记录被推翻的假设:
- P2-16/17/18 的「runtime → services → commands」顺序不成立,runtime 反向依赖上层
- pub(crate) 跨界提升是 61 个不是 29 个
- axum-server 的 RustlsConfig 签名已查证,从待决问题里移除

进度:GatewayController 引用 65 → 0,结构性 tauri 触点 13 → 0,
迁入 agent-core 69,440 行,HTTP 路由 4/176。
- scripts/generate-routes.mjs 从 tauri_commands 包装层生成 routes_gen.rs,
  ROUTED_COMMANDS 与注册同源;Makefile 加 check-routes 门禁
- 契约测试(route_contract.rs):175 路由可达性、清单一致性、认证语义
- 隧道重写(P2-30):旧 src-tauri/services/tunnel 2,126 行删除,状态层迁入
  agent-core/services/tunnel,数据面落 agent-backend/src/tunnel.rs,
  一隧道一端口;前端 constants/client/panel 随协议对齐,e2e 测试覆盖
- WS 事件接线:registry set_event_bus、automation 启动、managed-process monitor
- 文档:阶段 2 标记完成,记录路由生成与隧道重写决策
Review 发现的缺陷逐条修复:

- TTL 从不生效:TunnelStore::sweep() 无人调用。新增 spawn_sweeper 周期任务
  (30s),两个进程入口都接上;create() 里只清内存的 sweep_expired 删掉——
  过期清理唯一归属周期清扫,走完整关闭路径(停监听 + 删库)
- create/update/close/sweep 加 mutate 串行锁:消除 MAX_TUNNELS TOCTOU 与
  内存/DB/数据面三方交错
- update() 失败路径:start 失败摘掉 runtime 并广播(不再假装在跑);
  persist 失败连旧 DB 行一起删(不再重启复活旧目标)
- 关隧道立刻切断在途 WebSocket:数据面 oneshot 换 watch,WS 桥 select
  shutdown 信号
- 桌面端不再把 gatewayUrl 灌进 resolveTunnelUrl:隧道端口在本机,
  换主机名只会生成指向网关的死链接
- TunnelMutation 帧回 410 错误而不是静默吞掉:网关 Web UI 的请求不再挂到超时
- 错误契约补洞:crate::json::Json 把 axum 的 422/415 纯文本折成
  400 + {error},生成器与 routes_gen 同步,契约测试钉死缺字段/缺
  Content-Type 两档
- CI 新增 Backend Rust job:make check-routes 门禁 + cargo test -p
  agent-backend,此前 agent-backend 完全不在 CI 里
- 文档:csv 路由算术 170→175;记录「同机单实例」运行约定
- ws: broadcast recv 的 Lagged 原来和 Closed 一起 break,慢客户端直接被踢
  下线,与模块头「丢帧不断连」的约定相反。现在 Lagged 丢帧继续。
- ws: 删掉 GLOBAL_WS_SINK OnceLock 全局单例,sink 改为 AppState 字段,
  build_state 创建并注册进 EventBus。
- auth: verify 长度不等时改为跟自己比,去掉全零 Vec 堆分配,语义不变。
1. devDependencies 加 @types/node@^22,package.json build 脚本改为 'tsc --noEmit && esbuild ...',让 TypeScript 类型门禁真正生效。

2. callBackend 错误处理重构:AbortError 原样 rethrow 保留取消性,!response.ok throw 移出 try/catch 避免自己的错误再包一层,消除重复消息。
缺少 \"types\": [\"node\"] 导致 tsc 7.0.2 在当前 pnpm 布局下找不到 @types/node,
报 19 个隐式 any 错误(node:http、process、console、fetch、AbortSignal 等)。
pnpm build 实际失败,与提交消息「类型门禁真正生效」不符。

添加 types 字段后,tsc --noEmit 全绿,pnpm build 端到端通过。
修复三个关键问题:

1. callBackend 不拆响应信封 — 后端 {ok: value} / {error: value} 的信封没有被正确解析
   - 2xx 响应: 返回 parsed.ok (T) 而非整个 {ok: value}
   - 非 2xx 响应: 以 parsed.error 的原始值 reject,保留结构化错误对象供 fsBackend 消费
   - 所有调用点无需再用 `as T` 断言

2. callBackend 不支持泛型参数 — shellTools/terminalTools/tunnelManagerTools 用了不存在的 <T> 语法
   - 添加泛型参数 <T = unknown> 到 callBackend 签名
   - 所有调用点统一改用泛型实参 (移除 as T 断言): invokeWithAbort, fsBackend, fsTools, mcpManagerTools, mcpTools, shellTools, sshManagerTools

3. system_home_dir 路由不存在 — builtinRegistry 调用的虚拟后端端点在 agent-backend 的 175 条路由中不存在
   - 改用 Node 的 os.homedir() 实现,零网络往返、无依赖
1. agent-backend 路由补漏 system_list_skill_files
   - 在 agent-core 添加 async 版本的 system_list_skill_files()
   - 创建 tauri_commands/system.rs 作为 Tauri 包装层
   - 运行 generate-routes.mjs 生成后端路由

2. agent-core-js package.json 补缺 typebox 依赖
   - 11 个 tools 文件导入 typebox 的 Type,但依赖未声明
   - 补充 "typebox": "1.1.38" 与 agent-gui 同版本锁定

3. backendClient.ts 错误处理改进
   - 先判 response.ok/Content-Type 再 JSON.parse
   - 区分 2xx 成功(拆 ok 字段)与非 2xx 失败(抛 error)
   - 处理空 body 和 parse 失败,避免吞掉真实错误信息

4. toolApproval.ts 删除 Node 侧状态机
   - 违反决策 4:状态机应全在 Rust,Node 只调接口并 await
   - 删除 pending 表、setTimeout 超时、useSyncExternalStore 桩
   - requestToolApproval 改为薄壳:直接打 Rust 的审批接口
   - answerToolApproval 标记为占位实现(前端/Rust 协调完成)
1. backendClient.ts 第 38 行:修复非 2xx 响应的错误处理。
   - 当前代码 throw new Error(String(parsed.error)) 把结构化错误对象碾成 "[object Object]"
   - 修复后:若 typeof error === "string" 则 throw new Error;否则直接 throw error 原始值
   - 恢复 fsBackend/pathErrors 等依赖结构化错误的调用点的功能

2. toolApproval.ts:删除 answerToolApproval 函数和 AnswerToolApprovalOutcome 类型
   - 这两个是占位/兼容层,违反决策 9/17(不设兼容层、不留 feature flag)
   - 实际审批应答应由前端直接打 Rust 的 answer_tool_approval 接口(决策 4)
   - Node 侧仅保留 requestToolApproval 薄壳
- 创建 chatHistory.ts,用 callBackend 替换 @tauri-apps invoke(决策3)
- 简化 chatHistoryParser,改为同步解析(Node 无需 Web Worker)
- 删除 chatHistoryParser.worker.ts
- 创建 events.ts,提供 emitEvent() 接口用于发送事件到 Rust 后端
- gatewayBridgeEvents.ts 保持现状(纯事件构造逻辑,不涉及 tauri)
- 清扫浏览器 API:chat 目录中无真实 window/document 引用
Node⇄Rust 通信改动:
- auth.rs:AuthConfig 支持用户密码和内部 token 两个凭据,verify() 常数时间比较
- main.rs:启动时生成 LIVEAGENT_INTERNAL_TOKEN 并暂存进 AppState
- state.rs:AppState 新增 internal_token 字段供 spawn 子进程时传环境变量

工具审批网络化:
- toolApproval.ts:本地缀以 resolveToolPolicy 的快裁决逻辑,审批路由经 callBackend 走 Rust
  * requestToolApproval 调 /api/request_tool_approval;approval_session 决策本地登记
  * 新增 isSessionApproved() 查询会话级已批准工具,avoids 重复审批
  * 删除本地计时(超时权属 Rust 单一权威)
创建后端客户端 backendFetch/subscribeEvents,替代本地 ChatRuntimeHost 执行。

- src/lib/backend/client.ts(新):前端后端客户端,实现 backendFetch(command, args) 和 subscribeEvents(handler)
  - backendFetch 打 POST 请求到 http://127.0.0.1:$PORT/api/<command>
  - 端口与密码由 Tauri 壳通过 invoke("get_backend_endpoint") 注入,本地缓存
  - subscribeEvents 连 WS /api/events,分发 {event, payload} 给订阅者

- src/pages/chat/runtime/useSendChatTurn.ts:删除 chatRuntimeHost,替换为网络调用
  - 移除 ChatRuntimeHost 类型与参数
  - 两处 chatRuntimeHost.runTurn() 改为 backendFetch("chat_send", {...})
  - 添加 useEffect 订阅 WS 事件,驱动 UI 状态更新(事件处理待完善)

- 删除 src/pages/chat/runtime/ChatRuntimeHost.ts

- 移除 ChatPage.tsx 的 import 与 createChatRuntimeHost 调用

- 移除 src/pages/chat/index.ts 的 createChatRuntimeHost export

验证:grep -rn 'chatRuntimeHost|ChatRuntimeHost' src 无输出
FlowerRealm and others added 15 commits August 5, 2026 19:08
- 新增 pi/models_json.rs:从设置库读 provider 完整配置,渲染单 provider 的 models.json
- 每会话独立 agent dir(PI_CODING_AGENT_DIR=pi-agent/<conversationId>/),不碰用户 ~/.pi/agent/
- models.json 内容变化触发 respawn:pi 只在启动时读该文件,换进程是唯一正路
- provider 命名空间两边一致后,删除按模型 id 反查 provider 的回退逻辑及其测试
- system_ensure_builtin_skills / system_manage_skill / system_read_skill_metadata / system_read_skill_text 增加 async spawn_blocking wrapper 并注册到 routes_gen
- generate-routes.mjs EXTRA_COMMANDS 同步补齐,route_contract 排除集相应收窄
新增 runEndedWaiters 会话级等待表:发送方提交前注册 waiter,
WS 订阅层收到 run_ended 时带终态与 settle 前正文快照兑现,
完成态由发送方把正文写回会话历史并持久化。
翻译层新增 thinking_delta / tool_call / tool_result 三个带轮次的广播事件
(token_delta 补 round 字段),前端按 round 落进 liveRounds 实时渲染;
run_ended waiter 带走 liveRounds 快照,落史改用
buildPersistableMessagesFromSnapshot,思考与工具链随之持久化,
快照为空时回退纯正文路径。
删除 chat_run_ledger、bridgeEvents、useBridgeListeners、useRunMirrorCoordinator、
chatRuntimeSnapshot、runAgentConversationTurn 等旧链路及其测试,
相关命令与路由随之精简(净 -8400 行)。
- 删除 crates/backend/src/pi/、pi-extension/、server/chat.rs 与 pi-rpc 设计文档
- 从 f9074d6^ 取回 crates/core 与 engine_process/engine_proxy,接进现 server/ 结构
- backend↔core 零认证:不恢复 internal_token,auth 中间件按 loopback 对端 IP 豁免
- 重建 /api/tool_approval_request 长挂起路由,TimedOut 映射 deny
- Dockerfile 换回三阶段(engine-builder→backend-builder→runtime),CI/Makefile/tauri resources 同步恢复
- 保留 pi 之后的前端改进(runEndedWaiters、thinking_delta/tool_call 渲染),前端零改动
- 删除不可达死代码:subagents 运行时(保留 scheduler/card/protocol 活代码)、
  i18n 翻译表、memory organizer、settings sync 及死 UI 导出、clawHub、
  chat/messages 死文件、modelVendor、seedLongConversation 等
- providers/runner 库对齐:cliIdentityUpdates 整删、customHeaders 去 curl 解析器、
  DSML 管道改用 pi-ai createAssistantMessageEventStream、AbortSignal.any 替换手写联动、
  工具提示词移至 runner/toolsPrompt.ts
- tools 去样板:新增共享 toolResultHelpers,统一 ToolResultMessage 信封与守卫,
  删除三重校验手工层与 shellTools 正则嗅探启发式
- compaction:溢出/重试判定改用 pi-ai isContextOverflow/isRetryableAssistantError,
  删除 verification-signal 启发式
- 移除 @git-diff-view/file 依赖,更新 lockfile

前端零改动:事件结构、wire 字段、状态文案均未变。
- 事件流:删 engine.ts 改名翻译层,wire 事件以 protocol/wireEvents.ts
  为唯一契约(修实时工具卡/思考块不上屏、token_delta 丢 round);
  tool_status 改 tagged union,中文文案移前端本地化
- 本地执行路径迁 core:memory 抽取/整理、cron、edit-resend
  (修删会话不停抽取、cron 依赖界面开着、edit-resend 落库竞态)
- runner 顺 pi-agent-core:prepareNextTurnWithContext 取代手写
  while(true)/syntheticToolResults/state 改写,seed 文本恢复改
  message_end 就地 mutate(修恢复路径绕过工具审批门)
- core 实现 subagent(~4100 行),前端死实现删除,测试迁指 core
- 前端引擎副本清算:净删 ~2 万行,存活副本与 core 逐字 0-diff
- compaction 封板:ADR-0001,segment 模型与库 Session 树有意分叉

验证:core/frontend tsc 干净,test:frontend 1317/1317,cargo check 通过
- 前端删僵尸 compaction 与 providers/runtime 全套执行代码,不再发
  LLM 请求/拼鉴权头;pi-ai value import 清零(落库三元组改用 round_meta),
  剩余纯 import type;pi-agent-core 依赖移除
- titleJob 下沉 core(conversation_title_generate,同步返回);hook 编排
  下沉:拆前端双跑编排链,core 补 abort cancel,hook_warning 走 wire
- core 清 UI 残留:死文件/window·localStorage 空转/Render* 视图模型/
  中文文案结构化;skills 变更改发 skills_changed wire 事件
- 安全:删 loopback 豁免改 per-spawn 引擎 token,/api 无免认证入口;
  常量时间凭据比较
- 一致性:compaction_checkpoint 后前端禁写历史(fail-closed);
  compaction_status 接通订阅与 toast
- 门禁:check-wire-events(逐字镜像校验)、check-settings-drift
  (core 须为前端有序子序列)接入 CI;model catalog 双写两包
- backend 补挂 system/power/附件 4 条路由(实现自壳下沉);
  proxy_get_server_info 从 core 删除改直连 provider
- 验证:前端 1338/1338、cargo test -p backend 611、双侧 build、
  四道门禁全绿。已知收窄:useSystemProxy 暂不生效(Stack-Cairn#18)
core 直连上游后勾选"走系统代理"的供应商请求静默变直连(Stack-Cairn#18)。
prepareProxyRequest 打 x-liveagent-use-system-proxy 标记头,启动时
包装 globalThis.fetch 作为唯一裁决点:剥标记头,http 走 undici
ProxyAgent、socks5 走 fetch-socks。语义对齐 Rust system_proxy.rs:
未启用直连、配置无效 fail-fast、loopback 豁免;配置随每轮
settings 加载刷新,免重启。
317a469 把引擎从前端搬进 core 时留下三处残桩,settings/请求体
里的数据从未到达模型:

- agent 系统提示词:engine.ts 硬编码 activeAgentPrompt = "",
  启用的模板从不进 system prompt。提取 getActiveAgentPrompt 到
  settings 导出,聊天/cron 两条链路共用(cron 侧删掉本地副本)。
- 上传附件:createUserMessageWithUploads 传空数组,前端气泡显示
  附件但引擎重建的消息里没有——模型看不到用户上传的任何文件。
  chat_send 请求体新增 uploadedFiles(仅路径元数据,内容仍走
  Read 工具),wire 边界用 normalizePendingUploadedFiles 校验。
- 结构化 skill mentions:resolveExplicitSkillMentions 的
  structured 恒为空,菜单点选的消歧信息(skillFile/baseDir)丢失。
  请求体新增 skillMentions 透传。

Rust backend 是透传代理,零改动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- backend_server: 消灭 find_free_port 先探测再释放的 TOCTOU 窗口,
  bind :0 后 listener 直接交给 axum::serve;监听先于 Node 引擎
  spawn 生效,引擎启动即回调(cron claim、事件回流)不再 ECONNREFUSED。
- engine_emit_event: 响应改成与所有命令路由同构的 { "ok": ... },
  core 的 callBackend 校验 ok 字段,裸 200 空体会被判失败。
- powerActivity: 参数 key 对齐后端 snake_case 契约
  (activity_id/ttl_ms),否则反序列化失败。
- lib.rs: dev 模式引擎 bundle 路径锚定 CARGO_MANIFEST_DIR,
  不再依赖进程 cwd。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
segment-session ADR 和 backend/frontend/deleted 命令分类清单
描述的是 pi RPC 迁移期的架构,该方案已撤回(a2cf25e),
文档与现状不符,留着只会误导。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR governance checks failed — this PR has been converted to draft.

  • No linked issue: the PR body must contain Closes #123 / Fixes #123 / Resolves #123. This project requires an issue before a PR — see the contribution guidelines.
  • UI change without screenshots: this PR modifies frontend code. Please add before/after screenshots or a recording under "Screenshots / preview" in the PR body.
  • Merge conflicts with the target branch: merge or rebase the latest base branch and resolve conflicts on your branch before requesting review. Maintainers do not resolve conflicts for you.

Fix the items above, then click Ready for review to re-run the checks.

@StackCairn
StackCairn marked this pull request as draft August 7, 2026 05:20
@FlowerRealm

Copy link
Copy Markdown
Contributor Author

孩子们雷霆大pr来了

…ackend

# Conflicts:
#	README.md
#	README.zh-CN.md
#	crates/agent-gateway/internal/server/http.go
#	crates/agent-gateway/web/src/app/GatewayApp.tsx
#	crates/agent-gateway/web/src/app/WorkspaceOverlayHost.tsx
#	crates/agent-gateway/web/src/lib/providers/customHeaders.ts
#	crates/agent-gateway/web/src/lib/settings/sync.ts
#	crates/agent-gateway/web/src/pages/skills-hub/SkillsHubPage.tsx
#	crates/agent-gui/src/i18n/config.ts
#	crates/agent-gui/src/pages/chat/runtime/useSendChatTurn.ts
#	crates/agent-gui/src/pages/settings/ProvidersSection.tsx
#	crates/core/src/providers/cliIdentityCore.ts
#	crates/core/src/settings/index.ts
#	crates/frontend/src/App.tsx
#	crates/frontend/src/pages/settings/ProvidersSection.tsx
#	crates/frontend/test/providers/custom-headers-propagation.test.mjs
#	crates/frontend/test/providers/provider-runtime-config.test.mjs
#	crates/frontend/test/providers/request-options.test.mjs
#	scripts/mirror-manifest.json
engine.ts 的 conversation/recovery debug logger 一直硬编码 enabled: false,
主对话请求的 system prompt 快照从来没落过盘 —— 排查提示词注入时只能靠读代码
推断,没有现场证据。

改成与 cronPromptRunner / memory organizer 同一个闸门:isAgentDevMode 判定。
闸门读的是 settings.system.executionMode,不是本函数里那个同名局部变量 ——
后者已被压成 text|tools 供网络层使用,永远等不到 agent-dev。
…ore 构建、清 98 处 clippy 与 9 处前端 lint

- 删 command-classes 门禁(清单已在 1bd6cd8 有意删除,门禁守空):
  Makefile target、CI step、scripts/check-command-classes.sh、route_contract Test A
- Dockerfile: rquickjs-sys 的 bindgen 需要 libclang,builder 阶段补装
- ci.yml tauri-rust job: tauri.conf.json 把 core/dist/index.js 列进 resources,
  先 build core 再 cargo check
- backend clippy 98 errors: 契约层模块级 allow + 机械修复
- 前端 biome: 9 个 format/organizeImports error 修复;
  settings/index.ts 是 core 逐字镜像,豁免 organizeImports;
  镜像 getActiveAgentPrompt 补齐 settings 契约
…text-mode 遗留的死 DSML 测试

- runtime 镜像 node:bookworm-slim 不带系统根证书,reqwest Client::new() 直接 panic,
  backend 一启动就挂——装 ca-certificates
- remove text mode(1c49a05)删了 textOnlyRuntime 的 completeAssistantMessage/
  streamAssistantMessage,但 deepseek-dsml-stream 3 个测试还在引用,删掉
test/ 里 localLoader 直接 import core/src,core 的 fetch-socks 等依赖
不装会 Cannot find module。GUI job 在装完 frontend 后补装 core。
core 是 pi 外的引擎壳,不该持有任何 UI 概念:

- 删 i18n/config.ts、shared/fontFamily.ts(纯前端模块)
- 从 AppSettings 移除 theme/locale/fontFamily/fontScale 及全部 normalize 函数
- 标题生成不再依赖 UI locale,改为 "match the language of the content"
- tunnel 校验返回枚举 TunnelValidationError 替代 i18n key 字符串
- 同步更新 drift checker 白名单

−99 / +22 行,tsc 零错误,drift checker 通过。
emitWireEvent 之前用 void fire-and-forget 发 HTTP POST,高速流式
输出时多个请求并发到达 Rust backend,顺序不保证——thinking/text
delta 拼接后出现词序错乱(如 "".test 代替 "test".)。

改为 Promise 链串行发送:前一个 POST 完成后才发下一个,保证
backend 收到的事件顺序与 Node 侧发射顺序一致。
c4ec2a0 把标题 prompt 从 locale-specific 改为内容语言自动检测,
测试仍在断言旧的 "within 10 words" 和 "简体中文" 文本,导致 CI 两
条用例失败。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant