⚡ Sangsakala Proxy 🏠 Home
Skip to content

chore: dev → main(扩展机制迁到 nsh,页面全量重写) - #509

Closed
feigeCode wants to merge 32 commits into
mainfrom
dev
Closed

feigeCode wants to merge 32 commits into
mainfrom
dev

Conversation

@feigeCode

Copy link
Copy Markdown
Owner

变更

扩展(插件)的 UI 机制整体从 gpui-shell 迁到 nsh,扩展页面从 gpui 构件改成 TSX 页面,26 个页面全量重写。规模:31 个提交、106 个文件、+6250 / −17284 行(净删 1.1 万行)。

分四块:

1. nsh 宿主(M1–M8):新增 crates/nsh_host(宿主骨架、页面可见 ABI、能力平面 resource/job/event/blob/runtime、两段装载与租约、主线程桥与能力上限),删掉 shell_plugin_host 与 gpui-shell 的 UI 平面(约 17K 行),feature 由 shell-plugin-host 改名为 nsh-host。页面产物走 app.json + capabilities.json,宿主按 manifest 权限 ∩ 页面 nsh.json 授权,越权拒绝。

2. 收敛成一个 tab + 最小 API:删掉原生工作台(contributes.resourceWorkbenches、navop.workbench.* 通道、连接 tab 与 CustomPageHost)。扩展只贡献单页视图,数据统一走 navop.resource.invoke(inline / blob 分片 / 事件流三种返回形态)。

3. 原生组件:新增 nativeComponent,扩展页可以内嵌宿主原生组件(Docker 容器终端 = 宿主终端控件),删掉旧的终端宿主契约与 crates/resource_view。

