mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-11-06 16:07:55 +00:00
Introduced a new job in the GitHub Actions workflow for the Plugin SDK, which includes steps for checking out the repository, setting up Rust with necessary components, installing Linux dependencies, checking formatting, building SDK guests, and verifying the packaged SDK. Updated the Rust gate job dependencies to include the new plugin-sdk job. Additionally, refactored the NyaTermApp to improve the handling of floating panel close actions based on the current panel state, ensuring better user experience. Enhanced plugin management by implementing shutdown procedures and revising invocation checks to include expected revisions, improving stability and error handling during plugin operations. Added comprehensive tests for the plugin service to validate the new functionality and ensure robust performance across various scenarios.
349 lines
18 KiB
Markdown
349 lines
18 KiB
Markdown
# NyaTerm plugins V1
|
|
|
|
Plugins provide command drafts and transform text explicitly supplied by the
|
|
user. They cannot execute commands or access sessions, terminal output, history,
|
|
clipboard, credentials, files, network or processes. NyaTerm implements this
|
|
system independently under Apache-2.0; Zed's GPL extension architecture was a
|
|
read-only architectural reference.
|
|
|
|
## Install and use
|
|
|
|
Open **Plugins** using the extension icon in the bottom-left activity bar,
|
|
between **Sync / Backup** and **Settings** by default. The side panel follows the
|
|
workspace's docked/floating mode and can be moved or hidden like other panels.
|
|
Click its activity-bar icon again to hide it; the plugin panel has no top-right
|
|
close button.
|
|
Choose a directory containing `plugin.toml`, or a ZIP with
|
|
that file at its root, then select **Install**. Installation copies and validates
|
|
a managed snapshot; later edits to the source do not affect it.
|
|
|
|
The manager shows metadata, API/host requirements, origin (managed/development),
|
|
status, contributed actions and actionable errors. **Installed (disabled)** means
|
|
no contributions are available. Declarative plugins become **Enabled**; a live
|
|
component becomes **Running**. Incompatible packages, loading failures and runtime
|
|
faults have distinct states. A runtime fault revokes the instance's actions;
|
|
reload it to retry. Failed replacement leaves the old plugin available.
|
|
|
|
* **Enable/Disable** persists across restarts. Disable cancels calls and removes
|
|
contributions immediately when the operation completes.
|
|
* **Update from source path above** validates a package with the same plugin ID,
|
|
prepares its replacement, then switches it. Local updates are explicit; there
|
|
is no automatic networking or marketplace.
|
|
* **Reload** recreates a managed instance. For a registered development directory,
|
|
it first takes another validated snapshot of the source.
|
|
* **Uninstall (keep data)** removes the managed package, preference and all
|
|
contributions, retaining `work/data/<plugin-id>`. V1 guests have no KV or file
|
|
access, so the SDK itself currently writes no persistent plugin data.
|
|
|
|
Select an action, fill its typed parameters, and choose **Generate / transform**.
|
|
**Cancel** applies only to action calls. Cancelling an executing guest retires its
|
|
instance; reload it before invoking it again. An abandoned queued call does not
|
|
execute. Installation, update, enable/disable, reload and uninstall run to
|
|
completion or roll back and cannot be cancelled from the panel. Hiding the panel
|
|
retains its state; closing a workspace cancels its action call while accepted
|
|
management continues in the shared process service.
|
|
Text actions receive only text entered/pasted into their input box. The user must
|
|
explicitly copy terminal selection and paste it there; plugins never subscribe to
|
|
selection, terminal output or the clipboard. Results appear as ordinary editable
|
|
text and can be copied. Text results can be reused as another text input.
|
|
|
|
For command results, **Fill send box** appends to an existing draft; **Replace
|
|
send-box draft** explicitly replaces it. The send box opens, but neither action
|
|
sends bytes, presses Enter, changes targets or chooses sessions. Stop any active
|
|
send and select text mode first. Review the final draft and use NyaTerm's usual
|
|
send controls yourself. Plugin contributions never modify saved QuickCommands.
|
|
|
|
For a local demonstration from a checkout:
|
|
|
|
```sh
|
|
rustup target add wasm32-unknown-unknown
|
|
python scripts/plugins/build.py --fixtures
|
|
cargo build --locked
|
|
cargo run -p nyaterm-app --bin nyaterm
|
|
```
|
|
|
|
The default build includes the MCP, RDP and VNC helpers beside the application.
|
|
Open the manager and install `target/plugin-examples/templates.zip`,
|
|
`diagnostic-command.zip` and `text-tools.zip`. Generate a directory-listing or
|
|
diagnostic draft, edit its preview and explicitly fill the send box. For the text
|
|
example, paste lines to sort or JSON to format, then copy/reuse the result.
|
|
Disable and re-enable a plugin to check action removal/restoration. To exercise
|
|
development reload, register a package directory instead of its ZIP, edit its
|
|
manifest/template or rebuild its component, and reload. An invalid replacement
|
|
reports an error while preserving the previous version. Uninstall removes its
|
|
contributions; enabled preferences survive application restart.
|
|
|
|
## Directories and data compatibility
|
|
|
|
The root is `AppRuntime.data_dir()/plugins`, so portable installations use their
|
|
local data directory and installed stable/preview builds remain isolated.
|
|
|
|
| Path | Purpose |
|
|
| --- | --- |
|
|
| `installed/<plugin-id>/` | Validated managed package snapshot |
|
|
| `staging/<transaction-id>/` | Copy/extraction, replacement and rollback trees |
|
|
| `work/data/<plugin-id>/` | Retained, isolated data area; not exposed to V1 guests |
|
|
| `dev/preferences.redb` | Isolated enabled preferences and canonical development-source registrations |
|
|
|
|
The installed directory is the catalog. It is scanned at startup; there is no
|
|
irreplaceable contribution index. Missing preferences default to disabled.
|
|
Corrupt or future preference documents stop plugin startup with a visible error;
|
|
they are never replaced with defaults or silently rewritten. Restore/repair the
|
|
isolated file before restarting. Plugin packages, preferences and execution state
|
|
are excluded from the existing cloud-sync and `.nya` backup contracts. Existing
|
|
settings, redb tables, credential formats and user command data are unchanged.
|
|
|
|
Installation rejects symlinks and Windows reparse points, traversal, absolute
|
|
resource paths, ADS/device aliases, archive links and duplicate case-insensitive
|
|
paths. Limits are 512 entries, 64 MiB compressed archive, 128 MiB expanded package,
|
|
and 32 MiB per file/component. Replacement reads components into memory before
|
|
compiling; it holds no installed-file handles while executing. On Windows, a
|
|
foreign process blocking directory replacement produces an error and preserves
|
|
the old installation. Transaction commit failure restores the old directory.
|
|
If even rollback fails due to external filesystem interference, the recovery tree
|
|
is retained in staging and the error tells the user to preserve it.
|
|
|
|
## Manifest schema 1
|
|
|
|
Plugin semantic version, manifest schema, and Wasm API version are distinct.
|
|
The current schema is `1` and the API is exactly `1.0.0`. Host compatibility uses
|
|
semver requirements; prerelease builds must explicitly match a prerelease, for
|
|
example `>=2.0.0-preview.4`. All unknown fields in schema-1 manifests, nested
|
|
actions/parameters and preference documents are rejected. Arrays of authors,
|
|
actions and parameters are extensible within their size limits; optional/default
|
|
fields below may be omitted. There is no arbitrary extension/config JSON in V1.
|
|
Future schemas must be explicitly implemented with compatibility fixtures.
|
|
|
|
```toml
|
|
schema_version = 1
|
|
id = "shell-templates"
|
|
name = "Shell templates"
|
|
version = "1.0.0"
|
|
authors = ["Your name"]
|
|
description = "Prepare commands for review"
|
|
host_version = ">=2.0.0-preview.4"
|
|
api_version = "1.0.0"
|
|
# component = "plugin.wasm" # Optional component-model binary
|
|
# capabilities = [] # Empty only; every extra capability is rejected
|
|
|
|
[[actions]]
|
|
id = "inspect"
|
|
name = "Inspect files"
|
|
description = "Draft a POSIX directory listing"
|
|
result = "command" # command or text
|
|
template = "templates/inspect.txt" # Optional; otherwise implemented by component
|
|
quoting = "posix" # literal (default), posix, powershell
|
|
|
|
[[actions.parameters]]
|
|
id = "path"
|
|
name = "Directory"
|
|
kind = "string" # string, integer (signed 64-bit), boolean (true/false)
|
|
required = true # false by default
|
|
default = "." # Optional string parsed according to declared kind
|
|
```
|
|
|
|
IDs start with a lowercase ASCII letter and contain lowercase letters, digits,
|
|
`-` or `_`, up to 96 bytes; Windows reserved names are forbidden. Contributions
|
|
are addressed as `plugin-id:action-id`. Duplicate IDs cannot overwrite another
|
|
plugin. Metadata name/author strings are limited to 160 bytes; descriptions to
|
|
4096 bytes; manifests to 128 KiB, actions to 128 and parameters per action to 32.
|
|
|
|
Resource paths are relative UTF-8 paths with `/` separators, up to 240 bytes.
|
|
Both paths and every package entry follow the cross-platform safety rules.
|
|
Template files are UTF-8 and support only `${declared-parameter}` substitution.
|
|
Unclosed/undeclared substitutions are rejected. There are no expressions,
|
|
conditionals, includes or implicit script evaluation. `posix` single-quotes each
|
|
substituted value and escapes single quotes; `powershell` doubles single quotes
|
|
inside a single-quoted value. `literal` inserts the value verbatim into the draft.
|
|
Quoting applies to parameters, not to the whole resource. Every result remains a
|
|
user-reviewed draft, and the quoting mode must match its intended shell.
|
|
|
|
## Rust SDK and WIT
|
|
|
|
`nyaterm-plugin-api` version `1.0.0` has no desktop, core, store, transport or GPUI
|
|
dependency. Its published-package layout includes `wit/plugin.wit`; both guest
|
|
wit-bindgen and host Wasmtime bindings are generated from this same file. A third
|
|
party can use the SDK from a NyaTerm Git checkout:
|
|
|
|
```toml
|
|
[package]
|
|
name = "my-plugin"
|
|
version = "1.0.0"
|
|
edition = "2024"
|
|
|
|
[lib]
|
|
crate-type = ["cdylib"]
|
|
|
|
[dependencies]
|
|
nyaterm-plugin-api = { git = "https://github.com/nyakang/nyaterm" }
|
|
```
|
|
|
|
Pin a reviewed Git revision when distributing a plugin. No SDK publication is
|
|
performed by this change. For local development use a path to the SDK crate.
|
|
|
|
```rust
|
|
use nyaterm_plugin_api::{ActionInput, ActionResult, Plugin, PluginError};
|
|
|
|
#[derive(Default)]
|
|
struct SortLines;
|
|
impl Plugin for SortLines {
|
|
fn invoke(&mut self, input: ActionInput) -> Result<ActionResult, PluginError> {
|
|
let text = input.text.unwrap_or_default();
|
|
let mut lines: Vec<_> = text.lines().collect();
|
|
lines.sort();
|
|
Ok(ActionResult::Text(lines.join("\n")))
|
|
}
|
|
}
|
|
nyaterm_plugin_api::register_plugin!(SortLines);
|
|
```
|
|
|
|
WIT exports `api-version`, `initialize(identity)`, `invoke(action-input)` and
|
|
`shutdown`. Parameters use typed text/integer/boolean variants; results are either
|
|
a command record (title, command) or text. Errors distinguish invalid input,
|
|
unsupported action and plugin failure. The registration macro retains one mutable
|
|
guest for serial calls; reload starts a fresh instance. Guest errors are presented
|
|
as bounded generic host messages to avoid leaking user input from guest diagnostics.
|
|
|
|
The SDK embeds a `nyaterm:plugin-api` custom section. The host requires exactly
|
|
one marker matching `1.0.0`, verifies actual typed component exports using the
|
|
generated bindings, and calls the exported version function. A forged manifest
|
|
or marker cannot authorize a different interface. Only this ABI is implemented.
|
|
|
|
## Build, debug and package
|
|
|
|
Install the Rust target once, then build examples and validation fixtures:
|
|
|
|
```sh
|
|
rustup target add wasm32-unknown-unknown
|
|
python scripts/plugins/build.py --fixtures
|
|
python scripts/plugins/verify.py
|
|
```
|
|
|
|
The build script obtains native-tool and guest target directories from Cargo
|
|
metadata. It honors `CARGO_TARGET_DIR` and target-directory configuration. For
|
|
example, in PowerShell, put build artifacts on a drive with sufficient space:
|
|
|
|
```powershell
|
|
$env:CARGO_TARGET_DIR = 'D:\NyaTermBuild'
|
|
python scripts/plugins/build.py --fixtures
|
|
cargo build --locked
|
|
```
|
|
|
|
Installable examples still appear under the checkout's `target/plugin-examples`.
|
|
|
|
The guest examples have an isolated workspace at `examples/plugins/guests` with
|
|
a checked Cargo.lock. This avoids nested-workspace conflicts and does not link
|
|
the desktop into guests. The script builds core modules using the SDK, then uses
|
|
`wit-component` to produce real components, validates their ABI/initialization,
|
|
and writes installable packages under `target/plugin-examples`:
|
|
|
|
1. `templates`: declarative `ls` draft with a quoted parameter.
|
|
2. `diagnostic-command`: Rust/Wasm hostname diagnostics draft with per-instance
|
|
invocation count; never performs DNS/network/process operations itself.
|
|
3. `text-tools`: Rust/Wasm line sorting and JSON pretty-printing.
|
|
|
|
The SDK-built adversarial fixture is also reproducible. Checked binary fixtures
|
|
under `nyaterm-plugin-host/tests/fixtures` allow host tests from a clean checkout
|
|
without installing a guest target; rebuilding uses the same SDK and WIT.
|
|
|
|
For another guest:
|
|
|
|
```sh
|
|
cargo build --manifest-path /path/to/my-plugin/Cargo.toml --release --target wasm32-unknown-unknown
|
|
cargo run -p nyaterm-plugin-host --bin nyaterm-plugin-tool -- component /path/to/my_plugin.wasm /path/to/package/plugin.wasm
|
|
cargo run -p nyaterm-plugin-host --bin nyaterm-plugin-tool -- validate /path/to/package
|
|
cargo run -p nyaterm-plugin-host --bin nyaterm-plugin-tool -- pack /path/to/package /path/to/plugin.zip
|
|
```
|
|
|
|
Register the package directory for development, edit/build its resources, then
|
|
reload explicitly. Validation and initialization errors preserve the previous
|
|
instance. The manager shows diagnostics, never raw Wasmtime stacks or user text.
|
|
|
|
## Isolation and limits
|
|
|
|
Wasmtime `39.0.1` (MSRV 1.89) runs Component Model guests on dedicated serial
|
|
workers. wit-bindgen `0.49.0` has MSRV 1.82; the SDK declares 1.89. The tested
|
|
toolchain is Rust 1.97.1 on Windows x64. Each instance owns its Store and mutable
|
|
guest; different plugins have separate workers. The process manager accepts at
|
|
most 16 queued management operations; each guest accepts 8 queued calls.
|
|
|
|
Initialization and calls have a 2-second deadline including queued wait, a
|
|
20-million fuel budget and epoch checks every 10 ms. Epoch callbacks trap on
|
|
deadline/cancellation; they do not yield. Timeout actually stops execution.
|
|
Memory is limited to 32 MiB per linear memory, at most 4 memories, 4 tables of
|
|
16,384 elements, 8 core instances, and 512 KiB Wasm stack. Inputs and outputs
|
|
are limited to 256 KiB; command titles to 160 bytes. Results containing control
|
|
characters other than ordinary tabs/newlines are rejected. Guest memory, trap,
|
|
budget, deadline and invalid-result failures retire only that instance.
|
|
|
|
Cancellation, disabling, replacement and uninstall invalidate the old generation
|
|
and retire its worker. Dropping a pending invocation cancels it. Shutdown joins
|
|
all workers before AppShell quits, off the GPUI thread. Resource ownership is
|
|
explicit: snapshots own temporary directories, the registry owns instances,
|
|
instances own Stores/queues, and the engine owns its epoch clock. Components
|
|
receive no inherited stdin/stdout/stderr or WASI environment. No guest logging
|
|
import is exposed. CPU execution can be interrupted; compilation is bounded by
|
|
package size and runs on the manager thread, rather than in a render/update path.
|
|
|
|
Orderly application exit rejects new and queued operations, finishes any started
|
|
management transaction, then joins plugin workers and the epoch clock. Execution
|
|
timeouts do not cover Wasmtime compilation or filesystem I/O. These remain
|
|
in-process background work, so shutdown may wait for them; V1 does not promise a
|
|
hard compilation timeout. Selected revisions are checked before guest dispatch,
|
|
preventing stale form inputs from reaching replacement instances.
|
|
|
|
## Verification and current limits
|
|
|
|
Run the complete repository checks with:
|
|
|
|
```sh
|
|
python scripts/plugins/verify.py --workspace
|
|
```
|
|
|
|
This builds the application and runtime helpers, checks the workspace, runs all
|
|
tests, verifies formatting, and runs Clippy. Actual command results and any
|
|
baseline failures are maintained in `plugin-system-progress.md`.
|
|
The script also checks guest formatting, rebuilds SDK components, verifies the
|
|
packaged SDK and checks architecture boundaries. Each invocation owns a fresh
|
|
`target/plugin-verification/run-<id>/` directory, preserving previous failures.
|
|
|
|
Windows x64 verification used a HEAD source snapshot with only plugin changes,
|
|
and a fresh build cache: default native build, workspace check, all tests (3280
|
|
passed, 13 existing ignored), formatting and Clippy all passed. No new plugin
|
|
tests are ignored. The SDK's packaged WIT was independently compiled; the host
|
|
also passed ARM64 Windows cross-compilation, without ARM64 runtime execution.
|
|
|
|
The current workspace, including subsequent terminal changes and the explicit
|
|
backup/sync isolation regression, also passed check, formatting and all-targets
|
|
Clippy. Serial workspace tests passed 3290 with 13 existing ignored tests.
|
|
Default-parallel reruns encountered an unchanged ConPTY close timeout and an
|
|
unchanged HTTP test socket timeout; each exact rerun passed. Their failures and
|
|
the serial result remain recorded in the progress document. The detailed
|
|
[acceptance table](plugin-system-acceptance.md) separates automated evidence from
|
|
the current Windows native walkthrough and historical results.
|
|
|
|
Windows x64 host tests exercise real SDK component initialization/calls, state
|
|
isolation, archive/path safety, junction rejection, file occupation, transactional
|
|
rollback, enable persistence, cancellation, stale-result rejection, resource
|
|
failures and shutdown handle release. GPUI tests cover shared process ownership
|
|
and composer-only adaptation. The lifecycle closeout passed SDK source builds,
|
|
SDK packaging, plugin regressions and the default-parallel workspace suite
|
|
(3305 passed, 13 existing ignored), build/check/fmt/Clippy and workflow syntax.
|
|
The architecture gate still reports the existing transfer path-bar violation;
|
|
plugin crate boundaries pass and no allowlist was expanded.
|
|
|
|
The recovered native computer-use connection exercised all three examples in
|
|
an isolated Windows x64 portable instance: result editing and explicit draft
|
|
append/replace, text copy/reuse, shared-window enable/disable/uninstall, failed
|
|
update/development reload, guest fault isolation, restart preferences and normal
|
|
exit with released database/Wasm handles. No command was sent and real user data
|
|
was untouched. Precise lifecycle races use automated barriers. macOS/Linux and
|
|
Windows ARM64 execution are not claimed as tested. The
|
|
[acceptance record](plugin-system-acceptance.md) lists the evidence and limits.
|
|
|
|
V1 has no marketplace, automatic updates, plugin sync/backup, arbitrary UI,
|
|
protocol decoders, KV or host capabilities. Add a capability only with a narrow
|
|
WIT import, host enforcement of declaration ∩ user grant ∩ supported operation,
|
|
revocation cancellation and compatibility tests. Extend schema/API versions
|
|
explicitly; never infer ABI compatibility from the manifest alone.
|