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:
Hibryda 2026-03-17 04:18:05 +01:00
parent 8251321dac
commit b6c1d4b6af
11 changed files with 2198 additions and 0 deletions

186
docs/agents/ref-btmsg.md Normal file
View file

@ -0,0 +1,186 @@
# btmsg Reference
btmsg is the inter-agent messaging system for Agents Orchestrator. It enables
management agents (Tier 1) and project agents (Tier 2) to communicate via direct
messages and broadcast channels.
## Overview
btmsg uses a shared SQLite database at `~/.local/share/agor/btmsg.db`. Both the
Python CLI tools (used by agents) and the Rust backend (Tauri app) access the
same database concurrently. WAL mode and a 5-second busy timeout handle
contention.
Agents must register before sending or receiving messages. Registration creates
an entry in the `agents` table with the agent's ID, name, role, group, and tier.
## Database schema
### agents
| Column | Type | Description |
|--------|------|-------------|
| `id` | TEXT PK | Unique agent identifier |
| `name` | TEXT | Display name |
| `role` | TEXT | Agent role (manager, architect, tester, reviewer, or project) |
| `group_id` | TEXT | Group this agent belongs to |
| `tier` | INTEGER | 1 = management, 2 = project |
| `model` | TEXT | Model identifier (nullable) |
| `status` | TEXT | Current status (active, sleeping, stopped) |
### messages
| Column | Type | Description |
|--------|------|-------------|
| `id` | TEXT PK | UUID |
| `from_agent` | TEXT FK | Sender agent ID |
| `to_agent` | TEXT FK | Recipient agent ID |
| `content` | TEXT | Message body |
| `read` | INTEGER | 0 = unread, 1 = read |
| `reply_to` | TEXT | Parent message ID (nullable, for threading) |
| `sender_group_id` | TEXT | Sender's group ID (nullable, added by migration) |
| `created_at` | TEXT | ISO timestamp |
### channels
| Column | Type | Description |
|--------|------|-------------|
| `id` | TEXT PK | Channel identifier |
| `name` | TEXT | Channel name (e.g. `#review-queue`) |
| `group_id` | TEXT | Group this channel belongs to |
| `created_by` | TEXT FK | Agent that created the channel |
| `created_at` | TEXT | ISO timestamp |
### contacts
ACL table controlling which agents can see and message each other.
### heartbeats
Liveness tracking. Agents send periodic heartbeats; the health store uses these
to detect stalled agents.
### dead_letter_queue
Messages that could not be delivered (recipient not found, agent stopped).
Surfaced in the agent health monitoring UI.
### audit_log
Records agent actions for compliance and debugging. Entries include agent ID,
action type, target, and timestamp.
### seen_messages
Per-message read tracking with session-level granularity.
| Column | Type | Description |
|--------|------|-------------|
| `session_id` | TEXT | Reading session identifier |
| `message_id` | TEXT FK | Message that was seen |
| `seen_at` | INTEGER | Unix timestamp |
Primary key: `(session_id, message_id)`. This enables per-message acknowledgment
rather than bulk "mark all read" operations.
## CLI usage
The `btmsg` CLI is a Python script installed at `~/.local/bin/btmsg`. Agents
invoke it via shell commands in their sessions. The `BTMSG_AGENT_ID` environment
variable identifies the calling agent.
### Register an agent
```bash
btmsg register --id mgr-1 --name "Manager" --role manager \
--group my-team --tier 1
```
### Send a direct message
```bash
btmsg send --to architect-1 --content "Review the auth module architecture"
```
### Send to a channel
```bash
btmsg channel-post --channel review-queue \
--content "Task T-42 moved to review"
```
### Read unread messages
```bash
btmsg read
```
Returns all unread messages for the agent identified by `BTMSG_AGENT_ID`.
### List channels
```bash
btmsg channels
```
### Send a heartbeat
```bash
btmsg heartbeat
```
Updates the agent's liveness timestamp. The health store marks agents as stalled
if no heartbeat arrives within the configured `stallThresholdMin`.
### Mark messages as seen
```bash
btmsg ack --message-id <uuid>
```
Records an entry in `seen_messages` for per-message tracking.
## Role permissions
Agent capabilities depend on their role:
| Capability | Manager | Architect | Tester | Reviewer | Project |
|------------|---------|-----------|--------|----------|---------|
| Send DMs | Yes | Yes | Yes | Yes | Yes |
| Read own DMs | Yes | Yes | Yes | Yes | Yes |
| Post to channels | Yes | Yes | Yes | Yes | Yes |
| Create channels | Yes | No | No | No | No |
| Delete messages | Yes | No | No | No | No |
| List all agents | Yes | Yes | Yes | Yes | Read-only |
| Update agent status | Yes | No | No | No | No |
The Manager role has full CRUD on all btmsg resources. Other roles can read
messages addressed to them and post to channels.
## Rust backend integration
The Tauri backend reads btmsg data via `src-tauri/src/btmsg.rs`. Key functions:
- `get_agents(group_id)` -- List agents with unread counts
- `unread_count(agent_id)` -- Unread message count
- `unread_messages(agent_id)` -- Full unread message list with sender metadata
- `get_feed(group_id)` -- Recent messages across the group
- `get_channels(group_id)` -- List channels with member counts
- `get_channel_messages(channel_id)` -- Messages in a channel
All queries use named column access (`row.get("column_name")`) and return
`#[serde(rename_all = "camelCase")]` structs for direct JSON serialization to the
frontend.
## Frontend
The frontend accesses btmsg through `src/lib/adapters/btmsg-bridge.ts`, which
wraps Tauri IPC commands. The CommsTab component in ProjectBox displays messages
and channels for management agents.
## Review queue integration
When a task transitions to `review` status via bttask, the system auto-posts a
notification to the `#review-queue` channel. The `ensure_review_channels`
function creates `#review-queue` and `#review-log` idempotently. See
[bttask reference](ref-bttask.md) for details.

161
docs/agents/ref-bttask.md Normal file
View file

