diff --git a/docs/README.md b/docs/README.md index 8517e8850..a4d825f90 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,6 +33,7 @@ Pick the row that matches what you want to accomplish next: | Add model fallbacks | [Configure Model Fallback](./guides/configure-model-fallback.md) | | Enable web search | [Configure Web Search](./guides/configure-web-search.md) | | Add an MCP tool server | [Configure MCP Tools](./guides/configure-mcp-tools.md) | +| Install a nanobot, Pi, or OpenClaw extension | [Extensions](./extensions.md) | | Generate images | [Image Generation](./image-generation.md) | | Schedule work or create a local trigger | [Automations](./automations.md) | | Understand and manage long-term memory | [Memory](./memory.md) | @@ -79,6 +80,8 @@ These pages explain implementation and extension points. You do not need them to | Understand source ownership and runtime flow | [Architecture](./architecture.md) | | Set up a development environment | [Development](./development.md) and [CONTRIBUTING.md](../CONTRIBUTING.md) | | Add a channel package | [Channel Package Guide](./channel-package-guide.md) | +| Publish an extension package | [Extension Authoring](./extension-authoring.md) | +| Understand the extension control plane | [Extension System](./extension-system.md) | | Build the WebUI source | [WebUI Development](../webui/README.md) | If a command or screen no longer matches these docs, please [open an issue](https://github.com/HKUDS/nanobot/issues) with your nanobot version, operating system, and the page that needs correction. diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 7e36a6c3e..2429fb77f 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -18,6 +18,7 @@ Use this page when you know what you want to run and need the command shape. For | Deliver a local trigger | `nanobot trigger "message"` | Created first with `/trigger ` in the target chat/session | | Serve an OpenAI-compatible API | `nanobot serve` | Starts `/v1/chat/completions`, `/v1/models`, and `/health` | | Check chat channel setup | `nanobot channels status` | Useful before starting `nanobot gateway` | +| Manage extension packages | `nanobot extensions list` | Install, inspect, trust, enable, and remove nanobot, Pi, or OpenClaw packages | | Manage optional features | `nanobot plugins list` | Shows channels and optional capabilities you can turn on | | Log in to QR/OAuth-style channels | `nanobot channels login ` | Used by channels such as WhatsApp and WeChat | | Log in to OAuth model providers | `nanobot provider login ` | Used by OpenAI Codex, xAI subscription, and GitHub Copilot providers | @@ -248,6 +249,42 @@ nanobot channels status See [`chat-apps.md`](./chat-apps.md) for channel-specific setup. +## Extensions + +Extension installation, trust, permission grants, and enablement are separate +operations: + +| Command | Description | +|---|---| +| `nanobot extensions list` | Show installed packages and activation policy | +| `nanobot extensions inspect ` | Show contributions, dependencies, requested permissions, and diagnostics | +| `nanobot extensions search [query]` | Search compatible packages on npm | +| `nanobot extensions search [query] --ecosystem ` | Filter to `nanobot`, `pi`, or `openclaw` | +| `nanobot extensions install ` | Install an npm package as untrusted | +| `nanobot extensions install --kind git [--ref ]` | Install from a Git branch, tag, or commit | +| `nanobot extensions install --kind local` | Install from a local package directory | +| `nanobot extensions permissions [permissions...]` | Replace the exact granted permission set; omit values to revoke all | +| `nanobot extensions trust ` | Approve executing the installed package | +| `nanobot extensions untrust ` | Revoke trust and stop activation | +| `nanobot extensions enable ` | Allow activation when every other gate passes | +| `nanobot extensions disable ` | Stop activation without uninstalling | +| `nanobot extensions uninstall ` | Remove the user-scope package after confirmation | +| `nanobot extensions uninstall --yes` | Remove without an interactive confirmation | + +Example: + +```bash +nanobot extensions install @acme/pi-review +nanobot extensions inspect pi.acme.pi-review +nanobot extensions permissions pi.acme.pi-review workspace.read +nanobot extensions trust pi.acme.pi-review +nanobot extensions enable pi.acme.pi-review +``` + +Installed packages live under `~/.nanobot/extensions/`. They do not execute +until trusted. See [Extensions](./extensions.md) for the safety model and +[Extension Authoring](./extension-authoring.md) for package compatibility. + ## Optional Features Use these commands when you want nanobot to add or remove a built-in capability diff --git a/docs/configuration.md b/docs/configuration.md index 3f461a07e..ed35c27ba 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -27,6 +27,7 @@ the focused guides first and come back here for exact fields and defaults. | Configure model fallback | [`guides/configure-model-fallback.md`](./guides/configure-model-fallback.md) | | Add an OpenAI-compatible provider | [`guides/configure-openai-compatible-provider.md`](./guides/configure-openai-compatible-provider.md) | | Add Langfuse observability | [`guides/configure-langfuse-observability.md`](./guides/configure-langfuse-observability.md) | +| Install and govern extensions | [`extensions.md`](./extensions.md) | | Secure a local AI agent | [`guides/secure-local-ai-agent.md`](./guides/secure-local-ai-agent.md) | | Deploy the gateway | [`guides/deploy-nanobot-gateway.md`](./guides/deploy-nanobot-gateway.md) | @@ -45,6 +46,7 @@ the focused guides first and come back here for exact fields and defaults. | Configure web search and fetch | [Web Tools](#web-tools) | | Enable image generation | [Image Generation](#image-generation) | | Add MCP servers | [MCP](#mcp-model-context-protocol) | +| Configure extension discovery and policy | [Extensions](#extensions) | | Review shell, workspace, and SSRF controls | [Security](#security) | | Control access and pairing | [Pairing](#pairing) | | Tune gateway jobs, sessions, and tools | [Gateway Heartbeat](#gateway-heartbeat), [Auto Compact](#auto-compact), [Unified Session](#unified-session), [Tool Hint Max Length](#tool-hint-max-length) | @@ -64,6 +66,7 @@ If the WebUI does not expose the option you need, start from the task below. Mos | Enable web search or fetch | `tools.web.search.*`, `tools.web.fetch.*`, optional `tools.ssrfWhitelist` | Ask a question that requires current web information, then inspect logs if needed | [Web Tools](#web-tools), [Security](#security) | | Enable image generation | `tools.imageGeneration.enabled`, `tools.imageGeneration.provider`, `tools.imageGeneration.model`, matching provider credentials | Enable Image Generation in the WebUI and send one image request | [Image Generation](#image-generation) | | Add external tools through MCP | `tools.mcpServers.` | Start `nanobot gateway --verbose` and check startup/tool logs | [MCP](#mcp-model-context-protocol) | +| Discover and govern extension packages | `extensions.*` | `nanobot extensions list`, then inspect the package | [Extensions](#extensions), [Extension guide](./extensions.md) | | Tighten tool and network safety | `tools.restrictToWorkspace`, `tools.exec.sandbox`, `tools.ssrfWhitelist`, `channels.*.allowFrom` | Run the same workflow through the channel or CLI you plan to expose | [Security](#security), [Pairing](#pairing) | | Tune request timeouts or process concurrency | `NANOBOT_LLM_TIMEOUT_S`, `NANOBOT_STREAM_IDLE_TIMEOUT_S`, `NANOBOT_MAX_CONCURRENT_REQUESTS` | Start nanobot from the same environment and inspect startup/runtime logs | [Runtime Environment Variables](#runtime-environment-variables) | | Run multiple isolated bots | separate `--config` and `--workspace` paths, plus distinct `gateway.port` or channel ports when processes run together | Use the same explicit paths with `nanobot status`, `agent`, `webui`, `gateway`, and `serve` | [Multiple Instances](./multiple-instances.md), [CLI Reference](./cli-reference.md) | @@ -2228,6 +2231,53 @@ When enabled, all incoming messages — regardless of which channel they arrive > This is designed for single-user, multi-device setups. It is **off by default** — existing users see zero behavior change. +## Extensions + +Use the WebUI **Extensions** page or `nanobot extensions` commands for normal +installation and trust decisions. The top-level `extensions` object is for +advanced discovery and policy: + +```json +{ + "extensions": { + "enabled": true, + "paths": ["/opt/nanobot/extensions"], + "allow": [], + "deny": ["acme.blocked"], + "workspaceTrust": "ask", + "entries": { + "acme.review": { + "enabled": true, + "trusted": true, + "permissions": ["workspace.read"], + "config": { + "mode": "strict" + } + } + } + } +} +``` + +| Option | Default | Description | +|---|---|---| +| `extensions.enabled` | `true` | Enable external extension discovery and activation | +| `extensions.paths` | `[]` | Additional manifest roots; each root may be one package or contain package directories | +| `extensions.allow` | `[]` | Optional extension ID allowlist; empty allows all IDs not denied | +| `extensions.deny` | `[]` | Extension IDs that must remain inactive | +| `extensions.workspaceTrust` | `"ask"` | Workspace extension policy: `"ask"`, `"allow"`, or `"deny"` | +| `extensions.entries..enabled` | `true` | Per-extension activation switch | +| `extensions.entries..trusted` | `false` | Approve executing that extension's code | +| `extensions.entries..permissions` | `[]` | Exact host permissions granted to the extension | +| `extensions.entries..config` | `{}` | Package-owned configuration passed to its runtime | + +User-installed packages and their interactive trust state live under +`~/.nanobot/extensions/`. Config policy is merged at discovery time; nanobot +does not silently rewrite `config.json` when you install a package. + +See [Extensions](./extensions.md) for the safe install flow and +[Extension Authoring](./extension-authoring.md) for the package contract. + ## Disabled Skills nanobot ships with built-in skills, and your workspace can also define custom skills under `skills/`. If you want to hide specific skills from the agent, set `agents.defaults.disabledSkills` to a list of skill directory names: diff --git a/docs/extension-authoring.md b/docs/extension-authoring.md new file mode 100644 index 000000000..e0e00e0e2 --- /dev/null +++ b/docs/extension-authoring.md @@ -0,0 +1,294 @@ +# Extension Authoring + +nanobot extensions are packages with a strict `nanobot.extension.json` +manifest. The manifest can be inspected without importing optional SDKs or +executing package code. Runtime activation then projects each contribution into +the native nanobot registry that owns that capability. + +This guide covers native Python extensions and the compatibility boundary for +Pi and OpenClaw packages. Users installing packages should read +[Extensions](./extensions.md). + +## Package Layout + +A minimal native package is: + +```text +my-extension/ +|-- nanobot.extension.json +`-- my_extension/ + `-- __init__.py +``` + +`nanobot.extension.json`: + +```json +{ + "id": "acme.review", + "name": "Acme Review", + "version": "1.0.0", + "apiVersion": 1, + "runtime": "python", + "entry": "my_extension:register", + "description": "Adds a review tool and command.", + "homepage": "https://example.com/acme-review", + "license": "MIT", + "contributions": [ + { + "kind": "tool", + "name": "review_code", + "description": "Review a local change." + }, + { + "kind": "command", + "name": "review", + "description": "Start a review from chat." + } + ], + "dependencies": [ + { + "kind": "python", + "name": "acme-review-core", + "specifier": ">=1,<2", + "optional": false + } + ], + "permissions": [ + { + "name": "workspace.read", + "reason": "Read files selected for review." + } + ] +} +``` + +Unknown fields are rejected. This is deliberate: a misspelled permission, +dependency, or contribution must not silently change package behavior. + +## Manifest Reference + +### Top-level fields + +| Field | Required | Meaning | +|---|---:|---| +| `id` | yes | Stable lowercase package identity | +| `name` | yes | Human-readable name | +| `version` | yes | Installed package version | +| `apiVersion` | no | Manifest API, currently `1` | +| `runtime` | yes | `python`, `pi`, `openclaw`, or `declarative` | +| `entry` | runtime-dependent | One activation entry | +| `entries` | runtime-dependent | Multiple Pi/OpenClaw entries; takes precedence over `entry` | +| `contributions` | no | Capabilities owned by the package | +| `dependencies` | no | Activation prerequisites | +| `permissions` | no | Privileged host capabilities requested from the user | +| `description` | no | Catalog summary | +| `homepage` | no | Project or documentation URL | +| `license` | no | SPDX-style license label | + +Entries must be relative to the package root and cannot contain `..`. + +### Contributions + +Supported `kind` values are: + +```text +tool +skill +channel +llm_provider +transcription_provider +image_generation_provider +web_search_provider +mcp_server +hook +command +webui +``` + +Each contribution has a stable `name`, optional runtime `target`, optional +`description`, and optional `replaces` list. `replaces` contains extension IDs, +not contribution names. Replacement is still checked against scope precedence. + +The manifest declares ownership. It does not create an implementation by +itself. A runtime must register the corresponding native capability. + +### Dependencies + +| Kind | `name` identifies | Version behavior | +|---|---|---| +| `python` | Installed Python distribution | PEP 440 specifier | +| `npm` | Package under the extension's `node_modules` | npm installation constraint | +| `executable` | Command on `PATH` | No version probe | +| `environment` | Environment variable | Must be non-empty | +| `extension` | Another extension ID | Installed extension version | + +Set `optional: true` when the extension can activate without the dependency. +Do not put API keys in the manifest. + +### Permissions + +Permission names are lowercase namespaced identifiers such as +`workspace.read`, `workspace.write`, or `network.http`. Include a concrete +reason the user can evaluate. Activation requires every requested permission to +be granted. + +Permissions describe host policy; they are not an OS sandbox. Keep the request +set minimal and use native host operations when one exists. + +## Native Python Runtime + +The Python entry is `module[:attribute]`; the default attribute is `register`. +The function is synchronous and must return `None`: + +```python +from nanobot.agent.tools.base import Tool + + +class ReviewTool(Tool): + @property + def name(self) -> str: + return "review_code" + + @property + def description(self) -> str: + return "Review a local code change." + + @property + def parameters(self) -> dict: + return { + "type": "object", + "properties": { + "path": {"type": "string"}, + }, + "required": ["path"], + } + + async def execute(self, path: str) -> str: + return f"Review requested for {path}" + + +def register(api) -> None: + api.register_tool(ReviewTool()) +``` + +The v1 Python API exposes: + +- `register_tool(tool)` +- `register_command(command, handler, prefix=False)` +- `register_hook_factory(factory)` + +These methods use existing nanobot registries. Do not import or patch +`AgentLoop`, reach into WebSocket internals, or create a second tool registry. +If a new contribution kind needs execution support, add an adapter at the +native subsystem boundary and keep the manifest API independent from that +implementation. + +## Publish and Discover + +For npm discovery, include the exact `nanobot-extension` keyword: + +```json +{ + "keywords": [ + "nanobot-extension" + ] +} +``` + +Compatibility packages use their upstream metadata and keyword: + +- Pi: `pi-package` plus `pi.extensions` +- OpenClaw: `openclaw-plugin` plus `openclaw.extensions` or + `openclaw.runtimeExtensions` + +Native Python packages can be installed from a local directory or Git source. +The market is an index; installation always passes through local validation, +dependency checks, permission review, trust, and activation. + +## Compatibility Matrix + +The matrix describes the current adapter, not an intention to support every +upstream API. + +| Upstream capability | Pi | OpenClaw | nanobot behavior | +|---|---|---|---| +| Tool registration and calls | Executable | Executable | Projected as native `Tool`; implementation stays in Node sidecar | +| Slash commands | Executable | Executable | Registered in native command router | +| Supported lifecycle events | Observation-only | Observation-only | Receives serialized run/tool events; cannot mutate native context | +| Provider registration | Metadata only | Metadata only | Visible in catalog and diagnostics; not an executable provider | +| Transcription/image/web-search provider contracts | N/A | Metadata only | Visible but not projected into native provider registries | +| Skills declared in plugin metadata | N/A | Metadata only | Catalog ownership only; runtime skill installation is not synthesized | +| Channels | N/A | Metadata only | No OpenClaw channel host emulation | +| Session tree and custom entries | Unsupported | Unsupported | No compatible host surface | +| Terminal UI, widgets, renderers, shortcuts | Unsupported | Unsupported | WebUI and CLI have different rendering contracts | +| Model selection and thinking control | Unsupported | Unsupported | Remains owned by nanobot model presets and request policy | +| Arbitrary upstream host services | Unsupported | Unsupported | Reported as diagnostics rather than silently emulated | + +### Pi package shape + +```json +{ + "name": "@acme/pi-review", + "version": "1.0.0", + "keywords": ["pi-package"], + "pi": { + "extensions": ["./index.ts"] + } +} +``` + +The sidecar supports `registerTool`, `registerCommand`, and selected `on(...)` +lifecycle handlers. TypeScript uses Node's native type stripping when +available, with `jiti` as a fallback installed with the package runtime. + +### OpenClaw package shape + +```json +{ + "name": "@acme/openclaw-review", + "version": "1.0.0", + "keywords": ["openclaw-plugin"], + "openclaw": { + "runtimeExtensions": ["./dist/index.js"] + } +} +``` + +If present, `openclaw.plugin.json` supplies catalog identity, contribution +contracts, command aliases, and compatibility diagnostics. The OpenClaw +`register` function must complete synchronously during load. + +## Test an Extension + +Use an isolated config and workspace while developing: + +```bash +nanobot extensions install ./my-extension --kind local +nanobot extensions inspect acme.review +nanobot extensions permissions acme.review workspace.read +nanobot extensions trust acme.review +nanobot extensions enable acme.review +nanobot agent -m "Use review_code on README.md" +``` + +Also test: + +- install while untrusted does not execute code; +- missing hard dependencies leave the package inactive; +- denied or missing permissions prevent activation; +- duplicate contribution names become diagnostics; +- disable, untrust, reload, and uninstall remove runtime registrations; +- one broken package does not block unrelated packages. + +For nanobot itself, extension tests live under `tests/extensions/`. Keep +compatibility fixtures small and assert diagnostics for unsupported APIs. + +## Design Rules + +1. Extend the control plane, not the core loop. +2. Keep one native owner for each capability. +3. Make discovery metadata-only. +4. Separate install, permission grant, trust, and enable. +5. Report partial compatibility honestly. +6. Keep market metadata independent from runtime execution. +7. Roll back all registrations owned by a failed or unloaded extension. diff --git a/docs/extension-system.md b/docs/extension-system.md index e5f700692..682fd8635 100644 --- a/docs/extension-system.md +++ b/docs/extension-system.md @@ -1,5 +1,9 @@ # Extension system +This page documents the maintainer-facing architecture. For installation and +operations, read [Extensions](./extensions.md). For the package contract, read +[Extension Authoring](./extension-authoring.md). + nanobot treats an extension as an installable, governable unit and a contribution as one capability supplied by that unit. This distinction keeps the agent core small without forcing tools, channels, providers, skills, MCP @@ -77,6 +81,10 @@ Compatibility is capability-based rather than all-or-nothing: - Plugin failures are isolated from the agent process and produce actionable diagnostics. +The compatibility sidecar is a failure-isolation boundary, not a security +sandbox. The exact executable and metadata-only surfaces are listed in the +[compatibility matrix](./extension-authoring.md#compatibility-matrix). + ## Security model Extensions are trusted code, not prompts or static skills. Installation and @@ -99,6 +107,16 @@ per-extension enablement, package-owned config, and workspace trust. Discovery does not import extension code. Installation does not imply workspace trust, and activation does not rewrite `config.json` behind the user's back. +The activation gates are deliberately independent: + +```text +installed -> dependencies ready -> permissions granted -> trusted + enabled +``` + +Only candidates that pass every gate own active contributions. Reload first +rolls back registrations by extension owner and then activates the new +snapshot. A failed activation is converted into a diagnostic. + ## Market boundary The market is an index, not a runtime. It describes packages available from @@ -107,3 +125,35 @@ shape. Installing a listing still goes through the local installer, policy, dependency checks, and trust flow. This keeps discovery independent from code execution and allows multiple catalogs without coupling the agent to one store. + +## Ownership boundaries + +The extension package owns identity, policy, and contribution declarations. +Native subsystems continue to own execution: + +| Concern | Owner | +|---|---| +| Package identity, source, trust, permissions | `nanobot.extensions` | +| Tool execution contract | `nanobot.agent.tools` | +| Commands | `nanobot.command` | +| Agent lifecycle hooks | `nanobot.agent.hook` | +| Providers | `nanobot.providers` | +| Channels | `nanobot.channels` | +| Skills | `nanobot.skills` | +| Browser management surface | `webui` | + +Do not add extension discovery or compatibility branching to `AgentLoop`. +Runtime assembly creates an `ExtensionHost`, projects supported registrations +through native APIs, and closes the host with the surrounding runtime. + +## Protocol boundary + +Pi and OpenClaw entries run behind a versioned NDJSON request/response protocol. +The Python process sends load, call, lifecycle event, and close requests. The +Node process returns registrations, results, outputs, and diagnostics. Protocol +messages contain JSON-compatible values only. + +The adapter must reject malformed messages, time out stalled requests, and +close the sidecar when activation fails. Unsupported upstream methods are +either explicit no-ops with diagnostics or rejected; they must never be +reported as executable contributions. diff --git a/docs/extensions.md b/docs/extensions.md new file mode 100644 index 000000000..96ee97359 --- /dev/null +++ b/docs/extensions.md @@ -0,0 +1,189 @@ +# Extensions + +Extensions add capabilities to nanobot without modifying the agent loop. One +extension package can contribute tools, commands, hooks, skills, channels, +providers, MCP servers, or WebUI surfaces. nanobot also recognizes compatible +Pi packages and OpenClaw plugins, with capability-by-capability diagnostics. + +Use this page to install and manage extensions. To publish one, read +[Extension Authoring](./extension-authoring.md). For the internal design, read +[Extension System](./extension-system.md). + +## Install Safely + +The WebUI **Extensions** page and the `nanobot extensions` commands use the same +local extension store. The safe lifecycle is: + +1. Find or install a package. +2. Inspect its source, runtime, dependencies, requested permissions, and + diagnostics. +3. Grant only the permissions you understand. +4. Mark the package trusted. +5. Enable it. + +Installation does not grant trust. An installed package remains visible but +inactive until it is enabled, trusted, has all requested permissions, and +passes dependency checks. + +### WebUI + +Open **Extensions** in the sidebar: + +- **Installed** manages packages in `~/.nanobot/extensions/`. +- **Discover** searches npm packages marked for nanobot, Pi, or OpenClaw. +- **Built in** shows native nanobot capabilities projected into the same + catalog. Built-in entries are informational and are not uninstallable. + +Select an entry to see its contributions, dependencies, permission reasons, +source, compatibility notices, and activation errors. Trust and permission +controls are intentionally separate. + +### CLI + +```bash +# Search all supported npm ecosystems +nanobot extensions search "memory" + +# Search one compatibility ecosystem +nanobot extensions search "review" --ecosystem pi + +# Install without trusting the package +nanobot extensions install @scope/package + +# Inspect before activation +nanobot extensions inspect pi.scope.package + +# Grant exactly the requested host permissions +nanobot extensions permissions pi.scope.package network.http workspace.read + +# Trust and enable +nanobot extensions trust pi.scope.package +nanobot extensions enable pi.scope.package +``` + +Install from Git or a local directory when the package is not published: + +```bash +nanobot extensions install https://github.com/acme/nanobot-tool.git --kind git +nanobot extensions install ./my-extension --kind local +``` + +Local-directory installation is available only to local WebUI requests. Remote +browser clients cannot ask the gateway to read an arbitrary server path. + +## Sources and Scopes + +Extensions can come from three scopes: + +| Scope | Typical source | Behavior | +|---|---|---| +| Built in | nanobot package | Trusted by construction; shown for ownership and diagnostics | +| User | `~/.nanobot/extensions/` | Installed and governed through the WebUI or CLI | +| Workspace | `/.nanobot/extensions/` | Project-local code; controlled by workspace trust policy | + +When the same extension ID exists in multiple scopes, the nearest scope wins: +workspace over user, user over built in. A contribution cannot silently replace +another extension's contribution. Explicit replacement metadata and sufficient +scope are required. + +## Pi and OpenClaw Packages + +nanobot reads native Pi and OpenClaw package metadata and runs supported +JavaScript or TypeScript entries in a Node.js sidecar. Compatibility is not +all-or-nothing: + +- Tools, slash commands, and supported lifecycle observation hooks can run. +- Provider-like registrations and several host-specific capabilities may be + cataloged but not executable. +- Terminal UI, session-tree, renderer, shortcut, and model-control APIs do not + have equivalent nanobot host surfaces. +- Every degraded or unsupported registration appears in diagnostics. + +Check the [compatibility matrix](./extension-authoring.md#compatibility-matrix) +before depending on a package. A package appearing in search results means its +metadata is recognizable, not that every upstream API is implemented. + +## Trust and Permissions + +An extension is executable code. Review it with the same care as a Python or npm +dependency. + +- **Enabled** says the extension may activate. +- **Trusted** says you approve executing its code. +- **Granted permissions** are exact host capabilities approved for that + extension. +- **Dependencies** must be present before activation. + +Permissions are host policy, not an operating-system sandbox. A trusted native +Python extension executes in the nanobot process. A Pi or OpenClaw extension +executes in a separate Node.js process, which improves failure isolation but is +not a strong OS security boundary. Use containers or another OS sandbox for +untrusted third-party code. + +npm installation uses lifecycle scripts disabled. This prevents package +`preinstall` and `postinstall` scripts from running during installation, but the +extension entry itself will run after you explicitly trust and enable it. + +## Configuration + +Most users should manage installed packages in the WebUI or CLI. Advanced +deployments can also define extension policy in `~/.nanobot/config.json`: + +```json +{ + "extensions": { + "enabled": true, + "paths": ["/opt/nanobot/extensions"], + "allow": [], + "deny": ["acme.blocked"], + "workspaceTrust": "ask", + "entries": { + "acme.review": { + "enabled": true, + "trusted": true, + "permissions": ["workspace.read"], + "config": { + "mode": "strict" + } + } + } + } +} +``` + +Config entries do not rewrite the installation registry. See +[Configuration](./configuration.md#extensions) for exact fields. + +## Diagnose an Inactive Extension + +Start with: + +```bash +nanobot extensions list +nanobot extensions inspect +``` + +Common causes: + +| State or diagnostic | What to do | +|---|---| +| Untrusted | Review the package, then use `trust` | +| Requested permission pending | Grant the exact requested permission set | +| Disabled | Use `enable` or remove it from `extensions.deny` | +| Missing dependency | Install the named package, executable, environment variable, or extension | +| Contribution conflict | Disable one owner or use an explicit replacement from an appropriate scope | +| Compatibility notice | Read which upstream API was translated, degraded, or unsupported | +| Activation failed | Check the package entry, runtime dependency, and gateway logs | + +Policy changes reload the active extension host. A broken extension becomes a +diagnostic and does not prevent unrelated extensions from being discovered. + +## Remove an Extension + +```bash +nanobot extensions disable +nanobot extensions uninstall +``` + +Uninstall removes the user-scope package and its local policy record. It does +not remove a built-in capability or a separately configured workspace copy.