Commit Graph
6899 Commits
Author SHA1 Message Date
胡飞 22d90eb2bf chore(ci): 移除临时的 Windows 便携包验证 workflow 2026-09-21 16:31:22 +08:00
胡飞 786c6454cb fix(db): MySQL 结果列字符集未知时按 UTF-8 兜底解码
现象:连接某些 MySQL 兼容实现/代理时,结果列元数据的 character_set 字段为 0
(日志里 collation_id=0、charset=unknown、cause=charset-decode),而
decode_character 在拿不到 decoder 时一律降级为 DbValue::Binary,文本列因此在网格里
显示成「二进制 · N B」;元数据查询与自定义 SQL 不走 schema 纠偏,同样受影响。
样本显示这些字节是合法 UTF-8 且不含控制字符,即内容本身是文本。

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

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

验证:cargo test -p db --lib 1314 passed;新增 3 个 codec 回归测试(collation 0 与
未映射 collation 的文本解为 Text,控制字符与非法 UTF-8 仍为 Binary);
clippy -p db --all-targets 对本文件无告警。
2026-09-21 16:18:29 +08:00
胡飞 b9a36e065d chore(ci): 临时验证 workflow 改用 push 触发
workflow_dispatch 要求 workflow 先存在于默认分支 main,特性分支上无法手动触发,
因此临时增加对 feat/db-objects-batch-select 的 push 触发;inputs 为空时用统一的
env 默认值(BUILD_MODE/INCLUDE_RDP/LABEL)保证与手动触发行为一致。
2026-09-21 13:39:47 +08:00
胡飞 1a60f0cbab chore(ci): 增加 Windows 便携包临时验证 workflow
workflow_dispatch 触发,只产出 Windows x64 便携包(navop.exe + navop.portable
标记),供他人验证改动,不发布 GitHub Release,验证结束后可直接删除本文件。

- 走与正式发布一致的 windows-x64 口径:sccache(安装失败退化为无缓存)、
  NASM/CMake、MSVC 环境、RDP 探针门;include_rdp 可关闭以缩短构建时间。
- build_mode 默认 fast(thin LTO + codegen-units=16),可切 release(fat LTO)。
- 产物为 navop-win-x64-portable-<short-sha>-<label>.zip 及 sha256,
  保留 30 天,运行摘要里给出 commit 与下载位置。
2026-09-21 13:37:55 +08:00
胡飞 46f80959f8 chore(db): 增加 [result-diag] 排查日志,定位文本列被显示为二进制
问题:部分 MySQL 连接的字符列在结果网格里显示为「二进制 · N B」,
现有证据不足以区分是会话结果编码、服务端下发字节,还是外部驱动误判。

新增(统一前缀 [result-diag],定位后整体可删):
- result_diagnostics / ipc::diagnostics:列元数据(type/flags/collation_id/
  charset/collation/cause)与有界字节样本(hex 截断 16 字节、utf8 与控制字符统计),
  普通查询走 debug,出现二进制单元格才走 info,默认 info 级别即可落盘。
- mysql/connection:连接后打印会话变量(character_set_results 等)与 init_commands。
- mysql/codec:describe_binary_cause 区分 wire-binary / charset-decode / bit /
  json / scalar,避免把数值列的 collation 63 误读成 bug。
- query_result_normalization:输出每列 result_charset/schema_charset/decoder 与纠偏前后
  的二进制单元格数。
- ipc/connection:外部驱动结果打印 driver id@版本 + 列 spec + bytes 样本,
  用于区分内置 mysql_async 路径与 navop-extensions 驱动侧。

