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
186
docs/agents/ref-btmsg.md
Normal file
186
docs/agents/ref-btmsg.md
Normal 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
161
docs/agents/ref-bttask.md
Normal 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`.
|
||||
251
docs/architecture/data-model.md
Normal file
251
docs/architecture/data-model.md
Normal 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.
|
||||
170
docs/contributing/dual-repo-workflow.md
Normal file
170
docs/contributing/dual-repo-workflow.md
Normal 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.
|
||||
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.
|
||||
196
docs/pro/features/git-integration.md
Normal file
196
docs/pro/features/git-integration.md
Normal 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.
|
||||
247
docs/pro/features/knowledge-base.md
Normal file
247
docs/pro/features/knowledge-base.md
Normal 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.
|
||||
173
docs/pro/marketplace/README.md
Normal file
173
docs/pro/marketplace/README.md
Normal 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
207
docs/production/features.md
Normal 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
|
||||
140
docs/production/hardening.md
Normal file
140
docs/production/hardening.md
Normal 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.
|
||||
184
docs/providers/ref-providers.md
Normal file
184
docs/providers/ref-providers.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue