ticket: add plugin authoring roadmap
This commit is contained in:
+4
-3
@@ -12,9 +12,10 @@ It is not a dumping ground for external research, old plans, API inventories, or
|
||||
4. [`design/profiles-manifests-prompts.md`](design/profiles-manifests-prompts.md) — reusable Profiles, resolved Manifests, and prompt resources.
|
||||
5. [`design/tool-permissions-scope.md`](design/tool-permissions-scope.md) — tool policy and filesystem scope.
|
||||
6. [`design/plugin-packages.md`](design/plugin-packages.md) — plugin package distribution, discovery, and enablement boundaries.
|
||||
7. [`design/memory-knowledge.md`](design/memory-knowledge.md) — generated memory, Knowledge, and audit records.
|
||||
8. [`development/work-items.md`](development/work-items.md) — how project work is recorded and reviewed.
|
||||
9. [`development/validation.md`](development/validation.md) — how to check changes.
|
||||
7. [`development/plugin-development.md`](development/plugin-development.md) — how to build, package, enable, and inspect Yoi Plugins.
|
||||
8. [`design/memory-knowledge.md`](design/memory-knowledge.md) — generated memory, Knowledge, and audit records.
|
||||
9. [`development/work-items.md`](development/work-items.md) — how project work is recorded and reviewed.
|
||||
10. [`development/validation.md`](development/validation.md) — how to check changes.
|
||||
|
||||
## What belongs here
|
||||
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
# Plugin development
|
||||
|
||||
This guide is for building a Yoi Plugin outside the Yoi runtime codebase. It describes the current Plugin package shape, how to author a Tool Plugin, how to enable it in a workspace, and how to inspect/debug it.
|
||||
|
||||
Yoi Plugins are intentionally explicit:
|
||||
|
||||
- putting a package in `.yoi/plugins` only makes it discoverable;
|
||||
- a Profile/config entry must explicitly enable it;
|
||||
- Plugin grants must allow its surfaces and host APIs;
|
||||
- Plugin code runs only through the configured sandbox runtime;
|
||||
- Tool calls and Tool results use the ordinary Yoi Tool/Worker history path.
|
||||
|
||||
## Current status
|
||||
|
||||
Implemented foundation:
|
||||
|
||||
- package discovery from project/user Plugin stores;
|
||||
- explicit enablement resolution;
|
||||
- Tool surface registration;
|
||||
- Plugin permission grants;
|
||||
- raw core-Wasm Tool runtime;
|
||||
- Component Model Tool runtime;
|
||||
- `https` and `fs` host APIs for Tool runtime;
|
||||
- read-only `yoi plugin list/show` inspection.
|
||||
|
||||
Still intentionally separate/future work:
|
||||
|
||||
- `yoi plugin new/check/pack` authoring commands;
|
||||
- polished multi-language SDK/PDK crates;
|
||||
- Service / Ingress surfaces;
|
||||
- WebSocket or inbound HTTP for bidirectional bridges;
|
||||
- public registry/install/update/signature tooling.
|
||||
|
||||
## Package locations
|
||||
|
||||
Yoi discovers `.yoi-plugin` packages from:
|
||||
|
||||
```text
|
||||
<workspace>/.yoi/plugins/*.yoi-plugin
|
||||
${XDG_DATA_HOME:-~/.local/share}/yoi/plugins/*.yoi-plugin
|
||||
```
|
||||
|
||||
Use project packages for workspace-specific Plugins and user packages for personal reusable Plugins. Project packages should normally be committed only when the package content is safe and intended to be part of the project.
|
||||
|
||||
## Package archive format
|
||||
|
||||
A `.yoi-plugin` package is currently a bounded ZIP archive. For now, create it with stored entries, not compressed entries:
|
||||
|
||||
```bash
|
||||
(cd my-plugin && zip -0 -r ../example.echo.yoi-plugin plugin.toml plugin.component.wasm)
|
||||
```
|
||||
|
||||
The archive root must contain `plugin.toml`. Runtime files referenced by the manifest must also be inside the archive. Yoi rejects path traversal, root escapes, malformed manifests, unsupported API/runtime versions, and other unsafe archive shapes.
|
||||
|
||||
## Manifest: `plugin.toml`
|
||||
|
||||
A minimal Component Model Tool Plugin manifest looks like this:
|
||||
|
||||
```toml
|
||||
schema_version = 1
|
||||
id = "example.echo"
|
||||
name = "Example Echo"
|
||||
version = "0.1.0"
|
||||
surfaces = ["tool"]
|
||||
permissions = [
|
||||
{ kind = "surface", surface = "tool" },
|
||||
{ kind = "tool", name = "example_echo" },
|
||||
]
|
||||
|
||||
[runtime]
|
||||
kind = "wasm-component"
|
||||
component = "plugin.component.wasm"
|
||||
world = "yoi:plugin/tool@1.0.0"
|
||||
|
||||
[[tools]]
|
||||
name = "example_echo"
|
||||
description = "Echo input text."
|
||||
input_schema = { type = "object", properties = { text = { type = "string" } }, required = ["text"], additionalProperties = false }
|
||||
external_write = false
|
||||
```
|
||||
|
||||
The preferred new runtime is `wasm-component`. The older raw core-Wasm runtime remains explicit for compatibility:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "wasm"
|
||||
entry = "plugin.wasm"
|
||||
abi = "yoi-plugin-wasm-1"
|
||||
```
|
||||
|
||||
Do not rely on package presence to activate anything. Discovery only records inventory.
|
||||
|
||||
## Component Model authoring sketch
|
||||
|
||||
Yoi's Component Model Tool world is stored in `resources/plugin/wit/`. A minimal Rust sketch is available at:
|
||||
|
||||
```text
|
||||
docs/examples/plugin-component-tool/lib.rs
|
||||
```
|
||||
|
||||
The important authoring shape is:
|
||||
|
||||
```rust
|
||||
wit_bindgen::generate!({
|
||||
world: "tool",
|
||||
path: "../../../resources/plugin/wit",
|
||||
});
|
||||
|
||||
struct Plugin;
|
||||
|
||||
impl Guest for Plugin {
|
||||
fn call(tool_name: String, input_json: String) -> String {
|
||||
format!(
|
||||
r#"{{"summary":"component tool {tool_name}","content":"input was {input_json}"}}"#
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export!(Plugin);
|
||||
```
|
||||
|
||||
The returned string is ordinary `ToolOutput` JSON. It is routed through the normal Tool result path; the component cannot inject hidden context.
|
||||
|
||||
The exact build pipeline depends on the authoring toolchain (`wit-bindgen`, component adapter tooling, etc.). Until `yoi plugin new/check/pack` exists, Plugin authors should treat the example as the ABI contract sketch and use `yoi plugin list/show` plus focused runtime tests to verify packages.
|
||||
|
||||
## Enabling a Plugin in a workspace
|
||||
|
||||
Enablement belongs in the resolved Profile/config path for the workspace. For local dogfooding or private experiments, use the ignored local overlay rather than committing secrets or local paths:
|
||||
|
||||
```toml
|
||||
# .yoi/override.local.toml
|
||||
|
||||
[features]
|
||||
plugins = true
|
||||
|
||||
[[plugins.enabled]]
|
||||
id = "project:example.echo"
|
||||
version = "0.1.0"
|
||||
digest = "sha256:<digest from yoi plugin show/list>"
|
||||
surfaces = ["tool"]
|
||||
|
||||
[plugins.enabled.grants]
|
||||
id = "project:example.echo"
|
||||
version = "0.1.0"
|
||||
digest = "sha256:<same digest>"
|
||||
permissions = [
|
||||
{ kind = "surface", surface = "tool" },
|
||||
{ kind = "tool", name = "example_echo" },
|
||||
]
|
||||
```
|
||||
|
||||
A source-qualified id is preferred:
|
||||
|
||||
```text
|
||||
project:example.echo
|
||||
user:example.echo
|
||||
builtin:example.echo
|
||||
```
|
||||
|
||||
Unqualified ids can be ambiguous and should fail closed when more than one source matches.
|
||||
|
||||
## Inspecting Plugins
|
||||
|
||||
Use the read-only CLI inspection commands first:
|
||||
|
||||
```bash
|
||||
yoi plugin list
|
||||
yoi plugin list --json
|
||||
yoi plugin show project:example.echo
|
||||
yoi plugin show project:example.echo --json
|
||||
```
|
||||
|
||||
`list/show` must not execute Plugin code. They are intended to explain static state:
|
||||
|
||||
- discovered packages;
|
||||
- enabled vs disabled packages;
|
||||
- missing packages referenced by enablement;
|
||||
- invalid manifests;
|
||||
- digest/version/source mismatches;
|
||||
- granted/denied permissions;
|
||||
- Tool registration eligibility;
|
||||
- runtime metadata.
|
||||
|
||||
Typical statuses:
|
||||
|
||||
```text
|
||||
active enabled and statically valid for at least one surface/tool
|
||||
disabled discovered but not explicitly enabled
|
||||
missing enablement references a package that is not discovered
|
||||
rejected invalid manifest, incompatible API, digest mismatch, grant denial, etc.
|
||||
partial usable package with some rejected surfaces/tools
|
||||
```
|
||||
|
||||
## `https` host API
|
||||
|
||||
The `https` host API is outbound-only and grant-gated. It is meant for Tool calls such as webhook posting or REST requests. It is not a WebSocket/Gateway or inbound HTTP bridge.
|
||||
|
||||
Manifest permissions should request `host_api.https` in addition to the Tool permissions. Enablement grants must then allow the API and constrain hosts/methods.
|
||||
|
||||
Example grant shape:
|
||||
|
||||
```toml
|
||||
[plugins.enabled.grants]
|
||||
permissions = [
|
||||
{ kind = "surface", surface = "tool" },
|
||||
{ kind = "tool", name = "discord_post" },
|
||||
{ kind = "host_api", api = "https" },
|
||||
]
|
||||
|
||||
[[plugins.enabled.grants.https]]
|
||||
host = "discord.com"
|
||||
methods = ["POST"]
|
||||
path_prefixes = ["/api/webhooks/"]
|
||||
```
|
||||
|
||||
Yoi rejects `http://`, localhost/private/link-local targets, disallowed hosts/methods, oversize requests/responses, and missing grants. Credentials must come from explicit config/secret references, not ambient environment variables.
|
||||
|
||||
## `fs` host API
|
||||
|
||||
The `fs` host API is Plugin-scoped and grant-gated. Plugins do not inherit the Pod/workspace filesystem authority automatically.
|
||||
|
||||
Example grant shape:
|
||||
|
||||
```toml
|
||||
[plugins.enabled.grants]
|
||||
permissions = [
|
||||
{ kind = "surface", surface = "tool" },
|
||||
{ kind = "tool", name = "read_notes" },
|
||||
{ kind = "host_api", api = "fs" },
|
||||
]
|
||||
|
||||
[[plugins.enabled.grants.fs]]
|
||||
root = "/absolute/path/to/plugin-data"
|
||||
operations = ["read", "list"]
|
||||
```
|
||||
|
||||
Yoi normalizes paths, rejects `..` traversal, rejects symlink/root escapes, and applies read/write/list bounds. Diagnostics must not include file contents.
|
||||
|
||||
## Outbound vs bridge integrations
|
||||
|
||||
After `https`, an outbound Discord webhook Tool is feasible:
|
||||
|
||||
```text
|
||||
Yoi Tool call -> Plugin Tool -> yoi:host/https -> Discord REST/webhook
|
||||
```
|
||||
|
||||
A bidirectional Discord bridge is different. It needs a Service surface plus Ingress and either WebSocket/Gateway support or inbound HTTP interactions:
|
||||
|
||||
```text
|
||||
Discord Gateway/Webhook -> Plugin Service/Ingress -> host routing policy -> notify/run/drop/diagnostic
|
||||
```
|
||||
|
||||
Do not model bidirectional bridge work as an `https` Tool alone.
|
||||
|
||||
## Development checklist
|
||||
|
||||
1. Create a package directory with `plugin.toml` and the runtime artifact.
|
||||
2. Build the Wasm/component artifact.
|
||||
3. Package with stored ZIP entries as `.yoi-plugin`.
|
||||
4. Put it under `.yoi/plugins/` or the user Plugin store.
|
||||
5. Run `yoi plugin list` and `yoi plugin show <ref>`.
|
||||
6. Add explicit enablement and grants.
|
||||
7. Re-run `yoi plugin show <ref>` until status/diagnostics are correct.
|
||||
8. Start Yoi with `features.plugins = true` in the resolved config/Profile.
|
||||
9. Call the Tool and verify ordinary Tool result/history behavior.
|
||||
|
||||
## Safety rules for Plugin authors
|
||||
|
||||
- Do not assume ambient filesystem, network, or environment access.
|
||||
- Do not put secrets in `plugin.toml` or package files.
|
||||
- Request only the minimal host APIs and grants needed.
|
||||
- Keep Tool output bounded and structured.
|
||||
- Prefer Component Model authoring for new Plugins.
|
||||
- Treat raw core-Wasm ABI support as transitional compatibility.
|
||||
Reference in New Issue
Block a user