Automattic / Automattic/frontend-agent-chat
feat: Multi-agent support — mode-aware config, per-user agent resolution, agent list envelope
- Dominant language
- PHP
- Stars
- 3
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
The frontend chat plugin is hard-coded to a single agent per site. To support personalized agents and multi-agent switching, the entire config→resolve→localize pipeline needs mode awareness.
Depends on: Extra-Chill/data-machine#993, Extra-Chill/data-machine#994
Related: Extra-Chill/chat#5 (port Spawn), upcoming `@extrachill/chat` multi-agent issue
## Current Architecture
```
Site option (one agent_slug) → resolve 1 agent → wp_localize_script({agentId: N})
```
Every user on a site gets the same agent. No switching, no personalization.
## Proposed Architecture
```
Site option (mode + optional slug)
↓
Mode-aware resolution
↓
┌─────┼─────────────┐
single personal multi
↓ ↓ ↓
1 agent user's all accessible
agent agents
↓
wp_localize_script({
mode: 'multi',
basePath: '/datamachine/v1/chat',
agents: [{ agentId, agentSlug, agentName, agentDescription }, ...],
activeAgentId: N
})
```
## Gap 1: Config option needs `mode` field
**File:** `inc/config.php`
Current defaults:
```php
$defaults = [
'agent_slug' => '',
'description' => 'Your AI assistant.',
'enabled' => false,
];
```
Proposed:
```php
$defaults = [
'mode' => 'single', // 'single' | 'personal' | 'multi'
'agent_slug' => '', // used in single mode only
'description' => 'Your AI assistant.',
'enabled' => false,
];
```
**Mode semantics:**
- **`single`** — Current behavior. One `agent_slug` configured per site.
- **`personal`** — Each user sees their own agent (via `Agents::get_by_owner_id()`). Falls back to site default.
- **`multi`** — User sees ALL agents they have access to. Agent switcher UI in the drawer.
## Gap 2: Multi-agent resolution function needed
**File:** `inc/config.php`
New function `data_machine_frontend_chat_resolve_accessible_agents()` that:
1. Gets current user ID
2. If admin: `$agents_repo->get_all(['site_id' => get_current_blog_id()])`
3. If non-admin: merge `$agents_repo->get_all_by_owner_id($user_id)` + `$access_repo->get_agent_ids_for_user($user_id)` resolved via `get_agents_by_ids()`
4. Filter by `site_scope` matching current blog or NULL
5. Final gate: `PermissionHelper::can_access_agent($id, 'viewer')` on each
6. Filter to `status = 'active'` only
7. Return full agent rows
This reuses the same Data Machine APIs that `GET /datamachine/v1/agents` uses internally.
## Gap 3: `wp_localize_script` needs new envelope shape
**File:** `inc/enqueue.php`
Current:
```php
wp_localize_script('data-machine-frontend-chat', 'datamachineChatConfig', [
'agentId' => (int) $agent['agent_id'],
'basePath' => '/datamachine/v1/chat',
'agentName' => (string) $agent['agent_name'],
'agentDescription' => (string) $config['description'],
]);
```
Proposed:
```php
wp_localize_script('data-machine-frontend-chat', 'datamachineChatConfig', [
'mode' => $config['mode'],
'basePath' => '/datamachine/v1/chat',
'agents' => array_map(fn($a) => [
'agentId' => (int) $a['agent_id'],
'agentSlug' => (string) $a['agent_slug'],
'agentName' => (string) $a['agent_name'],
'agentDescription' => (string) ($a['agent_config']['description'] ?? $config['description']),
], $agents),
'activeAgentId' => $active_agent_id,
]);
```
## Gap 4: `enqueue.php` needs mode-branching logic
The enqueue function currently bails if the single agent can't be resolved. In multi mode, it needs to:
- **Single mode**: Current behavior (resolve one agent by slug, check access)
- **Personal mode**: Resolve via `Agents::get_by_owner_id()`, fall back to site default slug
- **Multi mode**: Call `resolve_accessible_agents()`, enqueue if any agents available
- Visibility check per agent (already handled by `can_access_agent()`)
## Gap 5: React entry point needs mode awareness
**File:** `src/index.ts`
Currently reads `window.datamachineChatConfig.agentId` — bails if falsy. Needs to:
1. Read `config.mode` and `config.agents[]`
2. Guard: bail if `!config?.agents?.length`
3. In `single`/`personal` mode: mount with `agents[0]` (current behavior)
4. In `multi` mode: mount with full agents array + agent switcher
## Gap 6: RoadieChat component needs agents prop
**File:** `src/RoadieChat.tsx`
Current props: `{ agentId, basePath, agentName, agentDescription }`
For multi mode, needs: `{ agents, basePath, activeAgentId?, mode }` — where agents is the full array. The component manages active agent state internally and remounts `` via React `key={agentId}` when switching.
## Gap 7: Per-agent description source
In single mode, description comes from the site option. In personal/multi mode, each agent needs its own description. Two options:
1. **`agent_config.description`** — store description in the agent's JSON config column (no schema change, just convention)
2. **New column** — add `description` to the agents table
Option 1 is simpler and consistent with how `context_models` and `tool_policy` are already stored in `agent_config`.
## Gap 8: Legacy naming cleanup
`src/RoadieChat.tsx` and `src/roadie.css` still use the "Roadie" name. Should rename to `ChatWidget.tsx` / `widget.css` or similar, since this is the generic DM frontend chat — not the EC-specific Roadie plugin.
## Backward Compatibility
If `mode` is absent from the config, treat as `single` and fall back to the flat `agentId` shape. This lets existing installs upgrade without reconfiguration.
## Checklist
- [ ] Add `mode` field to config defaults (`single` default)
- [ ] Add `data_machine_frontend_chat_resolve_accessible_agents()` function
- [ ] Mode-branching in `data_machine_frontend_chat_enqueue()`
- [ ] New JS config envelope shape: `{ mode, basePath, agents[], activeAgentId }`
- [ ] Update `src/index.ts` to handle mode + agents array
- [ ] Update `src/RoadieChat.tsx` for multi-agent props
- [ ] Use `agent_config.description` as per-agent description source
- [ ] Rename RoadieChat → ChatWidget (legacy naming cleanup)
- [ ] Backward-compatible fallback when `mode` is absent
Contributor guide
No contributing guide indexed for this repository
Research direction
Read inc/config.php and inc/enqueue.php first to understand current option handling, agent resolution, and wp_localize_script output. Then trace src/index.ts and src/RoadieChat.tsx for the existing single-agent flow. Done means the listed single, personal, and multi modes work across PHP and React, preserve the legacy fallback, expose per-agent descriptions, and complete the naming cleanup.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, react, typescript, wordpress
- Domain
- backend, frontend, full-stack
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100