Skip to content

feat: 移植 PilotDeck 上游 2026-09-19 之后的增量 - #610

Merged
xujian519 merged 6 commits into
mainfrom
feat/upstream-port-2026-10
Oct 7, 2026
Merged

xujian519 merged 6 commits into
mainfrom
feat/upstream-port-2026-10

Conversation

@xujian519

@xujian519 xujian519 commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

描述

Closes #609

变更内容

  • cdf4c93 docs: 回填技术债台账、架构基线与文档事实层
  • 2a8efe1 fix(ui): 预览与侧栏三处交互缺陷
  • e645f26 feat(cli): 会话标题采用会话路由模型
  • 7c4feed feat(search): web_search 补齐 6 家 provider 并与设置页对齐
  • 899eeec feat(uploads): 上传限额单一事实源并对外暴露
  • 5aaa545 feat(config): 配置写盘原子化、CAS 收口与读一致修正

测试

  • 本地 pnpm test 通过
  • pnpm lint 与 pnpm format:check 通过
  • pnpm typecheck 通过

上游增量筛选结论

移植 OpenBMB/PilotDeck 在 Sati 上次同步边界(cd52c9af / PR #599,2026-09-18)之后
产出的增量(141 条提交 / PR #600–#632)。上游 force-push 无法 git merge,故为手工
语义移植——成本在判定不在搬运。

零改动域:src/model、src/router、src/context、src/memory 上游四目录零改动;
src/gateway/protocol/version.ts 未触及 ⇒ 本次不升协议版本。

已覆盖或已分叉、判定不引入:cron day-fields OR(Sati 的 v2 缓存双条件保留 + 闰日
例外,强于上游)、后台任务限流(Sati 另有 TTL 回收)、插件 skill markdown、XLSX
drawings 命名空间、上传重名拒绝(Sati 用 COPYFILE_EXCL,无 TOCTOU,优于上游的
lstat 预检)、桌面端(已分叉:自有 release.sh / build-win.bat /
electron-builder.yml)、外观主题体系(白标分支品牌强耦合)。

代码审查后的 8 项修复

提交前跑了一轮多角度代码审查。8 项发现全部坐实,无一是误报,均已修复并附回归测试:

严重度 缺陷 修法
P0 serper / brave 在 web_search 里没有执行分支,穿透到末尾的 GLM 兜底——把 SERPER_API_KEY / BRAVE_API_KEY 当 GLM token 以 Bearer 发到 api.z.ai,搜索全失败且密钥交给无关第三方;设置页探测落到同一形状,对这两家永远测不通 纳入补充 provider 联合,实现各自的请求形状与响应归一化;加守护测试逐个 provider 断言请求落到自己的端点
P1 writeSatiConfig 锁内比对了 revision 却没下传给 writeConfigAtomically,CAS 只覆盖 updateSatiConfig 一条路径,TOCTOU 未闭合(注释却声称已闭合) 下传 expectedRevision,在 rename 前再确认一次
P1 ConfigConflictError 落到通用 catch,变成无 code 的裸 500,客户端按 code 走的「刷新后重放」失效 映射为 409 + code: CONFIG_CONFLICT(照 memory.js 既有形状)
P1 satiConfigWatcher.js 内含一个裸 NUL 字节,Git 判该文件为二进制——整模块改动不可审、不可 blame、不可分块回退 改用语义等价的转义写法
P2 detectYamlIndent 取全局最小缩进,一行较浅的行会把整份文档重排 改取众数;已知限制:缩进是文档级参数,少数派仍会被对齐
P2 预览失败态改用错误码映射后,code 为 null 时(如 "Project is not available.")真实信息被通用文案吞掉 fallback 回落原始 message
P2 WeCom 配置丢失对 botId / secret 的 trim,粘贴带尾随空格的凭据会落盘,握手报不透明鉴权错而 UI 显示「已保存」 恢复 trim 与回落语义
P2 /test-web-search 未脱敏;serpapi 与 custom 的 queryApiKey 把密钥放在 URL 上,传输层错误会原样回显给用户 两处错误回传走 redactSearchError

架构基线追认

file-size 棘轮本次追认(相对 main 的净变化):

文件 main 现在 Δ
ui/server/routes/config.js 1171 1234 +63(409 冲突分支、脱敏、探测限流)
ui/server/routes/config.test.js 934 1138 +204(新增用例)
ui/server/services/satiConfig.js 872 858 -14(棘轮收紧)
ui/src/components/app-shell/SidebarV2.tsx 1310 1323 +13(拖拽阈值)

验证

  • pnpm typecheck / pnpm lint(含 11 个领域门禁)/ pnpm format:check 全绿
  • 后端全量测试 fail 0;UI 全量 169 文件 / 1162 测试通过
  • 关键修复做了负控制:临时还原后对应测试精确变红、其余全绿(P0 那项还原后测试直接
    打印出错误端点 https://api.z.ai/api/paas/v4/web_search)
  • inputSchema 零改动——llm-replay 的请求键 digest 只投影 { name, inputSchema },
    工具顶层 description 不参与,既有 fixture 无需重录

非目标

上游的 memory.enabled 显式化、服务端权威引用改写(会与 Sati 自有的
providerRefs.ts 形成双写)、per-turn modelOverride 语义、桌面端与 Linux 打包。

同进程内两条配置写路径并存:config.js 走原子写 + 稳定读,而 gateway.js 的
saveYaml 仍是裸 writeFileSync,8 处 read-modify-write 调用点全在共享写锁之外,
进程崩溃或 IM 配置与设置页并发保存会留下截断或互相覆盖的 sati.yaml。

- gateway.js 8 处裸写迁移到 updateSatiConfig:temp + fsync + rename 原子写,
  且只改 paths 指到的键——注释、行尾注释与键序保留,不再把
  buildDefaultSatiConfig 的默认值物化进用户文件
- 乐观锁 CAS 贯穿到 rename 前:writeSatiConfig 把锁内比对的 revision 下传给
  writeConfigAtomically,关闭「锁内校验 → 落盘」之间的 TOCTOU;此前该窗口
  内的外部编辑被静默覆盖而客户端收到 200
- 写锁冲突映射为 409 + code:CONFIG_CONFLICT。此前 ConfigConflictError 落到
  通用 catch 变成无 code 的裸 500,客户端按 code 走的「刷新后重放」失效
- revision 改取原始 YAML 而非掩码视图:外部只改密钥的编辑此前对乐观锁完全
  不可见(掩码后两次 rev 相同)
- watcher 跟随软链目标(多 watcher),与写路径的 resolveConfigWritePath 对称;
  此前只 watch 配置目录,读得到软链后的文件而写得到目标
- 缩进探测改取众数而非最小值:doc.toString({indent}) 重排整份文档,取 min 会
  让一行浅缩进把全文拉平
- gateway.js 的 WeCom 配置恢复对 botId/secret 的 trim:粘贴常带尾随空格或
  换行,原样落盘会让握手报不透明的鉴权错误而 UI 显示「已保存」
- satiConfigWatcher.js 里的裸 NUL 字节改用等价转义写法:Git 此前把该文件判为
  二进制,整个模块的改动不可审、不可 blame、不可分块回退
- /test-connection 与 /test-web-search 补速率限制:二者会向外发真实请求,可被
  反复触发出网与计费;/test-web-search 的错误回传补 redactSearchError——serpapi
  与 custom 的 queryApiKey 把密钥放在 URL 上,传输层错误会原样回显给用户

arch 基线追认(详见 PR 说明):config.js 1224→1234,config.test.js 1029→1138,
satiConfig.js 854→858。
uploads.js 里硬编码 50MB / 20 文件,桌面端与 Web 端无法分别调参,前端也只能
各写一份常量。

新增 uploadLimits.js 作为唯一事实源,并暴露 GET /api/upload/limits 供前端读取。
上游把 web_search 的 provider 从 5 家扩到 9 家,Sati 只有 3 家(glm / tavily /
custom),连上游改动前就有的 serper / brave 都没有。本次补齐到 9 家。

- 新增 webSearchProviders.ts:provider 清单、默认端点与 SerpAPI 引擎的单一
  事实源,YAML 校验、工具实现与设置页探测共用一份
- 新增 webSearchAdapter.ts:六家补充 provider(baidu / bocha / exa / serpapi /
  serper / brave)的请求构造与响应归一化。放在 pilot/config 是边界要求——
  ui/server 只能经 src/pilot/index.js barrel 访问 src,而设置页的连通性探测必须
  复用同一套认证方式,否则会出现「设置页测通、实际搜索失败」
- webSearch.ts 拆出 webSearchPerformers.ts(原文件 885 行,超 800 行硬限)
- 每个 provider 都必须有自己的执行分支:serper / brave 此前没有任何分支,穿透
  到 execute() 末尾的 GLM 兜底,把 SERPER_API_KEY / BRAVE_API_KEY 当 GLM token
  以 Bearer 发到 api.z.ai——搜索全失败且密钥交给了无关第三方;设置页探测同样
  落到 GLM 形状,对这两家永远测不通
- 守护测试逐个 provider 断言实际请求落到自己的端点,将来新增 provider 漏接
  分支即红(负控制:还原该缺陷后测试精确复现 api.z.ai 这个错误端点)
- serpapi 空结果(search_metadata.status === Success)与 brave 缺 web 块都返回
  空列表而非报错,与「无结果」语义一致

inputSchema 未改动(llm-replay 的请求键 digest 只投影 { name, inputSchema },
工具顶层 description 不参与),既有 fixture 无需重录。
ProjectRuntimeRegistry 创建标题生成器时传的是项目默认模型
(config.agent.model),而同一处的 createAgentConfig 用的是 context.modelRoute
(M4 会话级模型路由覆盖,团队成员唤醒时传快照 modelRoute)。

后果:走路由的团队成员会话由 routed model 作答,标题却由项目默认模型生成。

- SessionTitleGenerator 的 input 增可选 model,缺省回落 agentModel,向后兼容
- resolveRoutedModel 抽到 agentSessionConfig.ts:provider 与 model 两者都非空
  才采用路由模型,避免半截配置覆盖出空模型名

未引入上游的 modelOverride / modelSelection 形态——全仓零命中,照搬等于新建
第二套 per-turn 模型覆盖语义。
- 侧栏拖拽:mousedown 即置 isResizing,按下的轻微抖动也会进入拖拽态;加 3px
  位移阈值,超过才判定为拖拽
- Docx 大纲面板默认展开会挤压正文,改为默认折叠
- 电子表格预览失败态此前把服务端下发的原始异常文本当正文(形如
  "Error: ... at ..."),用户既读不懂也无法据此行动:
  · 服务端只对有意构造的错误(带 statusCode)透出 message,未预期异常一律用
    通用文案;错误码仍照常下发
  · 前端按 code 映射为可操作提示;无 code 时保留 hook 的原始 message——
    "Project is not available." 与客户端侧的 "Interactive workbook data is
    incomplete." 都没有 code,却恰恰是唯一可行动的信息
  · 失败态补「重试」出口,此前只有下载与去设置两个
- backlog 登记 TD-DESKTOP-N08(macOS 全屏 Space 下关闭-隐藏不区分全屏,P3,
  待复现——不能仅凭上游存在修复就断言 Sati 有该缺陷)与 TD-DESKTOP-N09
  (arm64 安装包内嵌 win32-x64 原生预编译件且无 ARM64 冒烟,P2)
- metrics 与 architecture-baseline 按本次工作树刷新
- code-facts 随 src/pilot/config 新增文件回填

arch 基线本次追认 3 条 file-size 增长:config.js 1224→1234(+10,409 冲突分支
与脱敏)、config.test.js 1029→1138(+109,新增用例)、satiConfig.js 854→858
(+4,CAS 转发)。
@xujian519
xujian519 merged commit b5686a2 into main Oct 7, 2026
3 checks passed
@xujian519
xujian519 deleted the feat/upstream-port-2026-10 branch October 7, 2026 01:28
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.

feat: 移植 PilotDeck 上游 2026-09-19 之后的增量

1 participant