@ -0,0 +1,161 @@
# bttask Reference
bttask is the kanban task board for Agents Orchestrator. It provides structured
task tracking for multi-agent workflows, with optimistic locking to prevent
concurrent update conflicts.
## Overview
Tasks are stored in the shared `btmsg.db` SQLite database at
`~/.local/share/agor/btmsg.db`. The `bttask` CLI (Python, installed at
`~/.local/bin/bttask`) gives agents direct access. The Rust backend
(`src-tauri/src/bttask.rs`) provides read/write access for the Tauri frontend.
## Task schema
| Column | Type | Description |
|--------|------|-------------|
| `id` | TEXT PK | UUID |
| `title` | TEXT | Short task title |
| `description` | TEXT | Detailed description |
| `status` | TEXT | Current column (see below) |
| `priority` | TEXT | `low`, `medium`, or `high` |
| `assigned_to` | TEXT | Agent ID (nullable) |
| `created_by` | TEXT | Agent ID that created the task |
| `group_id` | TEXT | Group this task belongs to |
| `parent_task_id` | TEXT | Parent task for subtasks (nullable) |
| `sort_order` | INTEGER | Display order within column |
| `version` | INTEGER | Optimistic locking version (default 1) |
| `created_at` | TEXT | ISO timestamp |
| `updated_at` | TEXT | ISO timestamp |
### Task comments
| Column | Type | Description |
|--------|------|-------------|
| `id` | TEXT PK | UUID |
| `task_id` | TEXT FK | Parent task |
| `agent_id` | TEXT | Commenting agent |
| `content` | TEXT | Comment body |
| `created_at` | TEXT | ISO timestamp |
## Kanban columns
Tasks move through 5 statuses:
```
backlog -> todo -> in_progress -> review -> done
```
The frontend (`TaskBoardTab.svelte`) renders these as columns in a kanban board,
polling every 5 seconds for updates.
## Operations
### list_tasks
Returns all tasks for a group, ordered by `sort_order` then `created_at DESC`.
### create_task
Creates a new task with the given title, description, priority, and optional
assignment. Returns the created task with its generated ID.
### update_task_status
Moves a task to a new status. Uses optimistic locking: the caller must provide
the current `version` value. If the version in the database does not match, the
update is rejected with a conflict error. On success, the version is
incremented.
```
update_task_status(task_id, new_status, expected_version)
```
### delete_task
Removes a task by ID.
### add_comment
Adds a comment to a task. Any agent can comment regardless of role.
### task_comments
Returns all comments for a task, ordered by `created_at ASC`.
### review_queue_count
Returns the number of tasks currently in `review` status for a group. Used by
the health store to calculate reviewer attention scoring (10 points per review
task, capped at 50).
## CLI usage
Agents interact with bttask through shell commands. The `BTMSG_AGENT_ID`
environment variable identifies the calling agent.
```bash
# List all tasks
bttask list
# Create a task
bttask create --title "Fix auth bug" --priority high \
--description "Token refresh fails after 24h"
# Move a task to in_progress
bttask status --id T-42 --status in_progress --version 1
# Add a comment
bttask comment --id T-42 --content "Root cause identified: expired refresh token"
# Delete a task
bttask delete --id T-42
```
## Role permissions
| Capability | Manager | Reviewer | Others |
|------------|---------|----------|--------|
| List tasks | Yes | Yes | Yes |
| Create tasks | Yes | No | No |
| Update status | Yes | Yes | Read-only |
| Delete tasks | Yes | No | No |
| Add comments | Yes | Yes | Yes |
The Manager role has full CRUD. The Reviewer can change task status (to move
items through the review pipeline) and add comments. Other roles have read-only
access to tasks but can add comments.
## Review queue integration
When a task moves to `review` status, the system auto-posts a notification to
the `#review-queue` btmsg channel. This triggers the Reviewer agent's attention
scoring. The `ensure_review_channels` function creates `#review-queue` and
`#review-log` channels idempotently on first use.
The `review_queue_count` is polled every 10 seconds by ProjectBox for reviewer
agents and fed into the health store's `setReviewQueueDepth()` for attention
scoring. Review tasks contribute to the reviewer's attention score:
- 10 points per task in review status
- Capped at 50 points total
- Priority rank: between `file_conflict` (70) and `context_high` (40)
## Optimistic locking
Every task has a `version` field starting at 1. When an agent updates a task's
status, it must pass the expected version. If another agent modified the task
since it was read, the version will not match and the update fails with a
conflict error. The calling agent should re-read the task and retry.
This prevents race conditions when multiple agents attempt to move the same task
simultaneously.
## Frontend
The kanban board is rendered by `src/lib/components/Workspace/TaskBoardTab.svelte`,
available in the Tasks tab for Manager-role agents. It polls `list_tasks` every
5 seconds and supports drag-style status transitions between columns.
IPC adapter: `src/lib/adapters/bttask-bridge.ts`.

View file

@ -0,0 +1,251 @@
# Data Model & Layout System
This document covers agor's data model (SQLite schemas, project group config), layout system, xterm budget management, and keyboard shortcuts.
---
## SQLite Databases
The backend manages two SQLite databases, both in WAL mode with 5-second busy timeout for concurrent access:
| Database | Location | Purpose |
|----------|----------|---------|
| `sessions.db` | `~/.local/share/agor/` | Sessions, layout, settings, agent state, metrics, anchors |
| `btmsg.db` | `~/.local/share/agor/` | Inter-agent messages, tasks, agents registry, audit log |
WAL checkpoints run every 5 minutes via a background tokio task to prevent unbounded WAL growth.
All queries use **named column access** (`row.get("column_name")`) — never positional indices. Rust structs use `#[serde(rename_all = "camelCase")]` so TypeScript interfaces receive camelCase field names on the wire.
---
## Project Group Config (`~/.config/agor/groups.json`)
Human-editable JSON file defining workspaces. Each group contains up to 5 projects. Loaded at startup by `groups.rs`, not hot-reloaded.
```jsonc
{
"version": 1,
"groups": [
{
"id": "work-ai",
"name": "AI Projects",
"projects": [
{
"id": "agor",
"name": "Agents Orchestrator",
"identifier": "agor",
"description": "Terminal emulator with Claude integration",
"icon": "\uf120",
"cwd": "/home/user/code/Agents Orchestrator",
"profile": "default",
"enabled": true
}
]
}
],
"activeGroupId": "work-ai"
}
```
### TypeScript Types (`src/lib/types/groups.ts`)
```typescript
export interface ProjectConfig {
id: string;
name: string;
identifier: string;
description: string;
icon: string;
cwd: string;
profile: string;
enabled: boolean;
}
export interface GroupConfig {
id: string;
name: string;
projects: ProjectConfig[]; // max 5
}
export interface GroupsFile {
version: number;
groups: GroupConfig[];
activeGroupId: string;
}
```
---
## SQLite Schema (v3 Additions)
Beyond the core `sessions` and `settings` tables, v3 added project-scoped agent persistence:
```sql
ALTER TABLE sessions ADD COLUMN project_id TEXT DEFAULT '';
CREATE TABLE IF NOT EXISTS agent_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
project_id TEXT NOT NULL,
sdk_session_id TEXT,
message_type TEXT NOT NULL,
content TEXT NOT NULL,
parent_id TEXT,
created_at INTEGER NOT NULL,
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS project_agent_state (
project_id TEXT PRIMARY KEY,
last_session_id TEXT NOT NULL,
sdk_session_id TEXT,
status TEXT NOT NULL,
cost_usd REAL DEFAULT 0,
input_tokens INTEGER DEFAULT 0,
output_tokens INTEGER DEFAULT 0,
last_prompt TEXT,
updated_at INTEGER NOT NULL
);
```
---
## Layout System
### Project Grid (Flexbox + scroll-snap)
Projects are arranged horizontally in a flex container with CSS scroll-snap for clean project-to-project scrolling:
```css
.project-grid {
display: flex;
gap: 4px;
height: 100%;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.project-box {
flex: 0 0 calc((100% - (N-1) * 4px) / N);
scroll-snap-align: start;
min-width: 480px;
}
```
N is computed from viewport width: `Math.min(projects.length, Math.max(1, Math.floor(containerWidth / 520)))`
### Project Box Internal Layout
Each project box uses a CSS grid with 4 rows:
```
+-- ProjectHeader (auto) --------------------+
+---------------------+---------------------+
| AgentSession | TeamAgentsPanel |
| (flex: 1) | (240px/overlay) |
+---------------------+---------------------+
| [Tab1] [Tab2] [+] TabBar auto |
+--------------------------------------------+
| Terminal content (xterm or scrollback) |
+--------------------------------------------+
```
Team panel: inline at >2560px viewport (240px wide), overlay at <2560px. Collapsed when no subagents running.
### Responsive Breakpoints
| Viewport Width | Visible Projects | Team Panel Mode |
|---------------|-----------------|-----------------|
| 5120px+ | 5 | inline 240px |
| 3840px | 4 | inline 200px |
| 2560px | 3 | overlay |
| 1920px | 3 | overlay |
| <1600px | 1 + project tabs | overlay |
---
## xterm.js Budget: 4 Active Instances
WebKit2GTK OOMs at ~5 simultaneous xterm.js instances. The budget system manages this:
| State | xterm.js Instance? | Memory |
|-------|--------------------|--------|
| Active-Focused | Yes | ~20MB |
| Active-Background | Yes (if budget allows) | ~20MB |
| Suspended | No (HTML pre scrollback) | ~200KB |
| Uninitialized | No (placeholder) | 0 |
On focus: serialize least-recent xterm scrollback, destroy it, create new for focused tab, reconnect PTY. Suspend/resume cycle < 50ms.
### Project Accent Colors
Each project slot gets a distinct Catppuccin accent color for visual distinction:
| Slot | Color | CSS Variable |
|------|-------|-------------|
| 1 | Blue | `var(--ctp-blue)` |
| 2 | Green | `var(--ctp-green)` |
| 3 | Mauve | `var(--ctp-mauve)` |
| 4 | Peach | `var(--ctp-peach)` |
| 5 | Pink | `var(--ctp-pink)` |
Applied to border tint and header accent via `var(--accent)` CSS custom property set per ProjectBox.
---
## Adapters (IPC Bridge Layer)
Adapters wrap Tauri `invoke()` calls and `listen()` event subscriptions. They isolate the frontend from IPC details and provide typed TypeScript interfaces.
| Adapter | Backend Module | Purpose |
|---------|---------------|---------|
| `agent-bridge.ts` | sidecar + commands/agent | Agent query/stop/restart |
| `pty-bridge.ts` | pty + commands/pty | Terminal spawn/write/resize |
| `claude-messages.ts` | (frontend-only) | Parse Claude SDK NDJSON -> AgentMessage |
| `codex-messages.ts` | (frontend-only) | Parse Codex ThreadEvents -> AgentMessage |
| `ollama-messages.ts` | (frontend-only) | Parse Ollama chunks -> AgentMessage |
| `message-adapters.ts` | (frontend-only) | Provider registry for message parsers |
| `provider-bridge.ts` | commands/claude | Generic provider bridge (profiles, skills) |
| `btmsg-bridge.ts` | btmsg | Inter-agent messaging |
| `bttask-bridge.ts` | bttask | Task board operations |
| `groups-bridge.ts` | groups | Group config load/save |
| `session-bridge.ts` | session | Session/layout persistence |
| `settings-bridge.ts` | session/settings | Key-value settings |
| `files-bridge.ts` | commands/files | File browser operations |
| `search-bridge.ts` | search | FTS5 search |
| `secrets-bridge.ts` | secrets | System keyring |
| `anchors-bridge.ts` | session/anchors | Session anchor CRUD |
| `remote-bridge.ts` | remote | Remote machine management |
| `ssh-bridge.ts` | session/ssh | SSH session CRUD |
| `ctx-bridge.ts` | ctx | Context database queries |
| `memora-bridge.ts` | memora | Memora database queries |
| `fs-watcher-bridge.ts` | fs_watcher | Filesystem change events |
| `audit-bridge.ts` | btmsg (audit_log) | Audit log queries |
| `telemetry-bridge.ts` | telemetry | Frontend -> Rust tracing |
| `notifications-bridge.ts` | notifications | Desktop notification trigger |
| `plugins-bridge.ts` | plugins | Plugin discovery |
---
## Keyboard Shortcuts
Three-layer shortcut system prevents conflicts between terminal input, workspace navigation, and app-level commands:
| Shortcut | Action | Layer |
|----------|--------|-------|
| Ctrl+K | Command palette | App |
| Ctrl+G | Switch group (palette filtered) | App |
| Ctrl+1..5 | Focus project by index | App |
| Alt+1..4 | Switch sidebar tab + open drawer | App |
| Ctrl+B | Toggle sidebar open/closed | App |
| Ctrl+, | Toggle settings panel | App |
| Escape | Close sidebar drawer | App |
| Ctrl+Shift+F | FTS5 search overlay | App |
| Ctrl+N | New terminal in focused project | Workspace |
| Ctrl+Shift+N | New agent query | Workspace |
| Ctrl+Tab | Next terminal tab | Project |
| Ctrl+W | Close terminal tab | Project |
| Ctrl+Shift+C/V | Copy/paste in terminal | Terminal |
Terminal layer captures raw keys only when focused. App layer has highest priority.

