Files
nyaterm/docs/plugin-development.md
T

169 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 插件开发与排错
插件安装、授权、运行状态和日志都在 **插件面板** 中操作。
## 创建项目
在 NyaTerm 仓库根目录执行:
```sh
# 纯界面插件:查看选中终端的会话信息
pnpm plugin:create example.session temp/plugins/my-session --template ui
pnpm plugin:pack temp/plugins/my-session temp/plugins/my-session.nyap
# Rust 插件:界面调用后台,也提供原生命令
pnpm plugin:create example.hello temp/plugins/my-hello --template rust
node temp/plugins/my-hello/build.mjs
pnpm plugin:pack temp/plugins/my-hello temp/plugins/my-hello.nyap
```
创建器拒绝覆盖已有目录。项目包含 manifest、可用入口、SDK 和 README;
Rust 模板还包含编译脚本,编译后自动填入当前平台的可执行文件路径。
需要 Node 和 Rust 1.94 或更新版本。Rust SDK 使用本地路径依赖,移动项目时
应一起提供 SDK 或调整 Cargo.toml 中的路径。
将生成的 `.nyap` 安装到插件面板,检查权限并启用。UI 模板需先选中已连接终端;
Rust 模板可以打开界面点击运行,也可以直接执行插件卡片上的原生命令。
## 页面字体
宿主会将应用的界面字体栈通过 `NyaTerm.context.theme` 中的 `--font-sans`、
`--font-display` 同步到插件页面;修改设置时,已打开的页面也会收到更新,无需重载。
`--font-mono` 提供宿主的默认等宽字体栈。SDK 默认使用界面字体,表单控件继承字体;
插件自定义 CSS 应使用 `font-family: var(--font-sans, system-ui, sans-serif)`,
代码区域可使用 `var(--font-mono, monospace)`,避免写死 `system-ui`。
iframe 可以使用系统安装的字体,但不会继承主界面的 `@font-face`。
若使用未安装到系统的内置字体或其他 Web 字体,需要在插件包内提供字体文件并声明
`@font-face`。已有插件若写死字体,需要修改 CSS 并重新打包安装。
## 编写 Rust 后台
入口实现 `Plugin`,由 `Server::from_env()` 启动服务。SDK 负责握手、传输、
并发、作用域、超时和取消,无需手写 JSON-RPC:
```rust
#[async_trait]
impl Plugin for MyPlugin {
async fn call(&self, context: Context, method: &str, input: Value) -> Result<Value> {
match method {
"ui/session" => {
let session = context.host().session().await?;
context.log("info", "Session inspected").await?;
Ok(session)
}
_ => Err(RpcError::method_not_found(method)),
}
}
}
```
以上会话读取需在 manifest 中声明并获授 `session.read`;原生执行需 `native`。
UI 调用 `NyaTerm.backend("ui/session")`,contributions.commands 中的原生命令
则使用 manifest 声明的方法。不要向 stdout 打印日志,它专用于协议;使用
`Context::log` 或 stderr。不要记录口令、令牌、终端输入输出等敏感内容。
默认采用 JSONL。将 `backend.transport` 改为 `stdio-framed` 后,SDK 根据宿主
环境自动切换协议,支持二进制帧;界面流式订阅尚未提供。完整 API 和取消约定
见 [Rust SDK](../plugins/sdk/rust/nyaterm-plugin-sdk/README.md)。
## 固定远端 Probe 和共享 Monitor
Manifest v1 可选声明 `contributions.probes` 和 `contributions.monitors`,旧包不必增加字段。
以 GPU 为例:
```json
{
"contributions": {
"panels": [{ "id": "overview", "title": "GPU", "entry": "ui/index.html" }],
"probes": [
{
"id": "gpu-overview",
"title": "GPU probe",
"entry": "assets/probes/gpu.sh",
"timeoutMs": 15000
}
],
"monitors": [
{
"id": "gpu",
"title": "GPU",
"schema": "gpu.v1",
"method": "monitor/collect",
"panel": "overview"
}
]
},
"permissions": ["native", "remote.probe"]
}
```
每类最多 16 项,ID 不可与其他贡献重复。脚本必须位于包内 `assets/probes/*.sh`,为非空 UTF-8、无 NUL、最多 64 KiB;
超时范围 1–30 秒。Monitor 必须引用已声明面板,声明 `monitor/*` 方法、原生后台和 Probe;当前只支持 `gpu.v1`。
自动授权绑定审核版本;启用对话框展示脚本全文,更新/切换版本移除 `remote.probe` 授权。
宿主不保证第三方脚本只读,不允许两个有效插件同时提供同一种 Monitor schema。
后台每次 `monitor/collect` 调用 `context.host().remote_probe("gpu-overview").await?`,解析结果并返回 GPU JSON。
对应 RPC `host/remote/probe` **只接受** `{ "probeId": "gpu-overview" }`,返回 `{ stdout, stderr, exitStatus }`。
不能提供命令、参数、stdin 或其他会话 ID;宿主从当前包解析脚本,在所属 SSH 连接的新 exec 通道执行 `sh -s`。
stdout/stderr 合计限 1 MiB,超时或取消关闭该通道,不向交互终端输入命令、不关闭 SSH 连接。
执行前后检查版本、授权、窗口、会话及锁屏;网络设备和禁止远程探测的会话不可执行。
UI 只订阅结果,不直接启动后台采集循环:
```js
await NyaTerm.ready;
const monitor = await NyaTerm.monitoring.subscribe("gpu", (snapshot) => {
// revision, sessionId, overview, error, refreshing, paused
render(snapshot);
});
await monitor.refresh();
await monitor.unsubscribe();
```
宿主以插件/版本/窗口/会话/Monitor 为键共享任务,订阅返回初始快照,再推送递增 revision。
Bridge 仅向对应 iframe 发送所属订阅;未开放通用原生事件透传或 UI 调用 `monitor/*`。
宿主消费者使用 typed `pluginApi.subscribeMonitor/refreshMonitor/unsubscribeMonitor`;
`subscribePluginMonitor` 负责先监听再订阅、初始快照竞态、revision 去重及释放。
每个 iframe 最多 16 个订阅;宿主最多 256 订阅和 128 共享任务。
SDK Context 提供 `language`、主题变量和 `monitorIntervalSeconds`。使用 `onContextChange` 同步语言/主题,
仅间隔变化时重新订阅。主窗口订阅 25 分钟续期;iframe 作用域按现有机制重建。
闲置任务不持有请求租约;后台每次只处理一次采集,不保存已经结束请求的 Context。
`gpu.v1` 结构与 `RemoteGpuOverview` 相同,宿主拒绝未知字段并限制设备/进程数量和文本长度。
资产回填只能来自当前会话的 GPU 数据,没有任意连接修改接口。
完整示例与构建命令见 [GPU 插件](../plugins/examples/gpu-monitor/README.md)。
## 查看状态和日志
原生后台按需启动并复用进程。卡片显示未启动、启动中、运行中、已停止或运行异常;
禁用插件和仅界面插件有独立标识。RPC 方法返回错误时,只记录调用失败,后台仍可
继续运行;进程退出、握手或协议失败则显示运行异常。
点击 **查看日志** 可刷新、复制和清空。日志包括时间、版本、级别、来源和消息。
日志弹窗每秒刷新,卡片状态每 2.5 秒刷新;关闭插件管理面板后停止轮询。
每个插件最多保留 100 条、64 KiB 消息与来源/版本文本,单条消息最多 2 KiB。
stderr 每次启动最多采集约 64 KiB,超过后继续排空而不保留。
日志只在内存中保留,退出应用即清除,不进入备份或云同步。
宿主会遮蔽常见凭据和作用域令牌,但插件作者仍需自行避免写入敏感内容。
**停止后台** 保留启用状态和权限。普通调用可启动新进程,进程内计数等状态会重置;
监控任务会同时暂停,定时器不会自行重新启动后台,手动刷新或重新启用可恢复。
插件有进行中的请求时会提示忙碌;先结束或关闭该操作,再停止后台。
禁用、更新、切换版本、锁屏和退出应用也会停止后台。旧进程的延迟消息不会覆盖
新启动进程的状态。更新插件必须提升版本号,同版本不同内容不能重新安装。
## 本地验证
```sh
node --test scripts/create-plugin.node-test.mjs
cargo test --manifest-path plugins/sdk/rust/nyaterm-plugin-sdk/Cargo.toml
cargo test --manifest-path src-tauri/crates/nyaterm-plugin-runtime/Cargo.toml --features sdk-fixture
pnpm exec vitest run src/components/plugins/PluginDiagnostics.test.tsx --maxWorkers=4
```
`native-counter` 示例已使用 SDK,并升到 1.0.1:执行两次可看到计数递增和日志;
停止后台后再执行,计数从 1 开始。新版安装包为 `temp/plugins/native-counter.nyap`。
模板编译和打包已在 Windows 验证;其他平台需构建对应二进制并填写 executables。