| @@ -13,14 +13,15 @@ | ||
| 13 | 13 | | Route | Verb | Handler file | Permission | |
| 14 | 14 | |---|---|---|---| |
| 15 | 15 | | `/session` | GET / POST / DELETE | `includes/session.php` | logged-in + OpenStation enabled | |
| 16 | 16 | | `/default-window` | POST | `includes/default-window.php` | logged-in + OpenStation enabled | |
| 17 | -| `/intros/seen` | POST | `includes/seen-intros.php` | logged-in + OpenStation enabled | | |
| 17 | +| `/intros/seen` | POST | `includes/seen-intros.php` | logged-in + OpenStation enabled; the two classic-admin slugs (`activation-welcome`, `activation-nudge`) are accepted from any logged-in `read` account, since both render only while OpenStation is off | | |
| 18 | 18 | | `/intros` | DELETE | `includes/seen-intros.php` | logged-in + OpenStation enabled | |
| 19 | 19 | | `/os-settings` | GET / POST | `includes/os-settings.php` | logged-in + OpenStation enabled | |
| 20 | 20 | | `/extended-options` | GET / POST | `includes/extended-options.php` | `manage_options` | |
| 21 | 21 | | `/pwa-state` | GET / POST | `includes/pwa.php` | logged-in + OpenStation enabled | |
| 22 | 22 | | `/feedback/deactivation` | POST | `includes/feedback/rest.php` | `activate_plugins` + `openstation_deactivation_feedback_enabled()`; deliberately not `openstation_rest_require_enabled()` (the person deactivating usually has OpenStation off). No object-level checks: the route stores nothing on the site, it forwards an anonymous payload to the intake on openstation.blog and answers `{ sent }` | |
| 23 | +| `/feedback/usage` | POST | `includes/feedback/usage.php` | logged-in + OpenStation enabled + `openstation_usage_feedback_enabled()`. No object-level checks: the route stores nothing on the site but the caller's own `usage-feedback` seen-intro flag, forwards the typed answers (and an email only when one was typed) to the intake on openstation.blog and answers `{ sent }` (`400` for an empty form or a malformed address, `502` when the forward failed) | | |
| 23 | 24 | | `/debug` | GET | `includes/devtools.php` | `manage_options` (filterable via `openstation_debug_rest_permission`) | |
| 24 | 25 | | `/presence` | GET / POST | `includes/presence.php` | logged-in + OpenStation enabled | |
| 25 | 26 | | `/oauth/start` | POST | `includes/oauth-relay.php` | logged-in | |
| 26 | 27 | | `/oauth/callback` | GET | `includes/oauth-relay.php` | public (validated by the state nonce) | |
| @@ -49,10 +50,11 @@ | ||
| 49 | 50 | | `/user-footprint/{id}` | GET | `includes/my-wordpress/user-footprint.php` | `edit_posts` (filterable via `openstation_my_wordpress_user_can_use`), then gated per post rather than per tier. A timeline row needs the caller to see its post: a public status of a viewable type, else `read_post`; `edit_post` for a type with no front end; the `/comment-stats` parent gate for comment rows, sealed and deleted parents included. Every count that can reach those posts (`totals`, each day's `comments` and `updates`, and the streak built from them) asks the same gate of each post it counts, so a count never reports activity the rows withhold, a plugin's per-post capability filter included. `list_users` (or self) only adds `roleLabels` / `registered`. Viewer-dependent payload | |
| 50 | 51 | | `/media-usage/{id}` | GET | `includes/my-wordpress/media-usage.php` | `read_post` on the attachment | |
| 51 | 52 | | `/recycle-bin/*` | various | `includes/recycle-bin/rest.php` | `delete_posts` (per-route gate) | |
| 52 | 53 | | `/files/*` | various | `includes/desktop-files/rest.php` | logged-in + OpenStation enabled (share routes add a sharing gate) | |
| 53 | -| `/ai/search` | POST | `includes/ai-copilot/search.php` | logged-in + AI feature flag | | |
| 54 | -| `/ai/platform-settings` | GET / POST | `includes/ai-copilot/platform-settings.php` | `manage_options` | | |
| 54 | +| `/ai/search` | POST | `includes/ai-copilot/search.php` | `read` + `openstation_ai_is_available()` (`503` without it) + the caller's own AI assistant toggle in Preferences → Features (`403` when off). Every tool the server runs is a read-only ability dispatched through `WP_Ability::execute()`, so each one's own `permission_callback` still applies (see [`docs/agents-security.md`](../../docs/agents-security.md)); a picked client command comes back as a `tool_call` for the browser to run | | |
| 55 | +| `/ai/status` | GET | `includes/ai-copilot/settings.php` | `read`. Answers only the caller's own assistant config (availability, provider configured, their own toggle, the Connectors URL); no site content | | |
| 56 | +| `/mio/turn` | POST | `includes/ai-copilot/mio.php` | The caller's own MIO API preference (`403` when off), then the `/ai/search` gate, then a configured assistant provider (`503` without one). Stateless: stores nothing and runs no server-side tool; the window's tools call the existing write endpoints, which keep their own permissions | | |
| 55 | 57 | | `/games/{game}/scores` | GET / POST | `includes/games/rest.php` | logged-in + OpenStation enabled + `read` (filterable via `openstation_games_rest_permission`) | |
| 56 | 58 | | `/games/{game}/playtime` | POST | `includes/games/rest.php` | same games gate | |
| 57 | 59 | | `/games/playtime` | GET | `includes/games/rest.php` | same games gate | |
| 58 | 60 | | `/games/challenges` | GET / POST | `includes/games/rest.php` | same games gate (create also passes `openstation_games_can_challenge`) | |
| @@ -61,10 +63,12 @@ | ||
| 61 | 63 | | `/games/challenges/{id}/complete` | POST | `includes/games/rest.php` | same games gate + challenge recipient | |
| 62 | 64 | | `/games/users/search` | GET | `includes/games/rest.php` | same games gate | |
| 63 | 65 | | `/agents` | GET / POST | `includes/agents/rest.php` | GET `edit_posts`, POST `edit_users` (both filterable; whole module behind the `agents` extended option) | |
| 64 | 66 | | `/agents/{id}` | GET / POST / DELETE | `includes/agents/rest.php` | GET `edit_posts`, POST/DELETE `edit_users` (filterable) | |
| 65 | -| `/agents/{id}/invoke` | POST | `includes/agents/rest.php` | `edit_posts` (filterable via `openstation_agents_user_can_invoke`), then the per-agent gate `openstation_agent_user_can_invoke_agent()` (honours the trigger's `capability`) + per-invoker and per-agent rate limits. The run itself is ceilinged at the caller's own capabilities — see [`docs/agents-security.md`](../../docs/agents-security.md) | | |
| 67 | +| `/agents/{id}/invoke` | POST | `includes/agents/rest.php` | `edit_posts` (filterable via `openstation_agents_user_can_invoke`), then the per-agent gate `openstation_agent_user_can_invoke_agent()` (the caller must hold every `capability` declared on any of the agent's triggers; the client-supplied `source` does not select which apply) + per-invoker and per-agent rate limits. The run itself is ceilinged at the caller's own capabilities — see [`docs/agents-security.md`](../../docs/agents-security.md) | | |
| 66 | 68 | | `/agents/{id}/jobs/{jobId}` | GET | `includes/agents/jobs.php` | Authenticated invoker + exact job owner and agent match. Constant-cost status read, no worker dispatch. | |
| 69 | +| `/agents/conversations` | GET / POST | `includes/agents/conversations.php` | `edit_posts` (filterable via `openstation_agents_user_can_invoke`). GET lists only the caller's own conversations; POST creates one owned by the caller and answers `404` unless `agentId` is an agent | | |
| 70 | +| `/agents/conversations/{id}` | GET / PUT / DELETE | `includes/agents/conversations.php` | same gate + the caller is the conversation's author; anyone else's, like a missing one, answers the same `404` | | |
| 67 | 71 | | `/agents/abilities` | GET | `includes/agents/rest.php` | `edit_posts` (filterable) | |
| 68 | 72 | | `/agents/draft` | POST | `includes/agents/rest.php` | `edit_users` (filterable). One AI generate call with a strict answer schema; creates nothing. `503` without the AI Client, `502` when the provider fails or answers unreadably | |
| 69 | 73 | | `/agents/trigger-kinds` | GET | `includes/agents/rest.php` | `edit_posts` (filterable) | |
| 70 | 74 | | `/agents/hooks-catalogue` | GET | `includes/agents/rest.php` | `edit_posts` (filterable) | |