docs: add 11 new documentation files across all categories
New reference docs: - agents/ref-btmsg.md: inter-agent messaging schema and CLI - agents/ref-bttask.md: kanban task board operations - providers/ref-providers.md: Claude/Codex/Ollama/Aider comparison - config/ref-settings.md: (already committed) New guides: - contributing/dual-repo-workflow.md: community vs commercial repos - plugins/guide-developing.md: Web Worker sandbox API and publishing New pro docs: - pro/features/knowledge-base.md: persistent memory + symbol graph - pro/features/git-integration.md: context injection + branch policy - pro/marketplace/README.md: 13 plugins catalog Split files: - architecture/data-model.md: from architecture.md (schemas, layout) - production/hardening.md: from production.md (supervisor, sandbox, WAL) - production/features.md: from production.md (FTS5, plugins, secrets, audit)
This commit is contained in:
parent
8251321dac
commit
b6c1d4b6af
11 changed files with 2198 additions and 0 deletions
283
docs/plugins/guide-developing.md
Normal file
283
docs/plugins/guide-developing.md
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
# Plugin Development Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Agents Orchestrator plugins are self-contained bundles that run in a sandboxed Web Worker. A plugin consists of a manifest file (`plugin.json`) and an entry point script (`index.js`). Plugins interact with the host application through a message-passing API gated by declared permissions.
|
||||
|
||||
## Plugin Anatomy
|
||||
|
||||
A minimal plugin directory:
|
||||
|
||||
```
|
||||
~/.config/bterminal/plugins/my-plugin/
|
||||
plugin.json -- Manifest (required)
|
||||
index.js -- Entry point (required)
|
||||
```
|
||||
|
||||
### Manifest: plugin.json
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-plugin",
|
||||
"name": "My Plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "A brief description of what this plugin does.",
|
||||
"author": "Your Name",
|
||||
"entry": "index.js",
|
||||
"permissions": [
|
||||
"notifications",
|
||||
"tasks"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | `string` | Unique identifier (lowercase, hyphens allowed) |
|
||||
| `name` | `string` | Human-readable display name |
|
||||
| `version` | `string` | Semver version string |
|
||||
| `description` | `string` | Short description (under 200 characters) |
|
||||
| `entry` | `string` | Relative path to entry point script |
|
||||
| `permissions` | `string[]` | List of API permissions the plugin requires |
|
||||
|
||||
**Optional fields:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `author` | `string` | Plugin author name |
|
||||
| `homepage` | `string` | URL to plugin documentation |
|
||||
| `minVersion` | `string` | Minimum Agents Orchestrator version required |
|
||||
|
||||
### Entry Point: index.js
|
||||
|
||||
The entry point is loaded as a Web Worker module. The `agor` global object provides the plugin API.
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
const { meta, notifications } = agor;
|
||||
|
||||
console.log(`${meta.id} v${meta.version} loaded`);
|
||||
|
||||
notifications.send({
|
||||
title: meta.name,
|
||||
body: 'Plugin activated.',
|
||||
type: 'info',
|
||||
});
|
||||
```
|
||||
|
||||
## Web Worker Sandbox
|
||||
|
||||
Plugins run in an isolated Web Worker context. The sandbox enforces strict boundaries:
|
||||
|
||||
**Not available:**
|
||||
|
||||
- Filesystem access (no `fs`, no `Deno.readFile`, no `fetch` to `file://`)
|
||||
- Network access (no `fetch`, no `XMLHttpRequest`, no WebSocket)
|
||||
- DOM access (no `document`, no `window`)
|
||||
- Tauri IPC (no `invoke`, no event listeners)
|
||||
- Dynamic imports (no `import()`, no `importScripts()`)
|
||||
|
||||
**Available:**
|
||||
|
||||
- Standard JavaScript built-ins (`JSON`, `Map`, `Set`, `Promise`, `Date`, etc.)
|
||||
- `console.log/warn/error` (routed to host debug output)
|
||||
- `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`
|
||||
- The `agor` API object (permission-gated)
|
||||
|
||||
## Plugin API
|
||||
|
||||
All API methods are asynchronous and return Promises. Each API namespace requires a corresponding permission declared in `plugin.json`.
|
||||
|
||||
### agor.meta
|
||||
|
||||
Always available (no permission required).
|
||||
|
||||
```typescript
|
||||
interface PluginMeta {
|
||||
id: string; // Plugin ID from manifest
|
||||
version: string; // Plugin version from manifest
|
||||
name: string; // Plugin display name
|
||||
}
|
||||
```
|
||||
|
||||
### agor.palette
|
||||
|
||||
**Permission:** `"palette"`
|
||||
|
||||
Register commands in the application command palette.
|
||||
|
||||
```typescript
|
||||
// Register a command
|
||||
agor.palette.register({
|
||||
id: 'my-plugin.greet',
|
||||
label: 'My Plugin: Say Hello',
|
||||
callback: () => {
|
||||
agor.notifications.send({
|
||||
title: 'Hello',
|
||||
body: 'Greetings from the plugin.',
|
||||
type: 'info',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Unregister a command
|
||||
agor.palette.unregister('my-plugin.greet');
|
||||
```
|
||||
|
||||
### agor.messages
|
||||
|
||||
**Permission:** `"messages"` -- Read agent messages for the active session (read-only).
|
||||
|
||||
- `agor.messages.list(sessionId)` -- returns `Array<{ role, content, timestamp }>`.
|
||||
- `agor.messages.count(sessionId)` -- returns message count.
|
||||
|
||||
### agor.tasks
|
||||
|
||||
**Permission:** `"tasks"` -- Read/write task board entries (see Tasks-as-KV below).
|
||||
|
||||
- `agor.tasks.create({ title, description, status })` -- returns task ID.
|
||||
- `agor.tasks.list({ status?, limit? })` -- returns task array.
|
||||
- `agor.tasks.updateStatus(taskId, status)` -- updates status.
|
||||
- `agor.tasks.delete(taskId)` -- removes task.
|
||||
|
||||
### agor.events
|
||||
|
||||
**Permission:** `"events"` -- Subscribe to application events.
|
||||
|
||||
- `agor.events.on(eventName, callback)` -- returns unsubscribe function.
|
||||
- Events: `agent:status`, `agent:message`, `agent:complete`, `agent:error`.
|
||||
- Each event payload includes `sessionId` plus event-specific fields.
|
||||
|
||||
### agor.notifications
|
||||
|
||||
**Permission:** `"notifications"` -- Send toast notifications.
|
||||
|
||||
- `agor.notifications.send({ title, body, type })` -- type: info | success | warning | error.
|
||||
|
||||
## Permission Model
|
||||
|
||||
Each API namespace is gated by a permission string. If a plugin calls an API it has not declared in its `permissions` array, the call is rejected with a `PermissionDenied` error.
|
||||
|
||||
| Permission | API Namespace | Access Level |
|
||||
|------------|---------------|-------------|
|
||||
| `palette` | `agor.palette` | Register/unregister commands |
|
||||
| `messages` | `agor.messages` | Read-only agent messages |
|
||||
| `tasks` | `agor.tasks` | Read/write tasks |
|
||||
| `events` | `agor.events` | Subscribe to events |
|
||||
| `notifications` | `agor.notifications` | Send toast notifications |
|
||||
|
||||
Permissions are displayed to the user during plugin installation. Users must accept the permission list to proceed.
|
||||
|
||||
## Tasks-as-KV Pattern
|
||||
|
||||
Plugins that need persistent key-value storage use the task system with prefixed keys. This avoids adding a separate storage layer.
|
||||
|
||||
### Convention
|
||||
|
||||
Task titles use the format `plugin:{plugin-id}:{key}`. The task description field holds the value (string, or JSON-serialized for structured data).
|
||||
|
||||
```javascript
|
||||
// Store a value
|
||||
await agor.tasks.create({
|
||||
title: `plugin:${agor.meta.id}:last-run`,
|
||||
description: JSON.stringify({ timestamp: Date.now(), count: 42 }),
|
||||
status: 'done',
|
||||
});
|
||||
|
||||
// Retrieve a value
|
||||
const tasks = await agor.tasks.list({ limit: 1 });
|
||||
const entry = tasks.find(t => t.title === `plugin:${agor.meta.id}:last-run`);
|
||||
const data = JSON.parse(entry.description);
|
||||
```
|
||||
|
||||
### LRU Cap
|
||||
|
||||
Plugins should limit their KV entries to avoid unbounded growth. Recommended cap: 100 entries per plugin. When creating a new entry that would exceed the cap, delete the oldest entry first.
|
||||
|
||||
### Purge
|
||||
|
||||
On plugin uninstall, all tasks with the `plugin:{plugin-id}:` prefix are automatically purged.
|
||||
|
||||
## Shared Utilities
|
||||
|
||||
Two helper functions are injected into the Web Worker global scope for common patterns:
|
||||
|
||||
### safeGet(obj, path, defaultValue)
|
||||
|
||||
Safe property access for nested objects. Avoids `TypeError` on undefined intermediate properties.
|
||||
|
||||
```javascript
|
||||
const value = safeGet(event, 'data.session.cost', 0);
|
||||
```
|
||||
|
||||
### safeMsg(template, ...args)
|
||||
|
||||
String interpolation with type coercion and truncation (max 500 characters per argument).
|
||||
|
||||
```javascript
|
||||
const msg = safeMsg('Session {} completed with {} turns', sessionId, turnCount);
|
||||
```
|
||||
|
||||
## Example Plugin: hello-world
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "hello-world",
|
||||
"name": "Hello World",
|
||||
"version": "1.0.0",
|
||||
"description": "Minimal example plugin that greets the user.",
|
||||
"entry": "index.js",
|
||||
"permissions": ["palette", "notifications"]
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
const { meta, palette, notifications } = agor;
|
||||
|
||||
palette.register({
|
||||
id: 'hello-world.greet',
|
||||
label: 'Hello World: Greet',
|
||||
callback: () => {
|
||||
notifications.send({
|
||||
title: 'Hello',
|
||||
body: `Greetings from ${meta.name} v${meta.version}.`,
|
||||
type: 'info',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
## Publishing
|
||||
|
||||
To publish a plugin to the marketplace:
|
||||
|
||||
1. Create a directory in the `agents-orchestrator/agor-plugins` repository under `plugins/{plugin-id}/`.
|
||||
2. Add the plugin files (`plugin.json`, `index.js`, and any supporting files).
|
||||
3. Create a `.tar.gz` archive of the plugin directory.
|
||||
4. Compute the SHA-256 checksum: `sha256sum my-plugin.tar.gz`.
|
||||
5. Add an entry to `catalog.json` with the download URL, checksum, and metadata.
|
||||
6. Submit a pull request to the `agor-plugins` repository.
|
||||
|
||||
The catalog maintainers review the plugin for security (no obfuscated code, reasonable permissions) and functionality before merging.
|
||||
|
||||
## Scaffolding
|
||||
|
||||
Use the scaffolding script to generate a new plugin skeleton:
|
||||
|
||||
```bash
|
||||
./scripts/plugin-init.sh my-plugin "My Plugin" "A description of my plugin"
|
||||
```
|
||||
|
||||
This creates:
|
||||
|
||||
```
|
||||
~/.config/bterminal/plugins/my-plugin/
|
||||
plugin.json -- Pre-filled manifest
|
||||
index.js -- Minimal entry point with palette command stub
|
||||
```
|
||||
|
||||
The script prompts for permissions to declare. Generated files include comments explaining each section.
|
||||
Loading…
Add table
Add a link
Reference in a new issue