View file

@ -0,0 +1,170 @@
# Dual-Repo Workflow
Agents Orchestrator uses a dual-repository model to maintain an open-source
community edition alongside a private commercial edition.
## Repositories
| Remote | Repository | Visibility | Purpose |
|--------|-----------|------------|---------|
| `origin` | DexterFromLab/agent-orchestrator | Public (MIT) | Community edition |
| `orchestrator` | agents-orchestrator/agents-orchestrator | Private | Commercial edition |
Both remotes are configured in every developer clone:
```
$ git remote -v
orchestrator git@github.com:agents-orchestrator/agents-orchestrator.git (fetch)
orchestrator git@github.com:agents-orchestrator/agents-orchestrator.git (push)
origin git@github.com:DexterFromLab/agent-orchestrator.git (fetch)
origin no-push-to-community (push)
```
Note: `origin` push URL is set to `no-push-to-community` -- a non-existent URL
that causes `git push origin` to fail. This is intentional. All pushes go to
`orchestrator`.
## Development flow
All daily development happens on the `orchestrator` remote. The community
edition receives curated exports stripped of commercial code.
```
Developer -> orchestrator (private) -> curated export -> origin (public)
```
### Branch model
| Branch | Scope | Description |
|--------|-------|-------------|
| `main` | Shared | Community-safe code. Synced between both remotes. |
| `hib_changes_v2` | Development | Active development branch on orchestrator. |
| `commercial/*` | Pro-only | Features exclusive to the commercial edition. |
### Typical workflow
1. Create a feature branch from `main` on `orchestrator`.
2. Develop and test.
3. Push to `orchestrator` and open a PR against `main`.
4. After merge, community sync happens via curated export.
## Syncing from community
To pull community contributions into the private repo:
```bash
make sync
```
This runs:
```bash
git fetch origin
git merge origin/main
```
Resolve any conflicts between community contributions and commercial code.
## Leak prevention
Three layers prevent commercial code from reaching the public repository.
### 1. Git pre-push hook
`.githooks/pre-push` scans commits being pushed to any remote matching
`DexterFromLab`. If any commit touches files in `agor-pro/` or
`src/lib/commercial/`, the push is blocked:
```
==========================================
PUSH BLOCKED: Commercial code detected!
==========================================
The following commercial files were found in commits being pushed:
- agor-pro/src/billing.rs
- src/lib/commercial/license.ts
You are pushing to the community remote (git@github.com:DexterFromLab/...).
Commercial code must NOT be pushed to this remote.
==========================================
```
Enable the hook after cloning:
```bash
make setup
# or manually:
git config core.hooksPath .githooks
```
### 2. CI leak check
`.github/workflows/leak-check.yml` runs on every push and PR to `main` on the
community repo. It fails the build if:
- `agor-pro/` directory exists
- `src/lib/commercial/` contains files beyond `.gitkeep`
- Any file contains `SPDX-License-Identifier: LicenseRef-Agor-Commercial`
### 3. SPDX headers
Commercial files carry an SPDX header identifying them:
```rust
// SPDX-License-Identifier: LicenseRef-Agor-Commercial
```
The CI leak check scans for this marker as a final safety net.
## File conventions
| Path | Edition | Description |
|------|---------|-------------|
| `agor-pro/` | Commercial | Pro Rust crate (billing, licensing, accounts) |
| `src/lib/commercial/` | Commercial | Pro frontend components |
| `src/lib/commercial/.gitkeep` | Community | Placeholder (no content) |
| Everything else | Community | MIT-licensed code |
Commercial code is conditionally compiled:
- **Rust:** `#[cfg(feature = "pro")]` gates, Cargo feature flag
- **Frontend:** `AGOR_EDITION` env var checked at test/build time
## Makefile targets
```bash
make setup # Configure git hooks + npm install
make build # Community build (cargo build + npm run build)
make build-pro # Commercial build (cargo build --features pro)
make test # Run all community tests (npm run test:all)
make test-pro # Run commercial tests (cargo test --features pro + vitest)
make sync # Pull community changes (git fetch origin + merge)
make clean # Remove build artifacts (cargo clean + vite cache)
```
### Building the commercial Tauri app
```bash
cargo tauri build -- --features pro \
--config src-tauri/tauri.conf.commercial.json
```
This merges the commercial Tauri config (different bundle ID, product name,
updater URL) with the base config and enables the `pro` Cargo feature.
## Adding commercial features
1. Place Rust code in `agor-pro/src/` or behind `#[cfg(feature = "pro")]`.
2. Place frontend code in `src/lib/commercial/`.
3. Add SPDX header to every new file.
4. Test with `make test-pro`.
5. Push to `orchestrator` only. Never push to `origin`.
## Removing commercial code for export
When preparing a community release:
1. Ensure `main` has no commercial file paths in its history.
2. The `no-push-to-community` push URL and pre-push hook prevent accidents.
3. If a commercial file is accidentally committed to `main`, rewrite history
before any push to `origin`. Rotate any exposed secrets.