4. 扩展仓:子模块指向 navop-extensions 9e5169a06(#16 已合并),页面按新模型重写并共用一套设计系统。

顺带修掉几个构建问题:依赖图收敛为单份 wgpu 30、gpu-allocator 的 windows 边钉到 wgpu-hal 用的 0.62、32 位 Windows 构建形态编不过、发布契约测试跟上 feature 改名。

验证

  • CI 全绿:run 41608538379(18/18,含 Build shape ×10 与 linux/macos/windows 三平台 Test)。
  • Release:v0.23.0-beta.5 已从 dev tip 构建(run 41633913946,14 个目标全量构建中)。
  • 本地两种 feature 形态 cargo check 均 0 错:默认形态、--no-default-features --features wasm-components,windows-native-rdp。

注意

  • Cargo.lock 里 gpu-allocator → windows 是 lock 级钉法(cargo update 可能回退)。长期方案是让 nsh 的 GPUI 后端把 shader 特性做成可选,已记进迁移方案待办。
  • 旧插件包(gpui-shell 时代,含市场里现存 Docker 0.6.3 / MQTT 0.2.2)不再装载,这版 beta 的发布说明里已注明。

feigeCode and others added 30 commits November 2, 2026 23:41
gallery import 统计修正此前「nsh 组件深度不足」的误判:nsh/base 71+ 组件
(DataTable 百万行虚拟化/Editor+LSP/DiffView/DockArea/plot 全家)覆盖
gpui-component 深水区,nativeComponent 桥降级为兜底而非主路径;31 页
重写风险与 M6 验收措辞同步修正。
按 docs/extension-resource-plugins/nsh-migration-plan.md 的目标形态,验证
"navop 的 GPUI 窗口 + nsh 引擎页面"能在同一进程共存:扩展 UI 平面从
gpui-shell 换成 nsh,而 gpui-fast 仍是全进程唯一的那一份(nsh 的 GPUI
后端就是 gpui-fast 的 AppView,不是第二套渲染栈)。

- 依赖:nsh-platform-gpui(fork 分支 navop/gpui-0.1.12,已把该分支对齐
  gpui-fast 0.1.12)+ nsh-core(只用来解析能力声明)。default-features
  = false,只开 js,不引 webview/video。
- `nsh_spike` feature(默认关):把 resources/nsh-spike 的页面挂成主面板
  的一个 tab,主题/明暗/字体从宿主 gpui-component Theme 桥到 nsh 的
  shadcn token;`NAVOP_NSH_SPIKE=1` 时窗口出现后自动打开该 tab,方便冒烟。
- 页面用作者侧 `nsh build` 预构建(dist/bundle.js 随 crate 入库),宿主
  只装载不编译 —— 这就是迁移后扩展的分发形态。
- 与 gpui-shell 宿主互斥占用 ShellViewOpener:两个宿主特征同时开时默认
  让位给 gpui-shell,仅 no-shell-plugins 构建或 NAVOP_NSH_SPIKE=1 时接管。
- 新增台子测试:页面挂载后经 host.call 回报宿主(证明字节码装载、TSX
  执行、能力白名单放行、跨线程 host 桥往返都通)。

验证:cargo check(默认 / shell-plugins / nsh-spike 三种 feature 组合)、
cargo test -p universal-plugins --features nsh-spike(含新台子)、
cargo test -p universal-plugins --features shell-plugins(72 passed)、
clippy 干净。

已知成本:nsh-platform-gpui 无条件打开 nsh-engine 的 shader(wgpu 29),
与 gpui-fast 的 wgpu 30 并存 —— 依赖图各一份,编译与体积都要付双份代价,
M3 前应向 nsh 上游提议把 GPUI 后端的 shader 做成可选。
M2 的 spike 页面写死在 crate 里;M3 把它换成真正的装载路径:按 manifest 登记的
shell view(`contributes.shellViews`)找入口、读 nsh.json 的能力声明、装成
`AppView` 挂进 tab。这是"扩展页面由扩展自己提供"的第一版,后续 M4 接 host ABI、
M5 接 catalog/信任治理。

- `nsh_host` 模块取代 `nsh_spike`(feature 更名 `nsh-host`,环境变量
  `NAVOP_NSH_HOST`):`NshHost` 是 GPUI Global,实现 `ShellViewOpener`,
  singleton view 复用 tab,非 singleton 加序号(与 gpui-shell opener 同规则)。
- 两种装载形态:入口旁边有同名 `.nshpkg` 就走签名包(验签 → 解包资产 →
  运行 → 首帧健康后 `accept_package` 记住 build,防回滚);否则按开发形态
  读源码,`Bundle::source` + `nsh.json` 的 capabilities 白名单(harden 关闭
  以便调试,能力照样强制)。
- 装载失败不再静默:tab 照样开出来并在页面上写明原因。
- 信任库落 `data_dir/extensions/nsh/trust`(`TrustStore::open`,跨启动保留
  撤销表与已接受 build)。

验收台子:`nsh_host::tests::a_registered_view_loads_and_reaches_the_host` ——
构造一个 contribution 指向 `resources/nsh-host-test`(nsh.json + view.js),
装载后页面 `host.call("spike.mounted")` 回报宿主,断言数据到达;同时断言
扩展/view 身份与无装载错误。

验证:cargo check(默认 / shell-plugins / nsh-host)、cargo test
-p universal-plugins --features nsh-host(26 passed,含新台子)、
--features shell-plugins(72 passed,未回归)、clippy 干净、nightly fmt、
cargo check -p navop_app --features nsh-host。
页面在 nsh 里只有 `host.call(name, data) -> Promise` 一条通道,没有 gpui-shell
那种模块注册表。这里把同一批能力摊平成带命名空间的调用名,先把不需要
provider 的那几组接上:`navop.context.current`、`navop.log.*`、
`navop.i18n.*`、`navop.theme.snapshot` / `navop.theme.color`。

约定照抄 gpui-shell 版,页面侧的协议不用改:

- 能力仍由 manifest 声明:`nsh.json` 的 `capabilities.host` 是引擎侧的
  白名单(支持 `navop.theme.*` 这种前缀),`contributes.shellViews[].modules`
  是宿主侧的第二次收口——两边都声明才挂 handler。
- 错误仍是结构化信封:`HostCall::reject` 只能带一句 message,稳定 code
  编码成 `message\n__NAVOP_ERROR__<base64url-json>`,与 `decodeHostError`
  认的格式一致。
- handler 跑在引擎线程:这一层不放 `Rc`/`RefCell`,主题这类主线程数据由
  宿主算好推进 `HostAbi` 的缓存(`Mutex`),handler 只读;主题变化时另推
  `navop.theme.changed` 事件。

顺带:`NshTab::cancel` 在扩展停用时置取消位,在途调用按
`REQUEST_CANCELLED` 退出,而不是等 provider 调完再丢结果。

自检页现在启动时会调用这几组能力,并把应答一起回报给宿主;台子断言
`viewId == "self-check"`、语言非空、主题版本号 > 0 —— 这三条只有"真的调到
宿主"才成立。

验证:cargo check(默认 / shell-plugins / nsh-host)、cargo test
--features nsh-host(28 passed)、--features shell-plugins(72 passed,未回归)、
clippy 干净、nightly fmt、cargo check -p navop_app --features nsh-host。
页面现在不仅能问"我是谁、什么主题",还能真的用上 provider 侧的资源:
`navop.resource / job / event / blob / runtime` 六组调用都接到能力平面,
句柄记账、generation 过期检测、随 resource 回收派生子句柄的规则与
gpui-shell 版一致。

nsh 的 host.call 只有一条 JSON 通道,所以这层的形状也跟着变:

- handler 先把应答句柄 `into_reply()` 取出来(`HostReply` 是 Send),
  再搬到 tokio 线程等 provider;引擎线程一秒都不占。
- 取消用「会话根令牌 + 每次调用派生子令牌」,页面关闭时置取消位,
  在途调用按 REQUEST_CANCELLED 回给页面;open/start 这类有副作用的
  调用在取消后补一次 close,避免 provider 侧漏资源。
- 报错统一走 `host_error`:稳定 code + envelope 与 gpui-shell 平面
  同一套词汇表,页面侧的 decodeHostError 不用分叉。

顺手把错误模块提到 crate 级(`host_error`),两个 UI 平面共用一份
code/映射/envelope,不再各写一套;gpui-shell 那侧只剩
`NavopError -> HostError` 的薄适配。

验证:自检页加了一条能力平面探针(用不存在的 backend 开资源,断言
拿到 BACKEND_NOT_FOUND —— 说明调用真的走完了白名单、分发、
tokio 跳线程、envelope 回填整条链路,而不是被静默丢掉);
`cargo test -p universal-plugins --features nsh-host` 32 passed,
`--features shell-plugins` 67 passed(错误模块的 5 条用例随之上移,
总数不变)。
页面不再"先把界面挂上、后端慢就慢":现在先向激活管理器租下页面声明的
全部后端(activation lease),租到手才建 `AppView`。顺序是硬要求——页面
`mount` 第一行就可能 `host.call` 要数据,provider 没起来时它只能拿到错误,
而这种错误会被读成"扩展坏了"。

由此带出两处结构调整:

- tab 变成两段装载:`open` 先以激活中的样子开出来,激活结果回到主线程后
  由下一次 `render` 落地建视图(建 `AppView` 要 `&mut Window`,而激活是
  异步回来的,只能在帧里做)。激活失败就停在错误页,不挂半个页面。
- 租约跟着 tab 走:`Drop` 里还回去,激活途中关 tab 也把已租到的还掉。
  还租约要 await,所以 `Drop` 里投到 tokio 上,不阻塞 UI 线程。

顺带把 `host.call` 通道从"等 `AppView` 起来再取"改成在 `open` 就建好、
显式交给 `AppOptions.host`。这不只是接线方便:台子要在页面跑起来之前挂
handler,否则页面 `mount` 里的第一次调用会撞空——这也是自检页那条断言
现在能提前挂 handler 的原因。

验证:`cargo test -p universal-plugins --features nsh-host` 32 passed,
新增断言"首帧后页面已挂上(失败时打印原因)",把激活→装载这条链路的
终点钉住;`--features shell-plugins` 67 passed 未回归;clippy 与本 crate
零告警;nightly fmt。
清单里新增 `engines.nsh`:声明它的 shellView 由 nsh 宿主装载,
`engines.gpui_shell` 留作存量扩展的旧门。两个门**各自独立校验**,
至少要有一个 —— 只写一个也能表达"这页跑在哪个 UI 平面上"。

为什么不是直接换掉旧门:迁移期两个宿主在同一个二进制里,扩展作者
不该被迫同时声明两套。等 M7 删掉 gpui-shell,旧门连同校验一起消失。

`RegisteredShellViewContribution` 带上 `required_nsh_version`,
宿主可以据此分流(M7 后就是唯一的判断依据)。

验证:`cargo test -p extension-runtime` 234 passed;新增两条用例
——声明 nsh 门能装载、两个门都不声明时报 `/engines/nsh`。
(唯一失败的 `db_tree_action_runs_registered_wasm_component` 是既有的
wasm fixture 未构建问题,基线同样失败,与本次无关。)
`cargo test -p universal-plugins --features nsh-host` 32 passed。
Docker / MQTT 这些真实扩展的页面不是独立 tab:它们声明
`modules: ["context","workbench"]`,从资源工作台里嵌进来,主资源是外层
连接会话开好的那一个。所以只做 tab 路径的话,M8 重写的页面根本跑不起来。

补齐三块:

- 借来的资源(`owned: false`):页面能 invoke 它,但关闭时**不**调
  provider 的 close —— 那是外层连接的东西,关掉就把别人的连接掐了。
- `navop.workbench.current / dispatch`:`current()` 纯读页面上下文,
  `dispatch` 按 operation 的 mode 走 invoke 或 job 分派。`BindingContext`
  原样取自 `page_context`(route/selection/paging/connection),与原生
  工作台同源——两边解析 `source: connection` 必须得到同一个值。
- `CustomPageHost` 实现:嵌入页仍走同一套 session/ABI,只是多一个借来的
  句柄和 workbench 通道。黑名单与 gpui-shell 版逐条对齐:原始
  resource/job/event/blob/runtime/dev 模块一律挡住,i18n/theme 放行
  (它们读的是宿主自己的语言和主题,不带资源访问)。

验证:`cargo test -p universal-plugins --features nsh-host` 34 passed,
新增两条用例覆盖嵌入页的模块门(缺 context / 缺 workbench / 混入原始
资源模块);`--features shell-plugins` 67 passed 未回归;clippy 与本
crate 零告警;nightly fmt;`cargo check -p navop_app --features nsh-host`。
发布出去的页面目录里没有 `nsh.json`(那是构建输入),只有 `nsh build` 的
产物:`bundle.js` + `app.json`(app id)+ `capabilities.json`(能力声明)。
所以装载顺序改成先读产物、再回落到源目录形态,能力仍按声明收窄。

试点页(docker 引擎概览)落在扩展仓 `ui/pages/overview/`,产物入库,
`entry` 指向产物。加一条用例用**真实产物**验证这条路径:app id 来自
`app.json`、`capabilities.json` 里的 `navop.workbench.*` 成为声明上限。
扩展仓没检出产物时用例跳过 —— 和旧渲染底座测试同样的处理。
两件事,都是"删 gpui-shell 之前必须先补上的缝":

1. **主线程桥**。nsh 页面跑在引擎线程上,而 `navop.dev` 这些操作要碰 GPUI
   全局(dev 工程注册表、窗口、文件选择器)。加了 `dispatch::MainThread`:
   引擎线程把 `FnOnce(&mut App)` 投到主线程队列并等结果,闭包要求 `Send`,
   所以只能带字符串/JSON 过去 —— 主线程上再取 Global。

2. **能力上限**。之前页面不写 `capabilities` 就等于拿到 `Capabilities::all()`:
   nsh 的 `Policy::access()` 在 `declared` 缺失时退回全集,而宿主没设
   `granted`。现在按 manifest 的 `permissions`(fs/net/spawn)与
   `shellViews[].modules`(宿主函数命名空间)组装 granted 上限,页面的声明
   只能落在里面。无法翻译的权限(`*` 通配、展开不了的环境变量路径)记为
   skipped 并告警,不静默放权。

`navop.dev` 的操作集同时改写成 JSON 形态(`DevOps`,每个函数 `&mut App`
上执行),main 层注入;`install_dev_host_ops` 只负责装 Global 与调用注入。

验证:`cargo test -p universal-plugins --features nsh-host` 38 passed(新增
grant 的三条用例:模块→命名空间、spawn 取 basename、`*` 通配被丢弃);
`--features shell-plugins` 67 passed;`cargo check -p navop_app --features
nsh-host` 通过;nightly fmt。
扩展页面的 UI 平面只剩 nsh。`gpui-shell` 从依赖图里彻底消失
(`cargo tree -i gpui-shell` 报 "did not match any packages",
`Cargo.lock` 里已无该包),`shell_plugin_host` / `shell_plugin_tab` /
`shell_page_host` 三个模块文件一并删除,`shell-plugins` feature 换成
`nsh-host`。

删除前补齐了三处 gpui-shell 在做、nsh 侧还没有的事,否则删掉是减功能
而不是换实现:

- **连接型页面**:`NshHost::open_connection` 按保存的连接配置打开
  provider 资源,句柄归页面所有(`owned: true`),tab id 固定为
  `extension-connection:{id}` —— 同一条连接只开一份资源。
- **扩展退役**:`close_extension` 恢复 gpui-shell 的完整流程
  (`begin_extension_retire` → 关掉脚本页与 headless 连接 tab →
  `deactivate_extension` → `finish_extension_retire`),装不干净时也会
  把扩展从"退役中"状态放出来,免得它永远打不开。
- **provider 重启**:移植监视桥。事件带完整语义(generation、session
  是否关闭),页面按 `decide_runtime_change` 分三种处理:瞬时抖动忽略、
  换进程就重装页面、被移除就停在错误页并提示重开。连接型页面无法重放
  启动参数(连接配置已交给 provider),所以换进程时提示重新打开连接。

另外:界面语言变化改走 `set_locale`(页面重渲染取词条),不再重跑脚本;
`navop_proof` 之外没有任何调用方还知道 gpui-shell 的存在。

验证:`cargo test -p universal-plugins --features nsh-host` 38 passed;
`cargo check -p navop_app`(默认已含 nsh-host)通过;`cargo check -p navop`
通过;clippy 对本仓 crate 零告警;nightly fmt。
- 开发扩展的测试清单 fixture 改用 `engines.nsh`(旧门仍能装载,但新
  建的开发扩展没有理由再声明一个已经不存在的宿主)。
- 提示文案与注释里的 "shell-plugins build" 统一改成 nsh 构建;这些字
  串会出现在用户可见的错误提示里,不该再提一个删掉的平面。
M8 要一页页重写,谁也不该记着往测试清单里补一行。这条用例直接扫
`extensions/*/*/ui/pages/*/dist/bundle.js`:每多一页产物就自动多覆盖
一页,产物缺失(子模块没初始化)时跳过。

断言三件事:产物里的 `app.json` 能给出 app_id、程序是 JS 形态、
`capabilities.json` 里声明了至少一个 host 能力 —— 最后一条最容易漏,
而漏了页面在运行时调什么都会被拒。
宿主已经没有 gpui-shell 那条渲染路径了,再接受这个门只会让扩展以为
自己能跑:清单声明 `engines.gpui_shell` 就能注册成功,打开页面却装载
失败。现在 shellView 只认 `engines.nsh`,缺了就在清单校验阶段报
`/engines/nsh`。

连带删掉 `RegisteredShellViewContribution::required_gpui_shell_version`
与 `GPUI_SHELL_VERSION` 常量;docs 里的 reference 扩展(elasticsearch)
改成声明 `engines.nsh: "0.3.0"`。

另外修 `navop.dev.openView` 的入参优先级:它认扩展 id,其余 dev 操作认
项目根路径,之前一律先读 `root`,页面按 `{extensionId, viewId}` 传会拿到
`view not found: <root>/<viewId>`。

验证:`cargo test -p extension-runtime` 234 passed(唯一失败是既有的
wasm fixture 未构建,基线同样失败);`cargo test -p universal-plugins
--features nsh-host` 39 passed;`cargo check -p navop_app` 通过。
Elasticsearch mapping 页要「复制字段名」,但宿主的能力上限里没有
clipboard,`Clipboard.writeText` 只会 warn 后静默失败 —— 看起来能用,
实际什么都没复制。

上限这里放行 clipboard,页面仍要自己在 nsh.json 里声明 `"clipboard":
true` 才生效(nsh 拿声明与上限取交集)。写剪贴板只影响用户下一次粘贴,
且必须由页面在一次按键/点击里发起,风险低于 fs/net,所以不默认拒绝。
`nsh-page-guide.zh-CN.md` 是扩展作者写页面的唯一入口:页面工程结构、
`navop.*` 命名空间表、tab 与工作台嵌入两种形态、共享设计系统、10 条
nsh/TSX 坑、测试写法、交付清单。

同时把旧文档定性:`gpui-shell-extension-design.md` 标为废弃(只留追溯),
`development-guide.zh-CN.md` 只在顶部注明"provider/runtime/清单仍然有效,
UI 部分已过期"——它的权限与 runtime 章节现在还是准的,删掉不如标注。

迁移方案补 §14.5,告诉接手的人:加能力改哪、加页面形态改哪、别再加回
gpui-shell。
nsh fork 的 navop/gpui-0.1.12 已把 wgpu 从 29 升到 30(e2a57dd3),这里跟着
`cargo update -p nsh-engine/nsh-core/nsh-platform-gpui`:锁文件里 wgpu 只剩
30.0.1,wgpu-core / wgpu-hal / wgpu-types / wgpu-core-deps-apple|emscripten|
windows-linux-android / wgpu-naga-bridge 的 29.0.4 一整套全部移除。

验证:`cargo test -p universal-plugins --features nsh-host` 39 passed(含
`every_built_page_in_the_extension_repo_loads`,顺带覆盖了旧页面删除后的扩展仓)。
原生资源工作台整套删掉:声明式表格 UI、工作台清单、`navop.workbench.*`
通道、headless 连接 tab 全部不再存在。一条扩展连接现在只有一个形态——
扩展自己的页面。页面想看什么、怎么排版,都由页面决定。

为什么删:工作台是"宿主替扩展画 UI"的第二套机制,它的存在让每条连接
都有两条路(有工作台走原生、没有才开页面),页面因此被迫依赖
`workbench.dispatch` 才能拿到数据,扩展作者也无法真的自由发挥。

保留下来的能力面:context / resource / job / event / blob / runtime / log /
dev;theme 与 i18n 仍由宿主推送(页面 token 依赖)。`resource.invoke` 现在
由宿主自己解码 provider 的 `ResultRef`(inline 直接给值、blob 走分片读并按
8 MiB 上限拼回 JSON、事件流给句柄),页面拿到就是普通数据。

清单侧:`contributes.resourceWorkbenches` 字段删除(旧清单里的这个键会被
serde 忽略),连接必须用 `shellViewId` 指到自己的页面。

验证:`cargo test -p extension-runtime` 217 passed(唯一失败
`db_tree_action_runs_registered_wasm_component` 是既有 wasm fixture 未构建,
基线同样失败);`cargo test -p universal-plugins --features nsh-host` 28
passed;`cargo test -p navop_app --lib` 597 passed;clippy 对本 crate 无新增告警。
`nsh-page-guide` 补第 10 条坑:组件体只跑一次,把 props 算成常量就是挂载值
(按钮永久点不动 / 进度条不动 / 空态不消失),只有 JSX 属性表达式与子节点
绑定按帧求值。这是这次改页面时踩到的最贵的坑,写进指南比写在提交里有用。

`bundle.rs` 的试点页断言还要求页面声明 `navop.workbench.*`——工作台已经删了,
改成要求 `navop.resource.*` 且不得残留 workbench 命名空间。
页面是 TSX,终端这种「必须是 Rust 实体」的东西界面里没处放。这次给
nsh 宿主补上原生组件,并把为它存在的旧机制整条删掉。

注入方向是「注册表在扩展宿主、实现在应用层」:`nsh_host/native.rs` 只有
`register_native_component` 与 `NativeViewContext{extension_id, view_id,
allow_process}`;`NshTab` 建视图时用 `components_for(&contribution)` 拼出
**本页面**的 `Components` 交给 `AppOptions.components`(不是进程级
`set_ui_sets` —— 那样一页注册的名字会漏给所有页面)。实现放在
`shell_domain::native_terminal`,扩展宿主因此不用依赖 `terminal_view`。

权限:`allow_process` 只看扩展清单有没有 `shell:exec`。终端起的是宿主本地
进程、命令与参数必须由页面显式给出;没有权限时组件渲染一行错误,不静默
失败。「重新连接」= 改 `env` 让宿主重开一个 pty;只改 `title` 不重开。
组件 drop 时主线程拿不到 `App`,所以走一条回收队列,主线程补
`shutdown_embedded`(否则 pty 与连接占用要留到进程退出)。

删掉的旧机制(都是这次才真正无人调用的):
- `crates/resource_view` 整个 crate(3228 行原生工作台 + `TerminalHost`);
- `extension_plugin_adapter::workbench_dispatch`(`dispatch_invoke*/job*`、
  `BindingContext`);
- `extension-runtime::registration` 的 workbench 校验簇与它唯一的测试模块;
- `shell_domain::workbench_terminal` 的 `NativeTerminalHost`(保留 AI 工作台
  的终端面板提供者)。

顺带:`navop.resource.invoke` 现在会把 provider 在 invoke 响应里交出的事件流
登记进 EventActivationManager(原来只有 workbench dispatch 做这件事,删掉
dispatch 后不补的话,页面拿到的流第一次读就会被拒)。

验证:`cargo test -p extension-runtime` 207 passed(唯一失败
`db_tree_action_runs_registered_wasm_component` 是既有 wasm fixture 未构建,
基线同失败);`-p universal-plugins --features nsh-host` 29 passed;
`-p navop_app --lib` 597 passed;`cargo check -p navop` 通过;clippy 对新代码
零告警。docker 新增 `container-terminal` 页(`nsh check/build/test` 5 用例全过)。
上一提交把 `contributes.resourceWorkbenches` 字段删了,但类型定义还躺着:
`ResourceWorkbench*`(675 行的 `contributes/resource_workbench.rs`、
`types/resource_workbench.rs`、`types/workbench_operation_catalog.rs`)、
只测这些校验器的 `tree_children_tests` 模块、以及 `InvalidResourceWorkbench`
错误变体。清单里已经没有字段能往里装东西,这些类型没有任何读取者。

一起清掉之后 `catalog.rs` 的工具箱过滤也不再有「工作台页体」这个例外分支
——那条例外的前提(`pages[*].renderer.viewId`)已经不存在了。

同时把 docker 的容器终端页(原生组件 `navop.terminal`)接进清单:
`shellViews` 加 `container-terminal`,权限沿用扩展级的 `shell:exec`。

验证:`cargo test -p extension-runtime` 204 passed、`-p extension-plugin-adapter`
82 passed、`-p universal-plugins --features nsh-host` 29 passed、
`-p navop_app --lib` 597 passed、`cargo check -p navop_app --all-targets` 0 error。
(`db_tree_action_runs_registered_wasm_component` 仍是既有 wasm fixture 未构建的
失败,基线同样失败。)
CI 的 `script/test-release-packaging.mjs` 把「默认集里必须有 shell-plugins」
写成了字面量断言,改名成 nsh-host 之后第一个 job 十秒就红了。这次一起把
契约改成新形态,并顺带把 nsh 后端自己的 webview 也纳入同一条规则:

- 默认集断言改成 `["wasm-components","windows-native-rdp","nsh-host","database","shell"]`;
- `universal-plugins` 断言从 `gpui-shell` 换成 `nsh-platform-gpui/nsh-core/nsh-engine`,
  并新增一条:workspace 里 `nsh-platform-gpui` 必须是
  `default-features = false, features = ["js"]` —— 它的 `webview` 特性会拖进
  wry/WebKitGTK,和原来的 embedded-webview 一样不能进默认集。

(`.github/workflows/` 里两处注释还写着 shell-plugins:本机 token 没有
workflow scope,推不了 workflow 文件的改动,留到有权限时顺手改。)

验证:`node --test script/test-release-packaging.mjs` 45 passed;
`cargo test -p navop_app --test i18n_locale_keys_contract` 1 passed。
CI 的 Build shape 三个产品一起红,暴露的是「nsh-host 开、shell 关」这个
组合(navop-data / 部分 navop-shell 配置)从没被本地验证过:

- `dev_host_ops` 依赖 `dev_extension_registry`,后者是 `shell` feature 的
  模块。前者只挂了 `nsh-host`,于是 `crate::dev_extension_registry` 找不到。
  现在两者用同一组 cfg:`all(nsh-host, shell)`。
- `home_strategy` 里 `TabItem` 只在终端型连接那条路径用,`shell` 关掉时是
  未使用导入;改成 `all(nsh-host, shell)` 下才导入。
- `dev_extension_registry` 的 `Ordering`、`dev_host_ops` 的 `Context as _`
  与 `DevOps` 都是重构后残留的未使用导入,删掉。
- `extension-plugin-adapter` 的 `ResourceSessionHandle::client()` 唯一消费者
  是已删除的 workbench dispatch,删掉(默认集不带 nsh-host 时它是 dead code,
  CI 的 `-D warnings` 直接判死)。

验证:`cargo check -p navop / navop-data / navop-shell --target aarch64-apple-darwin`
三个产品零告警零错误;`cargo test -p universal-plugins --features nsh-host` 29 passed、
`-p navop_app --lib` 597 passed。
Windows 的 Build shape 挂在 `wgpu-hal-30.0.1/src/dx12/suballocation.rs`:
`ID3D12Device` 类型对不上。原因是 `gpu-allocator 0.28` 把 windows 依赖写成
**版本区间** `>=0.53, <=0.62`,解析器给它挑了 0.58(gpui-base /
gpui-component 正好用 0.58),而 wgpu-hal 30 自己用的是 0.61.3 —— 两边
`ID3D12Device` 不是同一个类型。

修法:把 lock 里 gpu-allocator 指向的 windows 边改成 0.61.3(区间允许),
这样它和 wgpu-hal 对同一份 windows 编译。`cargo check --locked -p navop`
与三个产品的 check 都通过,说明解析结果自洽。

这条是 lock 级别的钉法:下次 `cargo update` 可能把 gpu-allocator 又拉回
0.58(因为区间自由)。更稳的长期方案是让 nsh 的 GPUI 后端把 `shader`
特性做成可选(navop 不需要 wgpu 的 `<Shaders>`),那一并解决「两份 wgpu /
一份 wgpu 的编译成本」——已记进迁移方案的待办。
上一版钉成 0.61.3 还是对不上:wgpu-hal 30.0.1 自己依赖 windows 0.62
(不是 0.61)。gpu-allocator 的区间是 ">=0.53, <=0.62",钉到 0.62.2
才是同一个类型。cargo check --locked -p navop 通过。
i686 的 Build shape 用 `--no-default-features --features
wasm-components,windows-native-rdp`,没有 `nsh-host`,但 `shell` 在场:
`home_strategy.rs` 里 `TabItem` 的导入被挂在 `all(shell, nsh-host)` 上,
于是终端型连接那条路径引用了一个未导入的类型(E0433)。

`TabItem` 实际上只被 `#[cfg(feature = "shell")]` 的两个分支用到(终端型
连接、nsh 页面 tab),所以门就开 `shell` 一个。两种形态都验过:
`cargo check -p navop_app --no-default-features --features
"wasm-components,windows-native-rdp"` 与默认 `cargo check -p navop_app`
都零错误。
refactor(extension)!: 扩展 UI 平面迁移到 nsh,插件机制收敛为「一个 tab + 最小 API」
插件引擎换代(gpui-shell → nsh)之后的第一版 beta:26 个插件页面重写、
原生组件内嵌、剪贴板写权限;另外带 Shell/Data 独立图标、设置页顺序、
迁移包层级修复。扩展包这版不发,市场里仍是旧包,说明里已注明。
@feigeCode

Copy link
Copy Markdown
Owner Author

先不发版了,等改造完再提;此 PR 先关掉,需要时重开。

@feigeCode feigeCode closed this Nov 3, 2026
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