验证:cargo test -p db --lib 1313 passed;cargo check -p main 通过;
新文件 clippy 无告警;真实 MySQL 8.0.45 端到端复现(SET character_set_results=binary
后文本列出现 collation_id=63 + cause=charset-decode),确认日志可定性三种根因。
2026-09-21 13:37:50 +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
胡飞 fec47041f8 chore(release): 准备 v0.18.4(CHANGELOG 双语条目 + 版本号)
- CHANGELOG.md:新增 v0.18.4 双语条目(macOS 托盘恢复窗口崩溃、Windows 重复启动)
- main/Cargo.toml、Cargo.lock:版本 0.18.3 -> 0.18.4
2026-09-19 23:40:21 +08:00
胡飞 3259227131 Merge remote-tracking branch 'origin/main' into dev 2026-09-19 23:38:58 +08:00
xiaofei 984b009125 Merge pull request #239 from paofu-cium/fix/windows-single-instance-forwarding
fix(single-instance): 修复 Windows 重复启动会开出第二个窗口
2026-09-19 23:36:06 +08:00
xiaofei c9de5b11ed Merge branch 'main' into fix/windows-single-instance-forwarding 2026-09-19 23:04:32 +08:00
胡飞 1ddd63f311 Merge branch 'main' into fix/windows-single-instance-forwarding
解决与 PR #228(feat(tray): 关闭按钮询问「最小化到托盘 / 退出应用」,并修复
Windows 重复启动)的冲突,让本 PR 重新可合。

- main/src/windows_single_instance.rs:采用 PR 侧重写(SingleInstanceError 枚举、
  worker + 决策通道、Claim/ForwardAttempt、create_pipe_instance)。main 侧
  bd34cc464 新增的 instance_name_taken 语义已由 PR 的 is_name_in_use(Windows 专用,
  认 ERROR_ACCESS_DENIED=5)与 is_connect_retryable(认 2/231/5)覆盖;其被自动合并
  保留在非冲突区的定义一并删除,否则留下 3 个 dead_code 警告
- Cargo.lock:按合并后的 Cargo.toml 由 cargo 同步(保留 main 侧新增依赖,
  去掉 PR 已移除的 interprocess)
- main/src/main.rs、main/Cargo.toml:自动合并无冲突

验证:cargo check -p main --all-targets(0 警告)、cargo test -p main --bin navop
(634 passed, 0 failed)。Windows 侧属 CI 覆盖范围(本机无 MSVC 工具链,跨编译
在 libz-sys 处止步)。
2026-09-19 23:04:04 +08:00
xiaofei f47b59cb5a Merge pull request #238 from feigeCode/fix/tray-restore-macos-selector
fix(tray): 托盘恢复主窗口按系统版本选择激活选择器,修复 macOS 13 及更早系统崩溃
2026-09-19 22:53:03 +08:00
泡芙 737d215d51 fix(single-instance): 修复 Windows 重复启动会开出第二个窗口
Windows 单实例此前有三层叠加缺陷,任何一层单独存在都足以让「双击两次」得到两个 GUI:

1. 占用判定用错了错误码。claim_or_forward 按 io::ErrorKind::AddrInUse 判断「名称已被
   占用」,但 interprocess 首次建实例带 FILE_FLAG_FIRST_PIPE_INSTANCE,名称冲突时 OS
   返回 ERROR_ACCESS_DENIED(5),Rust std 把它映射为 PermissionDenied;而全 crate 唯一
   的 AddrInUse 处理在 Unix UDS 路径上。于是这个分支在 Windows 上永远不会命中,转发
   逻辑一次都没有被执行过。
2. 转发链路在 Windows 上不可用。接收端与发送端都调用 interprocess 的 set_recv_timeout /
   set_send_timeout,而它在 Windows 命名管道上恒返回 ErrorKind::Unsupported
   ("named pipes do not support I/O timeouts")。这是被第 1 条挡住的潜伏缺陷,第 1 条
   一修就会立刻暴露出来。
3. 启动门禁把「建不了单实例」解释成「可以再开一个」:main.rs 只有转发成功才 return,
   Err 分支只记一条 warn 就继续初始化 GUI。

本次按 tokio 命名管道重写 IPC 层,并收紧启动门禁与 ACK 语义:

- 互斥只由 ServerOptions::first_pipe_instance(true) 提供,且只给首个实例;服务循环补建
  的实例一律不带该标志,否则会把自己也挡在门外。
- 占用与连接错误全部按原始 Win32 错误码分类:ERROR_ACCESS_DENIED(5) 表示名称已被占用;
  客户端可重试码为 2/5/231(231 在 std 里落到 Uncategorized,按 kind 判会漏掉)。
- 所有 I/O 用 tokio::time::timeout 加时间边界,每条连接独立 task:一个半开连接不会拖住
  后续启动请求。接受循环先补建下一个实例、再移交当前连接,名称不会出现无主窗口;循环
  异常退出时保持名称被占用并停驻(fail-closed)——宁可让后续启动转发失败后明确报错退出,
  也不允许第二个 GUI 进来。