View 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.

View file

@ -0,0 +1,196 @@
# Git Integration
> This documentation covers Pro edition features available in the agents-orchestrator/agents-orchestrator private repository.
Git Integration provides two features: Git Context Injection (branch, commit, and diff information formatted for agent prompts) and Branch Policy (session-level protection for sensitive branches).
---
## Git Context Injection
### Purpose
Git Context Injection gathers repository state and formats it as markdown for inclusion in agent system prompts. This gives agents awareness of the current branch, recent commits, and modified files without requiring them to run git commands.
### Context Gathering
The system collects three categories of information by invoking the `git` CLI:
#### Branch Information
- Current branch name (`git rev-parse --abbrev-ref HEAD`)
- Tracking branch and ahead/behind counts (`git rev-list --left-right --count`)
- Last commit on branch (hash, author, date, subject)
#### Recent Commits
- Last N commits on the current branch (default: 10)
- Each commit includes: short hash, author, relative date, subject line
- Collected via `git log --oneline --format`
#### Modified Files
- Staged files (`git diff --cached --name-status`)
- Unstaged modifications (`git diff --name-status`)
- Untracked files (`git ls-files --others --exclude-standard`)
### Formatted Output
The collected information is formatted as a markdown section:
```markdown
## Git Context
**Branch:** feature/new-dashboard (ahead 3, behind 0 of origin/main)
### Recent Commits (last 10)
- `a1b2c3d` (2 hours ago) fix: resolve null check in analytics
- `e4f5g6h` (5 hours ago) feat: add daily cost chart
- ...
### Working Tree
**Staged:**
- M src/lib/components/Analytics.svelte
- A src/lib/stores/analytics.svelte.ts
**Modified:**
- M src/lib/adapters/pro-bridge.ts
**Untracked:**
- tests/analytics.test.ts
```
### Commands
#### pro_git_context
Gathers all git context for a project directory and returns formatted markdown.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cwd` | `String` | Yes | Absolute path to the git repository |
| `commitCount` | `u32` | No | Number of recent commits to include (default: 10) |
**Response:**
```typescript
interface GitContext {
markdown: string; // Formatted markdown section
branch: string; // Current branch name
isDirty: boolean; // Has uncommitted changes
aheadBehind: [number, number]; // [ahead, behind]
}
```
#### pro_git_inject
Convenience command that gathers git context and prepends it to a given system prompt.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cwd` | `String` | Yes | Repository path |
| `systemPrompt` | `String` | Yes | Existing system prompt to augment |
| `commitCount` | `u32` | No | Number of recent commits (default: 10) |
Returns the combined prompt string.
#### pro_git_branch_info
Returns structured branch information without formatting.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cwd` | `String` | Yes | Repository path |
**Response:**
```typescript
interface BranchInfo {
name: string;
trackingBranch: string | null;
ahead: number;
behind: number;
lastCommitHash: string;
lastCommitSubject: string;
lastCommitDate: string;
}
```
### Implementation Notes
- All git commands are executed via `std::process::Command` (not libgit2). This avoids a heavy native dependency and matches the git CLI behavior users expect.
- Commands run with a 5-second timeout. If git is not installed or the directory is not a repository, commands return structured errors.
- Output encoding is handled as UTF-8 with lossy conversion for non-UTF-8 paths.
---
## Branch Policy
### Purpose
Branch Policy prevents agents from making commits or modifications on protected branches. This is a session-level safeguard -- the policy is checked when an agent session starts and when git operations are detected in tool calls.
### Protection Rules
Protected branch patterns are configurable per project. The defaults are:
| Pattern | Matches |
|---------|---------|
| `main` | Exact match |
| `master` | Exact match |
| `release/*` | Any branch starting with `release/` |
When an agent session starts on a protected branch, the system emits a warning notification. It does not block the session, because agents may need to read code on these branches. However, the branch name is included in the agent's system prompt with a clear instruction not to commit.
### Commands
#### pro_branch_check
Checks whether the current branch is protected.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cwd` | `String` | Yes | Repository path |
| `projectId` | `String` | Yes | Project identifier (for project-specific policies) |
**Response:**
```typescript
interface BranchCheckResult {
branch: string;
isProtected: boolean;
matchedPattern: string | null; // Which pattern matched
}
```
#### pro_branch_policy_set
Sets custom protected branch patterns for a project. Replaces any existing patterns.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `patterns` | `Vec<String>` | Yes | Branch patterns (exact names or glob with `*`) |
#### pro_branch_policy_get
Returns the current branch policy for a project. Returns default patterns if none are set.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
#### pro_branch_policy_delete
Removes custom branch policy for a project, reverting to defaults.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
### Integration
Branch policy is checked at two points:
1. **Session start:** `AgentSession.startQuery()` calls `pro_branch_check`. If protected, a warning toast is shown and the branch protection instruction is appended to the system prompt.
2. **System prompt injection:** The formatted git context (from `pro_git_inject`) includes a `PROTECTED BRANCH` warning banner when applicable.

View file

@ -0,0 +1,247 @@
# Knowledge Base
> This documentation covers Pro edition features available in the agents-orchestrator/agents-orchestrator private repository.
The Knowledge Base provides two complementary systems: Persistent Agent Memory (structured knowledge fragments with search and TTL) and the Codebase Symbol Graph (regex-based symbol extraction for code navigation context).
---
## Persistent Agent Memory
### Purpose
Persistent Agent Memory stores knowledge fragments that agents produce during sessions and makes them available in future sessions. Unlike session anchors (community feature, per-session), memory fragments persist across sessions and are searchable via FTS5.
### Memory Fragments
A memory fragment is a discrete piece of knowledge with metadata:
```typescript
interface MemoryFragment {
id: number;
projectId: string;
content: string; // The knowledge itself (plain text or markdown)
source: string; // Where it came from (session ID, file path, user)
trustTier: TrustTier; // agent | human | auto
tags: string[]; // Categorization tags
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
expiresAt: string | null; // ISO 8601, null = never expires
accessCount: number; // Times retrieved for injection
}
type TrustTier = 'agent' | 'human' | 'auto';
```
### Trust Tiers
| Tier | Source | Injection Priority | Editable |
|------|--------|-------------------|----------|
| `human` | Created or approved by user | Highest | Yes |
| `agent` | Extracted by agent during session | Medium | Yes |
| `auto` | Auto-extracted from patterns | Lowest | Yes |
When injecting memories into agent prompts, higher-trust memories are prioritized. Within the same tier, more recently accessed memories rank higher.
### TTL (Time-To-Live)
Memories can have an optional expiration date. Expired memories are excluded from search results and injection. A background cleanup runs on plugin init, deleting memories expired more than 30 days ago.
Default TTL by trust tier:
| Tier | Default TTL |
|------|-------------|
| `human` | None (permanent) |
| `agent` | 90 days |
| `auto` | 30 days |
Users can override TTL on any individual memory.
### Auto-Extraction
When an agent session completes, the dispatcher can trigger auto-extraction. The extractor scans the session transcript for:
- Explicit knowledge statements ("I learned that...", "Note:", "Important:")
- Error resolutions (error message followed by successful fix)
- Configuration discoveries (env vars, file paths, API endpoints)
Extracted fragments are created with `trustTier: 'auto'` and default TTL. The user can promote them to `agent` or `human` tier.
### Memory Injection
Before an agent session starts, the top-K most relevant memories are retrieved and formatted into a `## Project Knowledge` section in the system prompt. Relevance is determined by FTS5 rank score against the project context (project name, CWD, recent file paths).
Default K = 5. Configurable per project via `pro_memory_set_config`.
### SQLite Schema
In `agor_pro.db`:
```sql
CREATE TABLE pro_memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id TEXT NOT NULL,
content TEXT NOT NULL,
source TEXT NOT NULL,
trust_tier TEXT NOT NULL DEFAULT 'auto',
tags TEXT NOT NULL DEFAULT '[]', -- JSON array
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT,
access_count INTEGER NOT NULL DEFAULT 0
);
CREATE VIRTUAL TABLE pro_memories_fts USING fts5(
content,
tags,
content=pro_memories,
content_rowid=id
);
CREATE INDEX idx_pro_memories_project ON pro_memories(project_id);
CREATE INDEX idx_pro_memories_expires ON pro_memories(expires_at);
```
### Commands
#### pro_memory_create
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `content` | `String` | Yes | Memory content |
| `source` | `String` | Yes | Origin (session ID, "user", file path) |
| `trustTier` | `String` | No | agent, human, or auto (default: auto) |
| `tags` | `Vec<String>` | No | Categorization tags |
| `ttlDays` | `u32` | No | Days until expiration (null = tier default) |
#### pro_memory_search
FTS5 search across memory fragments for a project.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `query` | `String` | Yes | FTS5 search query |
| `limit` | `u32` | No | Max results (default: 10) |
#### pro_memory_get, pro_memory_update, pro_memory_delete
Standard CRUD by memory ID.
#### pro_memory_list
List memories for a project with optional filters.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `trustTier` | `String` | No | Filter by tier |
| `tag` | `String` | No | Filter by tag |
| `limit` | `u32` | No | Max results (default: 50) |
#### pro_memory_inject
Returns formatted memory text for system prompt injection.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `contextHints` | `Vec<String>` | No | Additional FTS5 terms for relevance |
| `topK` | `u32` | No | Number of memories to include (default: 5) |
#### pro_memory_set_config
Sets memory injection configuration for a project.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `topK` | `u32` | No | Default injection count |
| `autoExtract` | `bool` | No | Enable auto-extraction on session end |
---
## Codebase Symbol Graph
### Purpose
The Symbol Graph provides structural code awareness by scanning source files with regex patterns to extract function/method/class/struct definitions and build a lightweight call graph. This gives agents contextual knowledge about code structure without requiring a full language server.
### Scanning
The scanner processes files matching configurable glob patterns. Default extensions:
| Language | Extensions | Patterns Extracted |
|----------|------------|--------------------|
| TypeScript | `.ts`, `.tsx` | functions, classes, interfaces, type aliases, exports |
| Rust | `.rs` | functions, structs, enums, traits, impls |
| Python | `.py` | functions, classes, decorators |
Scanning is triggered manually or on project open. Results are stored in `agor_pro.db`. A full re-scan replaces all symbols for the project.
### Symbol Types
```typescript
interface CodeSymbol {
id: number;
projectId: string;
filePath: string; // Relative to project root
name: string; // Symbol name
kind: SymbolKind; // function | class | struct | enum | trait | interface | type
line: number; // Line number (1-based)
signature: string; // Full signature line
parentName: string | null; // Enclosing class/struct/impl
}
type SymbolKind = 'function' | 'class' | 'struct' | 'enum' | 'trait' | 'interface' | 'type';
```
### Commands
#### pro_symbols_scan
Triggers a full scan of the project's source files.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `rootPath` | `String` | Yes | Absolute path to project root |
| `extensions` | `Vec<String>` | No | File extensions to scan (default: ts,rs,py) |
#### pro_symbols_search
Search symbols by name (prefix match).
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `query` | `String` | Yes | Symbol name prefix |
| `kind` | `String` | No | Filter by symbol kind |
| `limit` | `u32` | No | Max results (default: 20) |
#### pro_symbols_find_callers
Searches for references to a symbol name across the project's scanned files using text matching.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `symbolName` | `String` | Yes | Symbol to find references to |
Returns file paths and line numbers where the symbol name appears (excluding its definition).
#### pro_symbols_file
Returns all symbols in a specific file.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | `String` | Yes | Project identifier |
| `filePath` | `String` | Yes | Relative file path |
### Limitations
- Regex-based extraction is approximate. It does not parse ASTs and may miss symbols in unusual formatting or produce false positives in comments/strings.
- `find_callers` uses text matching, not semantic analysis. It will find string matches in comments and string literals.
- Large codebases (10,000+ files) may take several seconds to scan. Scanning runs on a background thread and emits a completion event.

View file

@ -0,0 +1,173 @@
# Plugin Marketplace
> This documentation covers Pro edition features available in the agents-orchestrator/agents-orchestrator private repository.
## Overview
The Plugin Marketplace provides a curated catalog of plugins for Agents Orchestrator. It handles discovery, installation, updates, and removal of plugins from a central GitHub-hosted repository. The community plugin runtime (Web Worker sandbox, permission model) is unchanged -- the marketplace adds distribution and lifecycle management on top.
## Catalog
The plugin catalog is a JSON file hosted in the `agents-orchestrator/agor-plugins` GitHub repository:
```
https://raw.githubusercontent.com/agents-orchestrator/agor-plugins/main/catalog.json
```
### Catalog Schema
```typescript
interface PluginCatalog {
version: number; // Catalog schema version
updatedAt: string; // ISO 8601 timestamp
plugins: CatalogEntry[];
}
interface CatalogEntry {
id: string; // Unique plugin identifier
name: string; // Display name
description: string; // Short description
version: string; // Semver version
author: string; // Author name
tier: 'free' | 'paid'; // Availability tier
downloadUrl: string; // HTTPS URL to plugin archive (.tar.gz)
checksum: string; // SHA-256 hex digest of the archive
size: number; // Archive size in bytes
permissions: string[]; // Required permissions
minVersion: string; // Minimum Agents Orchestrator version
tags: string[]; // Categorization tags
}
```
## Plugin Catalog
### Free Plugins (8)
| ID | Name | Description |
|----|------|-------------|
| `session-stats` | Session Stats | Displays token count, cost, and turn count as a compact overlay |
| `git-log-viewer` | Git Log Viewer | Shows recent git history for the active project in a formatted panel |
| `task-export` | Task Exporter | Exports bttask board contents to markdown or JSON |
| `prompt-library` | Prompt Library | User-managed collection of reusable prompt templates |
| `session-notes` | Session Notes | Per-session scratchpad persisted as tasks-KV entries |
| `time-tracker` | Time Tracker | Records wall-clock time per agent session with daily summaries |
| `diff-viewer` | Diff Viewer | Renders unified diffs from agent tool calls with syntax highlighting |
| `agent-logger` | Agent Logger | Streams agent messages to a local JSONL file for offline analysis |
### Paid Plugins (5)
| ID | Name | Description |
|----|------|-------------|
| `cost-alerts` | Cost Alerts | Configurable cost threshold notifications with Slack/webhook delivery |
| `team-dashboard` | Team Dashboard | Aggregated usage analytics across multiple workstations |
| `policy-engine` | Policy Engine | Custom rules for agent behavior (blocked commands, file restrictions) |
| `audit-export` | Audit Exporter | Exports audit logs to external SIEM systems (JSON, CEF formats) |
| `model-benchmark` | Model Benchmark | A/B testing framework for comparing model performance on identical tasks |
## Install Flow
1. User opens the Marketplace tab in the settings panel.
2. The UI fetches `catalog.json` from GitHub (cached for 1 hour).
3. User selects a plugin and clicks Install.
4. The backend downloads the plugin archive from `downloadUrl` over HTTPS.
5. The backend verifies the SHA-256 checksum against `checksum` in the catalog entry.
6. On checksum match, the archive is extracted to `~/.config/bterminal/plugins/{plugin-id}/`.
7. The plugin's `plugin.json` manifest is validated (required fields, permission declarations).
8. The plugin appears in the installed list and can be activated.
## Uninstall Flow
1. User selects an installed plugin and clicks Uninstall.
2. The backend calls `unloadPlugin()` if the plugin is currently active.
3. The plugin directory `~/.config/bterminal/plugins/{plugin-id}/` is deleted.
4. Any tasks-KV entries prefixed with `plugin:{plugin-id}:` are purged.
## Update Flow
1. On marketplace tab open, the UI compares installed plugin versions against catalog versions.
2. Plugins with available updates show an Update button.
3. Update performs: download new archive, verify checksum, unload plugin, replace directory contents, reload plugin.
4. The plugin's state (tasks-KV entries) is preserved across updates.
## Security
### Checksum Verification
Every plugin archive is verified against its SHA-256 checksum before extraction. If the checksum does not match, the install is aborted and an error is shown. This prevents tampered archives from being installed.
### HTTPS-Only Downloads
Plugin archives are only downloaded over HTTPS. The `downloadUrl` field is validated to start with `https://`. HTTP URLs are rejected.
### Path Traversal Protection
During archive extraction, all file paths are validated to ensure they resolve within the target plugin directory. Paths containing `..` segments or absolute paths are rejected. This prevents a malicious archive from writing files outside the plugin directory.
### Sandbox Isolation
Installed plugins run in the same Web Worker sandbox as community plugins. The marketplace does not grant additional privileges. Each plugin's permissions are declared in `plugin.json` and enforced by the plugin host at runtime.
## Commands
### pro_marketplace_fetch_catalog
Fetches the plugin catalog from GitHub. Returns cached data if fetched within the last hour.
**Response:** `PluginCatalog`
### pro_marketplace_install
Downloads, verifies, and installs a plugin.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `pluginId` | `String` | Yes | Plugin identifier from catalog |
### pro_marketplace_uninstall
Removes an installed plugin and its files.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `pluginId` | `String` | Yes | Plugin identifier |
### pro_marketplace_update
Updates an installed plugin to the latest catalog version.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `pluginId` | `String` | Yes | Plugin identifier |
### pro_marketplace_list_installed
Returns all installed plugins with their versions and active state.
**Response:** `Vec<InstalledPlugin>`
```typescript
interface InstalledPlugin {
id: string;
name: string;
version: string;
installedAt: string;
isActive: boolean;
hasUpdate: boolean;
latestVersion: string | null;
}
```
### pro_marketplace_check_updates
Compares installed plugins against the catalog and returns available updates.
**Response:** `Vec<PluginUpdate>`
```typescript
interface PluginUpdate {
id: string;
currentVersion: string;
latestVersion: string;
changelogUrl: string | null;
}
```

207
docs/production/features.md Normal file
View file

@ -0,0 +1,207 @@
# Production Features
User-facing production features: search, plugins, secrets, notifications, audit logging, error classification, and session metrics.
---
## FTS5 Full-Text Search
The search system uses SQLite's FTS5 extension for full-text search across three data types. Accessed via a Spotlight-style overlay (Ctrl+Shift+F).
### Architecture
```
SearchOverlay.svelte (Ctrl+Shift+F)
|
+-- search-bridge.ts -> Tauri commands
|
+-- search.rs -> SearchDb (separate FTS5 tables)
|
+-- search_messages -- agent session messages
+-- search_tasks -- bttask task content
+-- search_btmsg -- btmsg inter-agent messages
```
### Virtual Tables
| Table | Source | Indexed Columns |
|-------|--------|----------------|
| `search_messages` | Agent session messages | content, session_id, project_id |
| `search_tasks` | bttask tasks | title, description, assignee, status |
| `search_btmsg` | btmsg messages | content, sender, recipient, channel |
### Operations
| Tauri Command | Purpose |
|---------------|---------|
| `search_init` | Creates FTS5 virtual tables if not exist |
| `search_all` | Queries all 3 tables, returns ranked results |
| `search_rebuild` | Drops and rebuilds all indices (maintenance) |
| `search_index_message` | Indexes a single new message (real-time) |
### Frontend (SearchOverlay.svelte)
- Triggered by Ctrl+Shift+F
- Spotlight-style floating overlay centered on screen
- 300ms debounce on input to avoid excessive queries
- Results grouped by type (Messages, Tasks, Communications)
- Click result to navigate to source (focus project, switch tab)
---
## Plugin System
The plugin system allows extending agor with custom commands and event handlers. Plugins are sandboxed JavaScript executing in a restricted environment.
### Plugin Discovery
Plugins live in `~/.config/agor/plugins/`. Each plugin is a directory containing a `plugin.json` manifest:
```json
{
"name": "my-plugin",
"version": "1.0.0",
"description": "A custom plugin",
"main": "index.js",
"permissions": ["notifications", "settings"]
}
```
The Rust `plugins.rs` module scans for `plugin.json` files with path-traversal protection (rejects `..` in paths).
### Sandboxed Runtime (plugin-host.ts)
Plugins execute via `new Function()` in a restricted scope:
**Shadowed globals (13):**
`fetch`, `XMLHttpRequest`, `WebSocket`, `Worker`, `eval`, `Function`, `importScripts`, `require`, `process`, `globalThis`, `window`, `document`, `localStorage`
**Provided API (permission-gated):**
| API | Permission | Purpose |
|-----|-----------|---------|
| `bt.notify(msg)` | `notifications` | Show toast notification |
| `bt.getSetting(key)` | `settings` | Read app setting |
| `bt.setSetting(key, val)` | `settings` | Write app setting |
| `bt.registerCommand(name, fn)` | (always allowed) | Add command to palette |
| `bt.on(event, fn)` | (always allowed) | Subscribe to app events |
The API object is frozen (`Object.freeze`) to prevent tampering. Strict mode is enforced.
### Security Notes
The `new Function()` sandbox is best-effort — it is not a security boundary. A determined attacker could escape it. Landlock provides the actual filesystem restriction. The plugin sandbox primarily prevents accidental damage from buggy plugins.
35 tests cover the plugin system including permission validation, sandbox escape attempts, and lifecycle management.
---
## Secrets Management
Secrets (API keys, tokens) are stored in the system keyring rather than in plaintext files or SQLite.
### Backend (`secrets.rs`)
Uses the `keyring` crate with the `linux-native` feature (libsecret/DBUS):
```rust
pub struct SecretsManager;
impl SecretsManager {
pub fn store(key: &str, value: &str) -> Result<()>;
pub fn get(key: &str) -> Result<Option<String>>;
pub fn delete(key: &str) -> Result<()>;
pub fn list() -> Result<Vec<SecretMetadata>>;
pub fn has_keyring() -> bool;
}
```
Metadata (key names, last modified timestamps) is stored in SQLite settings. The actual secret values never touch disk — they live only in the system keyring (gnome-keyring, KWallet, or equivalent).
### No Fallback
If no keyring daemon is available (no DBUS session, no gnome-keyring), secret operations fail with a clear error message. There is no plaintext fallback — this is intentional to prevent accidental credential leakage.
---
## Notifications
Agor has two notification systems: in-app toasts and OS-level desktop notifications.
### In-App Toasts (`notifications.svelte.ts`)
- 6 notification types: `success`, `error`, `warning`, `info`, `agent_complete`, `agent_error`
- Maximum 5 visible toasts, 4-second auto-dismiss
- Toast history (up to 100 entries) with unread badge in NotificationCenter
- Agent dispatcher emits toasts on: agent completion, agent error, sidecar crash
### Desktop Notifications (`notifications.rs`)
Uses `notify-rust` crate for native Linux notifications. Graceful fallback if notification daemon is unavailable (e.g., no D-Bus session).
### Notification Center (`NotificationCenter.svelte`)
Bell icon in the top-right with unread badge. Dropdown panel shows notification history with timestamps, type icons, and clear/mark-read actions.
---
## Audit Logging
All significant events are logged to the `audit_log` table:
| Event Type | Logged When |
|-----------|-------------|
| `message_sent` | Agent sends a btmsg message |
| `message_read` | Agent reads messages |
| `channel_created` | New btmsg channel created |
| `agent_registered` | Agent registers with btmsg |
| `heartbeat` | Agent sends heartbeat |
| `task_created` | New bttask task |
| `task_status_changed` | Task status update |
| `wake_event` | Wake scheduler triggers |
| `prompt_injection_detected` | Suspicious content in agent messages |
The AuditLogTab component displays audit entries with filtering by event type and agent, with 5-second auto-refresh and max 200 entries.
---
## Error Classification
The error classifier (`utils/error-classifier.ts`) categorizes API errors into 6 types with appropriate retry behavior:
| Type | Examples | Retry? | User Message |
|------|----------|--------|--------------|
| `rate_limit` | HTTP 429, "rate limit exceeded" | Yes (with backoff) | "Rate limited -- retrying in Xs" |
| `auth` | HTTP 401/403, "invalid API key" | No | "Authentication failed -- check API key" |
| `quota` | "quota exceeded", "billing" | No | "Usage quota exceeded" |
| `overloaded` | HTTP 529, "overloaded" | Yes (longer backoff) | "Service overloaded -- retrying" |
| `network` | ECONNREFUSED, timeout, DNS failure | Yes | "Network error -- check connection" |
| `unknown` | Anything else | No | "Unexpected error" |
20 unit tests cover classification accuracy across various error message formats.
---
## Session Metrics
Per-project historical session data is stored in the `session_metrics` table:
| Column | Type | Purpose |
|--------|------|---------|
| `project_id` | TEXT | Which project |
| `session_id` | TEXT | Agent session ID |
| `start_time` | INTEGER | Session start timestamp |
| `end_time` | INTEGER | Session end timestamp |
| `peak_tokens` | INTEGER | Maximum context tokens used |
| `turn_count` | INTEGER | Total conversation turns |
| `tool_call_count` | INTEGER | Total tool calls made |
| `cost_usd` | REAL | Total cost in USD |
| `model` | TEXT | Model used |
| `status` | TEXT | Final status (success/error/stopped) |
| `error_message` | TEXT | Error details if failed |
100-row retention per project (oldest pruned on insert). Metrics are persisted on agent completion via the agent dispatcher.
The MetricsPanel component displays this data as:
- **Live view** — fleet aggregates, project health grid, task board summary, attention queue
- **History view** — SVG sparklines for cost/tokens/turns/tools/duration, stats row, session table

View file

@ -0,0 +1,140 @@
# Production Hardening
Reliability, security, and observability features that ensure agor runs safely in daily use.
---
## Sidecar Supervisor (Crash Recovery)
The `SidecarSupervisor` in `agor-core/src/supervisor.rs` automatically restarts crashed sidecar processes.
### Behavior
When the sidecar child process exits unexpectedly:
1. The supervisor detects the exit via process monitoring
2. Waits with exponential backoff before restarting:
- Attempt 1: wait 1 second
- Attempt 2: wait 2 seconds
- Attempt 3: wait 4 seconds
- Attempt 4: wait 8 seconds
- Attempt 5: wait 16 seconds (capped at 30s)
3. After 5 failed attempts, the supervisor gives up and reports `SidecarHealth::Failed`
### Health States
```rust
pub enum SidecarHealth {
Healthy,
Restarting { attempt: u32, next_retry: Duration },
Failed { attempts: u32, last_error: String },
}
```
The frontend can query health state and offer a manual restart button when auto-recovery fails. 17 unit tests cover all recovery scenarios including edge cases like rapid successive crashes.
---
## Landlock Sandbox
Landlock is a Linux kernel (6.2+) security module that restricts filesystem access for processes. Agor uses it to sandbox sidecar processes, limiting what files they can read and write.
### Configuration
```rust
pub struct SandboxConfig {
pub read_write_paths: Vec<PathBuf>, // Full access (project dir, temp)
pub read_only_paths: Vec<PathBuf>, // Read-only (system libs, SDK)
}
```
The sandbox is applied via `pre_exec()` on the child process command, before the sidecar starts executing.
### Path Rules
| Path | Access | Reason |
|------|--------|--------|
| Project CWD | Read/Write | Agent needs to read and modify project files |
| `/tmp` | Read/Write | Temporary files during operation |
| `~/.local/share/agor/` | Read/Write | SQLite databases (btmsg, sessions) |
| System library paths | Read-only | Node.js/Deno runtime dependencies |
| `~/.claude/` or config dir | Read-only | Claude configuration and credentials |
### Graceful Fallback
If the kernel doesn't support Landlock (< 6.2) or the kernel module isn't loaded, the sandbox silently degrades the sidecar runs without filesystem restrictions. This is logged as a warning but doesn't prevent operation.
---
## WAL Checkpoint
Both SQLite databases (`sessions.db` and `btmsg.db`) use WAL (Write-Ahead Logging) mode for concurrent read/write access. Without periodic checkpoints, the WAL file grows unboundedly.
A background tokio task runs `PRAGMA wal_checkpoint(TRUNCATE)` every 5 minutes on both databases. This moves WAL data into the main database file and resets the WAL.
---
## TLS Relay Support
The `agor-relay` binary supports TLS for encrypted WebSocket connections:
```bash
agor-relay \
--port 9750 \
--token <secret> \
--tls-cert /path/to/cert.pem \
--tls-key /path/to/key.pem
```
Without `--tls-cert`/`--tls-key`, the relay only accepts connections with the `--insecure` flag (plain WebSocket). In production, TLS is mandatory — the relay rejects `ws://` connections unless `--insecure` is explicitly set.
Certificate pinning (comparing relay certificate fingerprints) is planned for v3.1.
---
## OpenTelemetry Observability
The Rust backend supports optional OTLP trace export via the `AGOR_OTLP_ENDPOINT` environment variable.
### Backend (`telemetry.rs`)
- `TelemetryGuard` initializes tracing + OTLP export pipeline
- Uses `tracing` + `tracing-subscriber` + `opentelemetry` 0.28 + `tracing-opentelemetry` 0.29
- OTLP/HTTP export to configured endpoint
- `Drop`-based shutdown ensures spans are flushed
### Frontend (`telemetry-bridge.ts`)
The frontend cannot use the browser OTEL SDK (WebKit2GTK incompatible). Instead, it routes events through a `frontend_log` Tauri command that pipes into Rust's tracing system:
```typescript
tel.info('agent-started', { sessionId, provider });
tel.warn('context-pressure', { projectId, usage: 0.85 });
tel.error('sidecar-crash', { error: msg });
```
### Docker Stack
A pre-configured Tempo + Grafana stack lives in `docker/tempo/`:
```bash
cd docker/tempo && docker compose up -d
# Grafana at http://localhost:9715
# Set AGOR_OTLP_ENDPOINT=http://localhost:4318 to enable export
```
---
## Agent Health Monitoring
### Heartbeats
Tier 1 agents send periodic heartbeats via `btmsg heartbeat` CLI command. The heartbeats table tracks last heartbeat timestamp and status per agent.
### Stale Detection
The health store detects stalled agents via the `stallThresholdMin` setting (default 15 minutes). If an agent hasn't produced output within the threshold, its activity state transitions to `Stalled` and the attention score jumps to 100 (highest priority).
### Dead Letter Queue
Messages sent to agents that are offline or have crashed are moved to the dead letter queue in `btmsg.db`. This prevents silent message loss and allows debugging delivery failures.

View file

@ -0,0 +1,184 @@
# Provider Reference
Agents Orchestrator supports 4 agent providers. Each provider has its own sidecar
runner, message adapter, and capability set. Providers are selected per-project
in `groups.json` or via the Settings tab.
## Provider summary
| Provider | ID | Default Model | Sidecar Runner | API |
|----------|----|---------------|----------------|-----|
| Claude Code | `claude` | claude-opus-4-6 | `claude-runner.mjs` | Claude Agent SDK |
| Codex CLI | `codex` | gpt-5.4 | `codex-runner.mjs` | @openai/codex-sdk |
| Ollama | `ollama` | qwen3:8b | `ollama-runner.mjs` | REST (localhost:11434) |
| Aider | `aider` | openrouter/anthropic/claude-sonnet-4 | `aider-runner.mjs` | OpenRouter / direct |
## Capabilities
Each provider declares capabilities that gate UI features:
| Capability | Claude | Codex | Ollama | Aider |
|------------|--------|-------|--------|-------|
| `hasProfiles` | Yes | No | No | No |
| `hasSkills` | Yes | No | No | No |
| `hasModelSelection` | Yes | Yes | Yes | Yes |
| `hasSandbox` | No | Yes | No | No |
| `supportsSubagents` | Yes | No | No | No |
| `supportsCost` | Yes | No | No | No |
| `supportsResume` | Yes | Yes | No | No |
When a capability is `false`, the corresponding UI element is hidden. For
example, profile selectors and skill autocomplete only appear for Claude.
## Provider type
```typescript
type ProviderId = 'claude' | 'codex' | 'ollama' | 'aider';
```
Defined in `src/lib/providers/types.ts`.
## Provider selection
Set `provider` on a `ProjectConfig` in `groups.json`:
```json
{
"id": "my-project",
"name": "My Project",
"cwd": "/home/user/code/project",
"provider": "codex",
"model": "o3"
}
```
If omitted, defaults to `claude`.
## Claude Code
- **Source:** `src/lib/providers/claude.ts`
- **Runner:** `sidecar/claude-runner.ts` (compiled to `sidecar/dist/claude-runner.mjs`)
- **SDK:** `@anthropic-ai/claude-agent-sdk` `query()` function
- **CLI detection:** Auto-detects Claude CLI at startup. Checks paths in order:
`~/.local/bin/claude`, `~/.claude/local/claude`, `/usr/local/bin/claude`,
`/usr/bin/claude`, then `which claude`. Agent errors immediately if not found.
- **Config dir:** Override with `CLAUDE_CONFIG_DIR` for multi-account setups.
Mapped to `claude_config_dir` in `AgentQueryOptions`.
Available models:
| Model ID | Label |
|----------|-------|
| `claude-opus-4-6` | Opus 4.6 |
| `claude-sonnet-4-6` | Sonnet 4.6 |
| `claude-haiku-4-5-20251001` | Haiku 4.5 |
## Codex CLI
- **Source:** `src/lib/providers/codex.ts`
- **Runner:** `sidecar/codex-runner.ts` (compiled to `sidecar/dist/codex-runner.mjs`)
- **SDK:** `@openai/codex-sdk` (dynamic import; graceful failure if not installed)
- **Sandbox:** Maps agor sandbox settings to Codex approval modes.
- **Env:** Requires `OPENAI_API_KEY`.
Available models:
| Model ID | Label |
|----------|-------|
| `gpt-5.4` | GPT-5.4 |
| `o3` | o3 |
| `o4-mini` | o4-mini |
## Ollama
- **Source:** `src/lib/providers/ollama.ts`
- **Runner:** `sidecar/ollama-runner.ts` (compiled to `sidecar/dist/ollama-runner.mjs`)
- **API:** Direct HTTP to `localhost:11434` (native fetch, zero external deps)
- **Prereq:** Ollama must be running locally before starting an agent session.
Available models:
| Model ID | Label |
|----------|-------|
| `qwen3:8b` | Qwen3 8B |
| `qwen3:32b` | Qwen3 32B |
| `llama3.3:70b` | Llama 3.3 70B |
| `deepseek-r1:14b` | DeepSeek R1 14B |
| `codellama:13b` | Code Llama 13B |
## Aider
- **Source:** `src/lib/providers/aider.ts`
- **Runner:** `sidecar/aider-runner.ts` (compiled to `sidecar/dist/aider-runner.mjs`)
- **Routing:** Supports OpenRouter, OpenAI direct, Anthropic direct, and Ollama
local models via model ID prefixes.
- **Env:** Requires `OPENROUTER_API_KEY` for OpenRouter models, `OPENAI_API_KEY`
for OpenAI direct, `ANTHROPIC_API_KEY` for Anthropic direct.
- **Autonomous mode:** Supports `restricted` (surfaces commands for approval) and
`autonomous` (auto-executes with audit logging) via `autonomousMode` project
config.
Available models:
| Model ID | Label |
|----------|-------|
| `openrouter/anthropic/claude-sonnet-4` | Claude Sonnet 4 (OpenRouter) |
| `openrouter/anthropic/claude-haiku-4` | Claude Haiku 4 (OpenRouter) |
| `openrouter/openai/gpt-4.1` | GPT-4.1 (OpenRouter) |
| `openrouter/openai/o3` | o3 (OpenRouter) |
| `openrouter/google/gemini-2.5-pro` | Gemini 2.5 Pro (OpenRouter) |
| `openrouter/deepseek/deepseek-r1` | DeepSeek R1 (OpenRouter) |
| `openrouter/meta-llama/llama-4-maverick` | Llama 4 Maverick (OpenRouter) |
| `anthropic/claude-sonnet-4-5-20250514` | Claude Sonnet 4.5 (direct) |
| `o3` | o3 (OpenAI direct) |
| `ollama/qwen3:8b` | Qwen3 8B (Ollama) |
## Sidecar architecture
Each provider has a runner script compiled to a standalone ESM bundle in
`sidecar/dist/`. The sidecar communicates with the Rust backend via stdio
NDJSON.
Build all runners:
```bash
npm run build:sidecar
```
### Provider routing
`SidecarManager.resolve_sidecar_for_provider(provider)` selects the runner file
based on `ProviderId`. The sidecar process runs under Deno (preferred, faster
startup) with Node.js as fallback.
### Environment stripping
The sidecar strips provider-specific environment variables to prevent
cross-contamination. The function `strip_provider_env_var()` in
`agor-core/src/sidecar.rs` filters the process environment:
- **Stripped:** `CLAUDE_*`, `CODEX_*`, `OLLAMA_*` (and similar provider vars)
- **Preserved:** `CLAUDE_CODE_EXPERIMENTAL_*` (whitelist for experimental flags)
- **Preserved:** Standard vars (`HOME`, `PATH`, `USER`, `SHELL`, `TERM`,
`XDG_*`, `RUST_LOG`, etc.)
This is applied in two layers:
1. **Rust:** `env_clear()` + filtered `clean_env` on the spawned process
2. **JavaScript:** Runner SDK `env` option for provider-specific overrides
## Message adapters
Each provider has a message adapter that parses provider-specific output into a
common `AgentMessage` format:
| Provider | Adapter | Tests |
|----------|---------|-------|
| Claude | `src/lib/adapters/claude-messages.ts` | 25 tests |
| Codex | `src/lib/adapters/codex-messages.ts` | 19 tests |
| Ollama | `src/lib/adapters/ollama-messages.ts` | 11 tests |
| Aider | `sidecar/aider-parser.ts` | 72 tests |
Adapters are registered in `src/lib/adapters/message-adapters.ts` and routed by
`ProviderId`. The dispatcher (`src/lib/agent-dispatcher.ts`) delegates to the
correct adapter based on the session's provider.