6908 Commits
Author SHA1 Message Date
胡飞 21979414e6 chore(release): 准备 v0.18.6(CHANGELOG 双语条目 + 版本号)
- CHANGELOG.md 增加 v0.18.6 双语发布说明,覆盖本版全部改动:
  - 新功能:数据库对象列表全选与拖选、PostgreSQL 外部表与物化视图
  - 修复:SQL 页签并发执行、MySQL 未知字符集兜底解码、
    SSH 目录上传受限并发、macOS 关闭内置远程编辑器不再崩溃
- main/Cargo.toml 与 Cargo.lock 的主包版本 0.18.5 -> 0.18.6
- 一并把 origin/main 合入 dev,使发布 PR 收口 v0.18.5 之后的全部改动
2026-09-21 20:42:43 +08:00
胡飞 9708dea9df Merge remote-tracking branch 'origin/main' into dev 2026-09-21 20:39:20 +08:00
xiaofei 14370eb1dc Merge pull request #269 from feigeCode/fix/issue-265-sftp-upload-concurrency
fix(sftp): 目录上传改为受限并发,消除小文件串行往返(#265)
2026-09-21 19:31:04 +08:00
xiaofei 023536d32b Merge pull request #267 from feigeCode/fix/mysql-unknown-result-charset
fix(db): MySQL 结果列字符集未知时按 UTF-8 兜底解码,修复文本列显示为二进制
2026-09-21 18:56:38 +08:00
xiaofei ddd36f94fe Merge pull request #268 from feigeCode/fix/issue-262-editor-window-reuse
fix(editor): 关闭内置远程编辑器改为隐藏复用,规避 macOS Touch Bar 崩溃 (#262)
2026-09-21 18:56:21 +08:00
胡飞 82cdeb4731 fix(sftp): 目录上传改为受限并发,消除小文件串行往返(#265)
- 上传每个文件固定要付 6 次控制往返(stat/open/fsync/fstat/close/rename),
  串行执行时小文件吞吐由 RTT 而不是带宽决定,且任何时刻只有一个文件在传。
- 新增 upload_batch 模块:run_bounded 用 FuturesUnordered 做有界并发调度,
  ConcurrencyBudget 按槽位分配预算(小文件 1 槽、大文件独占全部槽位),
  ProgressHighWater 保证并发上报的进度单调不回跳。
- upload_dir_with_progress 小文件并发度 6;超过 SMALL_FILE_MAX_BYTES(512 KiB)
  的文件独占预算,行为与串行一致,不放大内存中的未确认字节。
- 失败语义保持不变:首个错误后不再接纳新任务,但在飞任务跑完再返回错误,
  避免 RemoteReplaceTemp 的暂存文件残留。
- 进度 transferred 改为全局原子累计(原先为「已完成文件 + 本文件」)。
- 验证:cargo test -p sftp 93 passed(新增 8 个);cargo clippy -p sftp
  --all-targets 无新告警;sftp_transfer/sftp_view/remote_file_editor 编译通过。
2026-09-21 17:17:45 +08:00
胡飞 ed2f79e852 fix(editor): 关闭内置远程编辑器改为隐藏复用,规避 macOS Touch Bar 崩溃
issue #262:Intel Touch Bar 机型(MacBookPro16,1)上关闭内置文件编辑器弹窗
必现 SIGILL。用户崩溃报告显示致命点在 AppKit:NSWindow 销毁后
-_NSTouchBarFinderObservation invalidate 在 NSDisplayCycleFlush 里注销 KVO
观察时抛出未捕获 NSException,-[NSApplication _crashOnException:] 以 ud2
终止进程。与 Rust panic 无关(release 为 panic=abort),Apple Silicon 无
Touch Bar 故不可复现。

修复:macOS 上关闭不再销毁原生窗口,改为 orderOut: 隐藏 + 复用:
- prepare_window_close 统一收口全部关闭路径(红按钮/⌘W/保存后关闭/
  末标签关闭),隐藏成功即保留 GPUI 窗口与注册表,下次打开复用
- editor_window_visibility::hide_for_reuse 仅 macOS 实现,其余平台/失败
  回退原 remove_window 行为
- client 下沉到每个 tab(修跨会话复用会走错连接的问题);同一窗口按
  (remote_path, Arc::ptr_eq) 匹配标签
- PendingCloseAction::Tab 改用稳定 tab id;prompt 引入代际计数,旧确认
  不得作用于复用后的新会话;隐藏时 next_tab_id 不回退,杜绝旧 I/O 落到
  新标签
- apply_load/save_error 返回是否命中标签,未命中的迟到错误不再弹通知

验证:cargo test -p remote_file_editor 70 passed(新增 5 个状态级复用/
代际/跨连接用例 + 3 个源码契约测试);原生隐藏/复用仍需 Touch Bar 真机
回归(报障人协助)。
2026-09-21 17:10:19 +08:00
xiaofei e847fbb270 Merge pull request #266 from feigeCode/feat/pg-foreign-table-matview
feat(db): PostgreSQL 外部表与物化视图纳入树、动作与转储
2026-09-21 17:01:19 +08:00
胡飞 d46a38d6ff fix(db): MySQL 结果列字符集未知时按 UTF-8 兜底解码
现象:连接某些 MySQL 兼容实现/代理时,结果列元数据里的 character_set 为 0
(collation_id=0、charset=unknown),decode_character 在取不到 decoder 时一律降级为
DbValue::Binary,文本列因此在网格里显示成「二进制 · N B」;表结构元数据、自定义 SQL 与
UNION ALL 结果不走 schema 纠偏,同样受影响,而字节本身是合法 UTF-8。

修复:字符集未知(collation 0 / 未映射)时用严格 UTF-8 兜底解码,与
decode_untyped_bytes 对无类型字节的判定保持一致(合法 UTF-8 且无控制字符)。
非法 UTF-8 或含控制字符的字节仍保留二进制 sidecar;wire 已声明 collation 63 的字节列仍走
wire-binary 分支;已声明字符集的列行为不变。

取舍:字符集未知的服务端上,内容是「可打印 ASCII」的真二进制列会显示为文本;这类字节此前
显示为二进制,属显示层面的取舍,若需严格区分可按 BINARY_FLAG 收窄。

验证:真机 MySQL 8.0.45 + 把列定义包 character_set 改写成 0 的透明代理复刻现场,表数据 /
UNION ALL / information_schema 三条路径的文本列均不再进入 binary_cells,X'000102FF' 这类真
二进制仍为二进制;cargo test -p db --lib 1311 passed(含 3 个新增 codec 回归测试);
clippy 对本文件无告警。
2026-09-21 16:36:03 +08:00
胡飞 4071882cb1 feat(db): PostgreSQL 外部表与物化视图纳入树、动作与转储
PG 连接的表目录写死 `c.relkind = 'r'`,外部表('f')和分区表('p')根本列不出来,
物化视图也找不到入口(视图走 `information_schema.views`,它不含物化视图)——与
Navicat 的行为不一致,用户已经建好的外部表在 Navop 里等于不存在。

- 列表:`list_tables` / `list_tables_view` 放开 relkind 到 `'r','p','f'`,按 relkind
  映射对象类型('f' → ForeignTable,'p'/'r' → Table),对象列表「类型」列分别显示
  Table / Partitioned Table / Foreign Table;新增 `list_materialized_views`(走
  `pg_class` relkind='m' + `pg_get_viewdef`),物化视图从普通视图里彻底分离。
- 树:新增 `DbNodeType::{ForeignTable, MaterializedViewsFolder, MaterializedView}`,
  外部表与普通表/分区表同处「表」目录(对齐 Navicat),物化视图单独一个目录;目录由
  `supports_materialized_views` 能力位控制,MySQL 等其它驱动不受影响。
- 动作:外部表与物化视图按 node_type 各自 scope 出菜单(Open / Rename / Truncate /
  Drop / Dump / Import / Export 等),删除与重命名按类型分派到 `drop_foreign_table`
  / `rename_foreign_table` / `drop_materialized_view`——不能把 `DROP TABLE` 打到外部表
  上。设计表 / 复制表 / 结构转储等尚无实现的动作不注册,因此不会出现点了没反应的项。
- 结构比较与结构转储:新增 `TableObjectType::ForeignTable`,`is_ddl_comparable()` 只让
  普通表参与,外部表自动从结构比较、数据比较、ER 图、整库 DDL 转储中排除(数据转储
  仍可用);`resolve_sql_dump_target` 补上新类型的映射,避免 Dump 静默报错。
- schema 限定:`rename_foreign_table` / `drop_materialized_view` 接受 schema 并生成
  schema 限定名——执行会话不会切 search_path,非 public schema 下的 DDL 原先会打错
  对象(或直接报错)。

验证:

- `cargo test -p db --lib` 1305 passed / 0 failed;`cargo test -p db_view --lib`
  703 passed / 0 failed / 1 ignored;`cargo check --workspace --all-targets` 无
  error/warning;对新增行做 clippy 比对,无新增告警。
- 新增真实库集成测试 `crates/db/tests/real_databases/postgres/foreign_matview.rs`
  (设置 `ONETCLI_TEST_POSTGRES_PASSWORD` 后才运行):在非 public schema 下真建外部表
  (postgres_fdw)、分区表与物化视图,断言列表类型、对象面板类型与树的归属,并真实
  执行 rename / drop 外部表和 drop 物化视图,最后校验 `list_views` 不混入物化视图。
  `cargo test -p db --test real_postgres` 5 passed;测试自带清理,不在库里留 schema、
  外部服务器或扩展。
- 未验证:GUI 端到端(右键菜单实际点击路径)没有真机回归,只覆盖到事件与 SQL 生成层。
2026-09-21 16:24:44 +08:00
xiaofei 46f4c67d37 Merge pull request #264 from feigeCode/fix/db-session-lock-per-session
fix(db): lock sessions individually so SQL tabs execute concurrently
2026-09-21 15:32:00 +08:00
胡飞 24e87f8627 fix(db): 会话复用与生命周期改为按 session 同步,修掉会话池三处并发缺陷
按 review 意见修复上一版(a044b63d3)留下的三处问题:

1. try_acquire_session 在持有会话表写锁时逐条 await 候选会话的 config()
   (原 manager.rs:633),一个正在跑长语句的会话会连带堵住其它页签与该连接的
   元数据路径 —— 本次要修的串行问题在复用/建会话路径上并没有修干净。
   现在会话表锁内只做「候选筛选 + 占用」,连接锁与 ping 一律在锁外;候选身份
   改用缓存的 DatabaseIdentity(由 verify_and_sync_database 同步刷新),校验
   失败或会话已不在池内就换下一个候选,不再持锁等待。

2. release_session_internal 用 bool 覆盖 in_use,释放与下一条语句的占用交错时
   会把正在执行的会话标成空闲,随后被 idle 清理回收(表现为 session not found)。
   改为引用计数占用 + 预留位:create_session/try_acquire_session 只做一次预留,
   由紧接着取连接的语句消费,保证一条语句恰好占用一次、释放一次,且两个并发
   create_session 不会共用同一会话。

3. release_session_internal 即使 detach 未成功也会 close_session,与并发的
   close_session/remove_all_sessions 重复 disconnect。现在只有真正把会话从池中
   摘出的调用方才负责断开;try_acquire_session 丢弃失效会话也走同一条路径。

附带:get_session_connection 在取得连接后复查会话是否仍在池内,避免把已摘除并
关闭的连接交出去;list_sessions 改读缓存身份,不再等待忙碌会话的连接锁;
get_session_config 保持读取实时配置,但不再在持有会话表锁时等待。

验证(macOS 本地,crates/db edition 2021):
- cargo test -p db --lib:1308 passed / 0 failed
- cargo test -p db:全量通过(tests/real_postgres.rs 4、tests/real_sqlite.rs 4,
  doc-test 1 ignored)
- cargo test -p db_view --lib:717 passed / 0 failed
- cargo check -p main:通过
- cargo clippy -p db --all-targets:manager.rs 仅剩 3 条既有告警(行号后移)
- rustfmt --edition 2021 crates/db/src/manager.rs

新增 5 个回归测试,均先复现失败再修复通过:过期释放覆盖新占用、复用扫描堵住
其它连接、并发 release/close 只 disconnect 一次、一次语句只占用一次占用单位、
复用扫描遇到忙碌会话立即返回。未跑 story 测试(需真实数据库与交互环境),
未在 Windows/Linux 上验证。
2026-09-21 14:51:08 +08:00
胡飞 a044b63d33 fix(db): 会话锁改为按 session 粒度,修复多页签 SQL 执行串行
问题:A 页签执行时 B 页签无法执行,即使 B 连的是另一台库。根因是
ConnectionManager 的整张会话表共用一把 RwLock:get_session_connection()
取得写锁后交给 SessionConnectionGuard,并一直持有到整条语句执行结束。
于是任一语句执行期间,所有页签的取连接、表数据查询、元数据与配置读取
全部排队(tokio RwLock 公平锁,排队的写请求还会连带阻塞后续读请求)。

修复:
- ConnectionSession.connection 改为 Arc<AsyncMutex<Box<dyn DbConnection>>>,
  每个会话持有自己的连接锁,语句只锁自己那条连接。
- 新增 SessionState(in_use/last_active)放入独立 StdMutex,状态查询不再
  等待执行中的语句;ConnectionSession 各方法改收 &self。
- 会话表改存 Arc<ConnectionSession>;get_session_connection 仅在查表与标记
  in_use 期间持有会话表锁,随后立即释放,guard 只持有该会话的连接锁
  (不再带生命周期,调用点无需改动)。
- close_session / remove_all_sessions / cleanup_expired_sessions /
  release_session_internal 改为锁内摘出会话、锁外关闭,避免拆连接时再次
  造成全局停顿。
- 保留原有语义:同一 session 仍互斥(物理连接串行)、被占用的会话不会被
  清理、取消与关闭仍等待执行结束;锁顺序统一为「会话表 → 连接锁」,
  不存在反向持锁路径。

验证:
- 新增两个并发回归测试(修复前均以 200ms/500ms 超时失败):
  session_connection_guards_do_not_serialize_different_sessions 断言不同
  session 取连接不互相等待且同一 session 仍互斥;
  executing_on_one_session_does_not_block_another_session 断言 A 阻塞执行时
  B 的 execute_session 仍能在超时内完成。
- cargo test -p db --lib 1313 passed / 0 failed(含 manager 38 个测试)。
- cargo test -p db 全量通过(含 real_postgres / real_sqlite 集成测试)。
- cargo test -p db_view --lib 717 passed。
- cargo check -p main 通过;cargo clippy -p db --all-targets 在 manager.rs
  无新增告警。
2026-09-21 13:46:25 +08:00
xiaofei 8e0bc8269e Merge pull request #263 from feigeCode/feat/db-objects-batch-select
feat(db_view): 数据库对象列表支持全选与拖选多行
2026-09-21 11:14:29 +08:00
胡飞 2b20f1eaa2 fix(db_view): 修正对象列表全选的无效补全链路与拖选交互
- 删除 SelectAllInScope 补偿链:对象页签只在容器节点 children_loaded 时才展示清单,
  宿主侧该判断必然命中,且列表本身不做截断,可见行即全部对象;一并移除只服务该链路的
  DbTreeView::store_loaded_children(它绕过了 lazy_load_children 的重建与事件收尾)
  和对应的字符串契约测试
- 拖选锚点改为每次按下无条件重置:gpui 只把 mouse_up 发给命中路径上的元素,指针在列表外
  松开时视图收不到释放事件,旧实现会沿用旧锚点截出错区间
- Shift 点击改用 ScrollStrategy::Nearest,不再把可见行顶到视口顶部
- Cmd/Ctrl 拖选支持加选:按下时快照选择集,拖选区间叠加其上,来回拖动可正常收缩
- 清理 PressOutcome、RowDragState::last_hovered 等死状态与重复状态
2026-09-21 10:43:16 +08:00
cnb.cIvqO7MMwEA 5beab53757 feat(db_view): 数据库对象列表支持全选与拖选多行 2026-09-21 10:43:16 +08:00
xiaofei 8c0aece579 Merge pull request #261 from feigeCode/dev
chore(release): 发布 v0.18.5(Windows 更新后应用未拉起 + 托盘隐藏窗口无法叫回)
v0.18.5
2026-09-20 21:05:39 +08:00
胡飞 2b64a41c8a chore(release): 准备 v0.18.5(CHANGELOG 双语条目 + 版本号)
- CHANGELOG.md:新增 v0.18.5 双语条目,覆盖 Windows 更新后新版本未拉起、
  Windows 托盘隐藏窗口二次启动无法叫回、内置 AI 保存连接的
  function-calling schema、外部驱动最低版本门、终端左边距空行的时间戳占位
  与行号,以及随带的 UI 依赖链跟进
- main/Cargo.toml、Cargo.lock:版本 0.18.4 -> 0.18.5
2026-09-20 21:04:10 +08:00
xiaofei e3829d0449 Merge pull request #260 from feigeCode/fix/oceanbase-driver-min-version-249
fix(db): 外部驱动增加最低版本门,OceanBase 低于 0.1.12 时引导用户更新
2026-09-20 20:49:33 +08:00
xiaofei 6f0692ae3c Merge pull request #259 from feigeCode/fix/terminal-timestamp-placeholder-244
fix(terminal_view): 修复 SSH 连接窗口空行的时间戳占位括号与行号
2026-09-20 20:42:40 +08:00
胡飞 9ce529e45c fix(db): 外部驱动增加最低版本门,OceanBase 低于 0.1.12 引导更新
宿主此前只判断外部驱动是否已安装,不校验版本:OceanBase 0.1.12 之前的驱动对
无符号列发十进制文本,宿主反序列化会直接判类型不匹配
(invalid type: string "4", expected u64),整表读取失败;而旧驱动既不会被自动
更新,宿主也不会提示,用户只能看到一句读不懂的报错。

- DriverRequirement::Required 增加 minimum_version
- required_external_driver 按驱动 id 声明最低版本(当前: oceanbase -> 0.1.12)
- 连接守卫改为版本感知:已安装但低于最低版本时同样走安装/更新引导,
  提示文案带"(最低版本 0.1.12)"
- 安装流程沿用既有 find_database_driver_entry_for_requirement 校验市场版本

Refs #249
2026-09-20 20:15:25 +08:00
xiaofei ae9119ec25 Merge branch 'main' into fix/terminal-timestamp-placeholder-244 2026-09-20 20:02:16 +08:00
xiaofei 95f69e1a06 Merge pull request #258 from feigeCode/chore/gpui-kit-upstream-0.6.4
chore(deps): 合入 gpui-kit 上游改动,gpui-shell 跟进到 0.6.4
2026-09-20 20:02:04 +08:00
xiaofei d4ca74350e Merge branch 'main' into chore/gpui-kit-upstream-0.6.4 2026-09-20 19:33:10 +08:00
胡飞 15b1e2505f chore(deps): 合入 gpui-kit 上游改动,gpui-shell 跟进到 0.6.4
- gpui-kit(feigeCode/gpui-kit port-ce-generic-to-main)升到 23c8e5b78:合入上游
  45 个提交(0.6.1 → 0.6.4)。其中 QuickJS JIT 升到 0.12.9,与本分支的 32 位改动
  重叠,冲突已在 gpui-kit 侧解决(保留按位宽拆分 + i686 补丁;上游把软换行的行换成
  `InputLine`、对话框 button props 改为合并语义,内联 widget 布局与 alert/confirm
  已相应适配)。Cargo.toml 的 8 处 pin 一起回填到新 rev,Cargo.lock 重新解析
  (gpui-wry 0.6.4、rquickjs 0.12.9,旧 rev 无残留)。
- 宿主 gpui-shell 版本 0.6.1 → 0.6.4:extension-runtime 的 GPUI_SHELL_VERSION 与
  各处测试清单、文档示例同步提升。该校验对 0.x 按 minor 精确匹配,旧值会被直接拒绝。

验证:cargo check -p remote_desktop_view(新 gpui-kit + quickjs-jit i686 分支)
→ Finished dev profile,0 error。
2026-09-20 19:20:21 +08:00
胡飞 f1dec7d5a1 fix(terminal_view): 空行不再显示行号
左边距改为以“该行是否已有输出”为唯一判定:时间线里没有这一行的时间戳,
说明它还没有任何输出(光标下方的空行),此时整行留空——既不画时间戳
占位括号,也不显示行号。

原来只有显示时间戳时才查时间线,行号列会无条件铺满整屏,空行上挂着
一串孤立的行号。现在两列共用同一个判定。

行时间轴新增一次加锁的按行批量查询 `labels`,视图整帧只加锁一次、不做
逐行查询;原先仅服务视图逐行查询的单行 `label` 已无生产调用方,一并移除,
只保留批量这一条查找路径。

Refs #244
2026-09-20 19:14:37 +08:00
xiaofei 659a037d71 Merge pull request #254 from feigeCode/fix/connections-save-llm-schema
chore(deps): fork 链更新到 gpui-pre fork-0.3.110 + gpui-kit 8caa3aee,shell 依赖链支持 32 位 Windows
2026-09-20 19:08:22 +08:00
胡飞 9dd85dd21e fix(terminal_view): 无输出的行不再显示时间戳空白占位符
左边距为每一屏幕行生成时间戳文本,尚未有输出的行(光标下方的空行)
此前会画出 `[          ]` 占位括号,整屏空行看起来全是括号和空白。

改为没有时间戳时不画占位符:只显示行号时补足时间戳列宽的空白以保证
行号对齐,时间戳与行号都不显示时该行留空,由元素侧跳过绘制。列宽与
行号对齐行为不变。

Refs #244
2026-09-20 18:53:55 +08:00
胡飞 f30ca41cb3 fix(dev-registry): 测试清单补上 gpui_shell 0.6.1
`main/src/dev_extension_registry.rs` 的测试辅助 `write_manifest` 仍写
`"gpui_shell": "0.2.0"`。shellViews 要求声明与宿主兼容的语义版本,而 `0.x` 的判定
是 minor 精确匹配,于是这些清单在解析期就被拒,7 条测试拿不到预期的视图/指纹:

  dev_extension_registry::tests::load_adds_dev_prefix_and_keeps_root
  dev_extension_registry::tests::loading_dev_project_registers_shell_views_without_existing_catalog
  dev_extension_registry::tests::installed_catalog_refresh_keeps_development_views
  dev_extension_registry::tests::duplicate_extension_id_reports_error_instead_of_silently_skipping
  dev_extension_registry::tests::duplicate_load_replaces_previous_entry
  dev_extension_registry::tests::reload_replaces_manifest_in_place
  dev_extension_registry::tests::fingerprint_changes_when_ui_source_changes

上一轮 CI 没有暴露它们:`cargo test` 在第一个失败的测试二进制
(extension-runtime)就中止,navop 这个测试二进制根本没被执行。修好前一环后才轮到
它们。

验证:本机 `cargo test --all --no-fail-fast`(CI 命令 `cargo test --all` 的超集,
后者遇到第一个失败即停)→ 207 个套件 ok,0 个失败套件属于本仓。唯一的失败是
universal-plugins 的 `extension_manifests_load_against_this_host`,它只在本地存在
相邻仓 `../../../navop-extensions/extensions/composite` 时才执行,CI 上直接 SKIP;
其失败原因是该仓 docker / mqtt / rocketmq / elasticsearch 清单也还写着 0.2.0,属于
相邻仓自己的后续改动,不在本 PR 范围。本机已装 `wasm-tools`,故 wasm 相关套件无环境噪声。
2026-09-20 18:01:05 +08:00
胡飞 bd7548e53b fix(extension-runtime): 补齐 gpui_shell 版本 fixture 到 0.6.1
abb055843 把宿主 `GPUI_SHELL_VERSION` 从 0.2.0 升到 0.6.1(跟随 gpui-shell
crate 版本),但漏了三处声明,CI 因此在 macOS / Linux / Windows 三个平台上各挂
同样的 3 条测试:

  extension::manifest::parser_tests::reference_resource_plugin_manifests_are_parser_valid
  global::tests::development_views_are_rebuilt_with_installed_views
  global::tests::invalid_development_manifest_does_not_remove_valid_views

    manifest 字段 /engines/gpui_shell 非法: requires gpui-shell 0.2.0, host provides 0.6.1

原因是 `0.x` 的兼容判定要求 minor 精确匹配
(crates/extension-runtime/src/extension/manifest/shell_validation.rs):

    if required.major == 0 { current.major == 0 && current.minor == required.minor }

补齐的三处:

- crates/extension-runtime/src/global.rs 测试内联 manifest
- docs/extension-resource-plugins/examples/elasticsearch/extension.json
- docs/extension-resource-plugins/development-guide.zh-CN.md 的示例清单

验证:cargo test -p extension-runtime --lib → 214 passed,原先挂的 3 条全绿。
唯一未过的是 db_tree_action_runs_registered_wasm_component,与本次改动无关:它
读 `fixtures/coverage-plugin/wasm/coverage_component.wat`,而 wasmtime 的 `wat`
解析器只在 crates/extension-wasm 的 `[dev-dependencies]` 里打开,CI 跑的是
`cargo test --all`(feature 统一启用)因此通过,本地收窄成 `-p extension-runtime`
就不带该 feature。该测试在本次 CI 运行的上一条日志里是通过的。
2026-09-20 17:05:05 +08:00
xiaofei 477987e6fc Merge branch 'main' into fix/connections-save-llm-schema 2026-09-20 15:51:49 +08:00
xiaofei bd7a8dd77f Merge pull request #245 from feigeCode/fix/connections-save-llm-schema
fix(agent): connections.save 的 function-calling schema 去掉顶层 oneOf
2026-09-20 15:44:41 +08:00
胡飞 07dba58f0d chore(deps): gpui-kit 升到 8caa3aee,适配 gpui-pre 的工厂式 inspector 注册
gpui-pre 快照 fork-0.3.110 把 `App::register_inspector_element` 从单层闭包改成了
工厂闭包 `Fn(&mut Window, &mut App) -> F`(registry 按 (inspector, state 类型)
缓存渲染器,首次渲染时才建)。gpui-component 的 inspector 注册随之改成在工厂里
创建 `DivInspector` entity,去掉原来的进程级 `OnceCell`,写法对齐上游
`crates/inspector_ui/src/inspector.rs`。

不跟着改的话,`cargo check -p remote_desktop_view` 会在 gpui-component 上直接挂:

    error[E0593]: closure is expected to take 2 arguments, but it takes 4 arguments
      --> crates/component/src/inspector.rs:52:8

变更:
- gpui-kit 8 处 pin(`gpui-component` 等 7 个依赖 + `rquickjs` patch)→ 8caa3aee
- 文档里的 revision 同步
- Cargo.lock 重新解析

验证:cargo check -p remote_desktop_view → Finished,0 error。
2026-09-20 15:41:26 +08:00
胡飞 19fd01a71a deps: bump gpui-pre fork to fork-0.3.110
Snapshot cut from the local Zed `gpui-pre-release` branch after merging
`upstream/main` (zed ed16e536e7), i.e. the first refreshed snapshot since
f59ad1b / fork-0.3.109.

One crate-set change comes with the merge: upstream deleted `crates/media`
(4b47ceb9d3 "gpui_macos: Migrate screen capture to objc2 (#64251)"), so the
snapshot no longer publishes `gpui-pre-media` and the corresponding patch
entry is dropped. Nothing in Navop or in the snapshot still depends on it.

The dynamic-texture API (`Window::update_dynamic_texture`,
`Window::paint_dynamic_texture`, `DynamicTexture`) is preserved by the merge;
`remote_desktop_view` keeps using it.

Regenerated with:
  script/publish-gpui-pre-fork.py --tag fork-0.3.110
  script/migrate-to-git-fork.py --tag fork-0.3.110
2026-09-20 15:35:11 +08:00
xiaofei 4d91415071 Merge branch 'main' into fix/connections-save-llm-schema 2026-09-20 14:59:16 +08:00
胡飞 abb0558434 chore(deps): gpui-kit / quickjs-jit 升到支持 32 位 Windows 的版本
gpui-shell 的 QuickJS 运行时依赖改为按目标位宽拆分:64 位
macOS/Windows/Linux 保留 `compiler`,其余目标只跑解释器,于是
i686-pc-windows-msvc 上 shell 依赖链可以编译(此前会直接报缺少
i686-pc-windows-msvc 绑定)。

- gpui-kit: 6df86ef8 → 329a064a
- quickjs-jit: 51c57e3c → 1a5b7fa

gpui-wry / gpui-kit / rquickjs patch 三行一并同步到同一 rev,避免两份
gpui-kit 与两份 quickjs-jit 并存导致 ModuleDef / Ctx / Error 类型不统一。
2026-09-20 14:54:02 +08:00
胡飞 6c303e2839 fix(agent): connections.save 的 function-calling schema 去掉顶层 oneOf
现象:内置 AI 里让模型保存连接时,整个任务直接失败:

    模型调用失败: Invalid request: OpenAI: Invalid schema for function
    'connections_save': schema must have type 'object' and not have
    'oneOf'/'anyOf'/'allOf'/'enum'/'const'/'not' at the top level

根因有两层:

1. `connections.save` 的工具参数 schema 用顶层 `oneOf` 表达「创建 or 更新」,
   分支里还带 `not`。provider 要求工具参数 schema 顶层是纯 object,遇到这些
   组合关键字会拒绝整个模型请求,而不是拒绝这一个工具——所以是任务级失败。
2. 本仓的 `validate_function_calling_schema` 当时只要求顶层是 object、组合
   分支自身是 object,因此放行了这份 schema,直到请求发到 provider 才炸,且
   报错只带函数名,很难定位到具体工具。

修复:
- `connections.save` 改为扁平 object schema,把「不带 id 即创建、带 id 即更新」
  写进 schema/字段描述,二选一约束仍由运行时 `save` 按是否携带 `id` 分流,行为
  与之前一致。
- `agent_runtime` 的 function-calling 校验新增顶层关键字拦截
  (oneOf/anyOf/allOf/enum/const/not),本地就报错并带上工具名与指针;嵌套层
  (如 `properties.*` 内的 anyOf)不受影响。

验证:
- `cargo test -p navop_runtime --lib connections::` → 46 passed
- `cargo test -p agent_runtime --lib tools::spec` → 10 passed
- `cargo test -p main --bin navop
  agent_runtime_tool_schemas_convert_to_function_calling` → 1 passed
- 新增回归契约:连接工具 schema 顶层 provider-safe;LLM 边界上所有已暴露工具都
  必须能转换成 llm-connector 工具定义(旧代码下该测试必红)。
2026-09-20 14:52:00 +08:00
xiaofei 9cb13ae07c Merge pull request #243 from paofu-cium/fix/update-restart-wait-previous-instance
fix(update): 更新替换后等旧实例退出再拉起新版本
2026-09-20 14:51:57 +08:00
xiaofei f7266baba7 Merge branch 'main' into fix/update-restart-wait-previous-instance 2026-09-20 14:20:59 +08:00
xiaofei e9baf00990 Merge pull request #242 from paofu-cium/fix/tray-restore-on-forwarded-startup
fix(tray): 二次启动时叫回被托盘隐藏的主窗口
2026-09-20 14:19:07 +08:00
xiaofei 6edc4c96b4 Merge branch 'main' into fix/tray-restore-on-forwarded-startup 2026-09-20 13:46:13 +08:00
胡飞 913a0d64c6 fix(update): 等不到旧实例退出时不再盲重启
超时(10s,原 30s)或探测失败统一视为「无法确认旧实例已退出」,不再拉起新版本:新版本在旧实例仍占着单实例管道时启动,要么转发后自己退出(用户看不到任何窗口),要么弹「启动请求没能交给它」。改为弹系统对话框提示用户结束 Navop 进程后手动启动——更新 helper 没有窗口,release 又是 windows_subsystem = windows,eprintln 用户看不到。
2026-09-20 13:44:47 +08:00
泡芙 1f4689561d Merge branch 'feigeCode:main' into fix/update-restart-wait-previous-instance 2026-09-20 12:34:09 +08:00
泡芙 237fdccdcb fix(tray): 二次启动时叫回被托盘隐藏的主窗口
现象:主窗口经「关闭按钮 → 最小化到托盘」隐藏之后,再次启动 navop(双击图标,
或通过关联文件打开)都不会再把窗口显示出来;但如果先用系统最小化、再启动,则
一切正常。

根因有两层:

1. 转发的启动请求只调了 `window.activate_window()`,从没走过托盘自己那套可见性
   适配器。gpui_windows 的 `activate()` 只在 `IsIconic`(系统最小化)时才补一次
   `ShowWindowAsync(SW_RESTORE)`;而托盘隐藏走的是 `ShowWindow(SW_HIDE)`,隐藏
   窗口不是 iconic,于是一次 ShowWindow 都不会发出,紧随其后的
   `SetActiveWindow`/`SetFocus` 对不可见窗口也毫无作用。系统最小化恰好命中
   `IsIconic` 分支,所以那条路径表现正常——这正是「最小化之后再操作可以显示」的
   由来。`file_open` 内部同样只有 `activate_window()`,关联文件打开一并受影响。

2. `window_visibility` 的 Windows 恢复写死了 `SW_RESTORE`。Win32 文档写明它对
   最大化窗口会「还原到原始尺寸与位置」,也就是取消最大化;而二次启动时窗口往往
   正处在最大化状态。若把这条路径直接接到转发上,会把用户的最大化窗口缩回去,
   等于用一个更显眼的问题换掉原问题。

修复:转发循环复用 `window_visibility::main_window_target` +
`show_main_window`,并把句柄获取放在窗口借用内、原生调用放在借用之外——原生激活
回调会同步重入 GPUI 抢同一个 App 借用,写在 `cx.update_window(...)` 闭包内只会
得到 `RefCell already borrowed`。同时让 `platform::show` 按 `IsIconic` 分派:
只有被最小化时才用 `SW_RESTORE`,否则用 `SW_SHOW`(按当前尺寸与位置显示,保住
最大化,对已可见窗口是无操作)。补一条源文本契约测试,同时钉住「借用外恢复」与
「恢复先于打开文件」两个形态约束。

验证:
- 真机复现(安装版 v0.18.4,临时 --data-dir,未触碰真实配置):二次启动确实走了
  转发(转发方退出码 0、场上窗口数仍为 1),但主窗口 `IsWindowVisible=false`;
  此后外部直接 `ShowWindow(SW_SHOW)` + `SetForegroundWindow` 立即恢复可见。带
  文件路径启动同样转发、同样恢复不了(修正「关联文件打开可以」的观察:那应发生
  在系统最小化态,而非托盘隐藏态)。
- 真机实测 `SW_RESTORE` 与 `SW_SHOW` 对「隐藏且最大化」窗口的差异:`SW_RESTORE`
  后可见但 `IsZoomed` true→false;`SW_SHOW` 后可见且 `IsZoomed` 保持 true;对已
  可见窗口 `SW_SHOW` 完全无操作。据此确定分派策略。
- 离线契约校验(`.workbuddy/tmp/tray-restore-contract/`,用 `#[path]` 直接引用仓库
  真实源文件、`--offline` 编译):3/3 通过,覆盖本次新增的两条 main.rs 断言与
  window_visibility.rs 的分派形态。
- 测试目标:`CARGO_INCREMENTAL=0 cargo-msvc.py test -p main --bin navop
  --config profile.dev.package.main.debug=0` → **634 passed / 0 failed / 0 ignored**,
  测试本体 4.55s,含本次新增的
  `embedded_cli_removal_tests::forwarded_startup_request_restores_a_tray_hidden_window`。
  本轮编译耗时 1h3m:上游把 gpui-pre 升到 fork-0.3.109 且本地无该 rev 缓存,
  需联网重建整条依赖链。
- 另:`rustfmt` 已对改动文件做过格式化。

未验证项:真机完整「隐藏 → 更新 → 自动拉起新版本」链路;macOS/Linux 的
`platform::show` 未被本次改动触及(改动全部在 `target_os = "windows"` 分支内)。
2026-09-20 11:57:06 +08:00
xiaofei e8ce41d4a8 Merge pull request #241 from feigeCode/chore/gpui-kit-rename-and-screenshot-safe
Point gpui dependencies at gpui-kit and randomize screenshot-safe display data
2026-09-20 11:18:47 +08:00
泡芙 1f5802b0a0 fix(update): 更新替换后等旧实例退出再拉起新版本
Windows 允许重命名正在运行的 exe(实测 `MoveFileW` 返回 0),所以
`replace_target_with_backup` 的 rename + 拷贝新文件**能在旧实例仍然存活时跑完**,
而原来的 `let _ = remove_file_if_exists(&backup_path)` 恰好丢掉了"旧进程是否已退出"
的唯一信号:helper 替换完立刻 `restart_application()`,新版本在旧实例仍持有 Windows
单实例管道时启动,就会走进"转发给已有实例"分支、拿到确认后立刻退出,用户看到的是
"更新完成后应用不再出现"。

运行中的 exe 无法被删除(实测 `DeleteFileW` 返回 `ERROR_ACCESS_DENIED(5)`,进程
退出后转为成功),因此"备份文件变成可删除"就是旧进程已退出的可靠探针。新增
`wait_for_previous_instance_exit()`:有界等待(30s / 200ms 轮询)后再拉起新版本,
超时只记录并继续,避免旧进程异常不退时把更新卡在替换之后;首次安装(无备份)
不等待。

"仍被占用"按**原始错误码**判定(`file_is_still_in_use()`),不能用 `ErrorKind`:
std 把 5 映射成 `PermissionDenied`,却把 32 映射成 `Uncategorized`,按 kind 判会漏掉
`ERROR_SHARING_VIOLATION`。这与单实例模块按原始码分类的理由一致。

这条链路是单实例门禁收紧后才暴露的:在此之前转发恒失败、门禁放行,新版本反而
"能起来",掩盖了旧实例与新版本并存的时序问题。

验证:
- `cargo test -p main --bin navop --config profile.dev.package.main.debug=0`:585 passed /
  1 failed,测试本体 3.33s。唯一失败是**既有**的
  `home_tab::tests::rendering::recent_section_does_not_participate_in_search`(纯源文本契约,
  输入 `main/src/home_tab/content.rs` 未被本提交触碰;它 split 的函数实际在 `home_layout.rs`,
  上游已改断言),本分支基点即为红,与本改动无关。本提交新增的
  `restart_waits_until_the_previous_instance_releases_the_executable` 通过。
- 独立探针 `.workbuddy/tmp/share-mode-probe/`(std-only 独立 crate,与产品逻辑同构,
  秒级编译):3/3 PASS —— 备份被占用时消耗完整个 timeout 且不删除(410ms/400ms)、
  释放后 819µs 内放行并清理备份、无旧文件时不等待(132µs);并复现 std 的错误码映射
  `raw 5 -> PermissionDenied` / `raw 32 -> Uncategorized`(按 `ErrorKind` 判会漏掉 32)。
- 原始文件语义探针 `.workbuddy/tmp/update_race_probe_raw.py`(裸 `MoveFileW` /
  `DeleteFileW`):运行中的 exe 可重命名(winerror=0)、不可删除
  (`ERROR_ACCESS_DENIED=5`)、进程退出后删除成功。
- 未验证:真机跑一次完整更新安装(需要一个真实 release 包替换正在运行的 exe,
  无法在不干扰用户真实安装的前提下构造)。
2026-09-20 10:43:19 +08:00
胡飞 bc36868c0a docs: 更新应用截图 2026-09-20 10:24:24 +08:00
胡飞 a3e3b77595 feat(home): screenshot-safe 下随机化连接与分组的展示名
screenshot-safe 之前对所有连接使用同一套固定占位符,截图里每张卡片都长一样。
现在改为按连接 ID 生成稳定的假数据:同一个连接每次渲染结果一致,不同连接之间
互不重名,地址只使用 example.com 与文档专用网段(192.0.2.x / 198.51.100.x /
203.0.113.x),不会指向真实主机。

新增 main/src/screenshot_safe.rs 统一提供脱敏数据,首页卡片、连接信息行以及
左侧连接树里的最近连接名、分组名都改为经过它。只替换展示字符串,
HomePage.connections / workspaces 保持原样,因此加密存储、云同步与实际连接
仍然使用真实数据。
2026-09-20 10:24:24 +08:00
胡飞 1142f2f843 chore(deps): 依赖地址改为 gpui-kit 仓库
上游 longbridge/gpui-component 已改名为 longbridge/gpui-kit,fork 同步改名,
所有 gpui-* 依赖、patch、文档与同步脚本里的仓库地址一并更新。

同步脚本同时修掉了三处与旧仓库名绑定的匹配逻辑:守卫条件改为按 gpui-kit
仓库名匹配、Cargo.lock 校验的 rev 正则允许缺少 ".git"、依赖行允许只锁定
rev 而没有 version 字段。
2026-09-20 10:24:22 +08:00
xiaofei e16f479126 Merge pull request #240 from feigeCode/dev
chore(release): 发布 v0.18.4(macOS 托盘恢复崩溃 + Windows 重复启动)
v0.18.4
2026-09-20 00:08:10 +08:00