- ACK 收紧为「启动请求已入队」:入队失败回 [0],不再假成功。
- 门禁区分「转发失败」与「监听失败」并给出各自的提示文案,Err 一律提示后 exit(1)。
  release 是 windows_subsystem = "windows"、没有控制台,用户看不到 eprintln,提示走
  MessageBoxW。
- 顺带修掉一个空转断言:windows_single_instance_gate_precedes_application_creation 里
  assert!(!source.contains("continuing startup")) 的字面量本身就在 include_str! 的范围内,
  会自己满足自己。现在断言只看 production_source()(截掉测试模块的生产代码),并补上
  exit(1) 与提示函数的断言。

依赖侧:main 不再使用 interprocess,从 main 的依赖表中摘除(工作区声明保留,因为
extension-driver 与 extension-host 仍在使用,Cargo.lock 相应少一行)。tokio 增加
io-util / net 两个 feature,二者已由 public_mcp、sftp 启用,不引入重编。

验证:
- cargo test --offline:9 passed。测试放在一个独立 crate 里,用 #[path] 直接引用
  main/src/windows_single_instance.rs 本体(不是副本),依赖按 Cargo.lock 精确版本锁定。
  其中 5 个是真实 Windows 命名管道传输测试:转发成功且主实例确实收到请求、入队失败时
  ACK 必须为拒绝、半个请求超时断开且不阻塞后续请求、4 线程并发抢占只有 1 个取得主实例
  资格、不同配置目录各自独立。
- cargo clippy --offline --all-targets -- -D warnings:0 warning。
- rustfmt --edition 2024:windows_single_instance.rs 无格式差异,main.rs 新增片段无差异。
- 原始故障的真机探针复现:第 2 次 create_sync 返回 PermissionDenied(raw 5)、
  kind() == AddrInUse 命中为 false、set_recv_timeout 返回 Unsupported。
- 未验证:main crate 本体的编译,以及真机双击验收(第二次启动只唤起已有窗口)。原因:
  依赖图含 git 依赖 quickjs-jit,其 rev 需联网获取,而本地构建缓存被清空后正在做全量
  编译,尚未跑完;托管 CI 在 Windows 平台会编译 main 本体,可作为该缺口的补充验证。
2026-09-19 22:31:34 +08:00
胡飞 c1a637d1e8 fix(tray): 托盘恢复主窗口按系统版本选择激活选择器,修复 macOS 13 及更早系统崩溃
`NSApplication::activate()` 是 macOS 14 才引入的选择器(Apple《AppKit Release Notes
for macOS 14》:`activate(ignoringOtherApps:)` 在 14 废弃,同时新增 `activate`),而
`Info.plist` 声明的最低支持版本是 macOS 12.0。旧系统上该选择器不存在,消息转发失败
(`doesNotRecognizeSelector:`)→ `std::terminate` → SIGABRT。

- 崩溃报告:x86_64 / macOS 12.7.6(MacBookPro11,4)/ navop 0.18.3;未捕获 ObjC 异常
  `EXC_CRASH (SIGABRT)`,帧序 `__exceptionPreprocess` → `objc_exception_throw` →
  `___forwarding___` → `_CF_forwarding_prep_0`
- 必现路径:SSH 终端窗口最小化到托盘后恢复主窗口
- 修复:`activate_app` 用 `respondsToSelector(sel!(activate))` 运行时按系统版本分派,
  命中走 `activate()`,否则回退 `activateIgnoringOtherApps:`;回退收进 `activate_legacy`
  并 `#[allow(deprecated)]`,两条路径各自都在原生支持的系统上
- 新增源码级守卫测试:禁止调用方绕过 helper 直接调新选择器,也禁止删掉旧系统回退
- 影响面仅 `main/src/window_visibility.rs`;gpui 的 `activate_window()` 只做
  `makeKeyAndOrderFront:`,无同类越线
2026-09-19 22:31:33 +08:00
xiaofei 06e154568b Merge pull request #237 from feigeCode/ci/release-only-fast-path
ci: 发布 PR 只跑发布元数据校验,跳过全平台测试
2026-09-19 15:42:37 +08:00