Automattic / Automattic/frontend-agent-chat

feat: Multi-agent support — mode-aware config, per-user agent resolution, agent list envelope

Open
#1 2 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.