diff --git a/.distpackage b/.distpackage
index 6d41aa3..c172ab6 100644
--- a/.distpackage
+++ b/.distpackage
@@ -5,5 +5,3 @@ uninstall.php
build-extensions/
languages/
readme.txt
-README.md
-docs/
diff --git a/.eslintrc.js b/.eslintrc.js
index 741348b..51af2dd 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -25,9 +25,21 @@ module.exports = {
// Allow nested ternary in specific cases (model loader status messages)
'no-nested-ternary': 'warn',
- // JSDoc: Make missing descriptions a warning instead of error
+ // We intentionally use HStack/VStack/etc. for now. Track v0.12 migration
+ // to stable APIs separately when @wordpress/components publishes them.
+ '@wordpress/no-unsafe-wp-apis': 'warn',
+
+ // Unused vars are noise, not bugs. Track + clean up in a dedicated pass.
+ 'no-unused-vars': 'warn',
+
+ // JSDoc: missing descriptions/types are downgraded to warnings — the
+ // recommended preset is too aggressive for our hybrid JS+JSX codebase.
'jsdoc/require-returns-description': 'warn',
'jsdoc/require-param-description': 'warn',
+ 'jsdoc/require-param-type': 'warn',
+ 'jsdoc/require-returns-type': 'warn',
+ 'jsdoc/require-param': 'warn',
+ 'jsdoc/require-returns': 'warn',
// JSX type is valid in WordPress component returns
'jsdoc/no-undefined-types': [
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 66058d8..172bffb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -63,4 +63,9 @@ jobs:
run: composer install --no-interaction --prefer-dist
- name: Run ${{ matrix.check_name }}
- run: ${{ matrix.command }}
\ No newline at end of file
+ run: ${{ matrix.command }}
+
+ # WordPress.org Plugin Check is run locally against an extracted dist zip
+ # in a Playground instance — see Section "Plugin Check" in CONTRIBUTING.md.
+ # Adding it to GHA requires wp-env bootstrap that's worth doing in a focused
+ # follow-up; tracked separately for v0.12.
\ No newline at end of file
diff --git a/.wordpress-org/.gitignore b/.wordpress-org/.gitignore
new file mode 100644
index 0000000..5f027ec
--- /dev/null
+++ b/.wordpress-org/.gitignore
@@ -0,0 +1,7 @@
+# Scratch capture + AI generation artifacts. Design source (_artboards.html)
+# is tracked so the listing assets can be regenerated later.
+_preview.png
+_banner_raw.png
+_icon_raw.png
+_banner_nb2.png
+_icon_nb2.png
diff --git a/.wordpress-org/_artboards.html b/.wordpress-org/_artboards.html
new file mode 100644
index 0000000..ea0f52d
--- /dev/null
+++ b/.wordpress-org/_artboards.html
@@ -0,0 +1,214 @@
+
+
+
+
+Agentic Admin — WP.org Assets
+
+
+
+
+
+
+
+
+
icon-256x256.png
+
+
AA
+
+
+
+
+
+
icon-128x128.png
+
+
AA
+
+
+
+
+
+
banner-772x250.png
+
+
+ AI Connectors · WP 7.0 + Abilities API
+
Agentic Admin
+
+
+
+
+
+
+
banner-1544x500.png
+
+
+ AI Connectors · WP 7.0 + Abilities API
+
Agentic Admin
+
+
+
+
+
+
+
diff --git a/.wordpress-org/banner-1544x500.png b/.wordpress-org/banner-1544x500.png
new file mode 100644
index 0000000..01df806
Binary files /dev/null and b/.wordpress-org/banner-1544x500.png differ
diff --git a/.wordpress-org/banner-772x250.png b/.wordpress-org/banner-772x250.png
new file mode 100644
index 0000000..3d2deb0
Binary files /dev/null and b/.wordpress-org/banner-772x250.png differ
diff --git a/.wordpress-org/icon-128x128.png b/.wordpress-org/icon-128x128.png
new file mode 100644
index 0000000..af31b7c
Binary files /dev/null and b/.wordpress-org/icon-128x128.png differ
diff --git a/.wordpress-org/icon-256x256.png b/.wordpress-org/icon-256x256.png
new file mode 100644
index 0000000..34a55e0
Binary files /dev/null and b/.wordpress-org/icon-256x256.png differ
diff --git a/.wordpress-org/screenshot-1.png b/.wordpress-org/screenshot-1.png
new file mode 100644
index 0000000..f30236b
Binary files /dev/null and b/.wordpress-org/screenshot-1.png differ
diff --git a/.wordpress-org/screenshot-2.png b/.wordpress-org/screenshot-2.png
new file mode 100644
index 0000000..3bcda7b
Binary files /dev/null and b/.wordpress-org/screenshot-2.png differ
diff --git a/.wordpress-org/screenshot-3.png b/.wordpress-org/screenshot-3.png
new file mode 100644
index 0000000..a8ee148
Binary files /dev/null and b/.wordpress-org/screenshot-3.png differ
diff --git a/.wordpress-org/screenshot-4.png b/.wordpress-org/screenshot-4.png
new file mode 100644
index 0000000..26325c3
Binary files /dev/null and b/.wordpress-org/screenshot-4.png differ
diff --git a/.wordpress-org/screenshot-5.png b/.wordpress-org/screenshot-5.png
new file mode 100644
index 0000000..cb786ac
Binary files /dev/null and b/.wordpress-org/screenshot-5.png differ
diff --git a/.wordpress-org/screenshot-6.png b/.wordpress-org/screenshot-6.png
new file mode 100644
index 0000000..168b193
Binary files /dev/null and b/.wordpress-org/screenshot-6.png differ
diff --git a/docs/UI-AUDIT-WP-7.0.md b/docs/UI-AUDIT-WP-7.0.md
new file mode 100644
index 0000000..883aeb8
--- /dev/null
+++ b/docs/UI-AUDIT-WP-7.0.md
@@ -0,0 +1,218 @@
+# UI Audit — WordPress 7.0
+
+**Issue:** [#220 — Wider UI review to match WordPress 7.0 admin styling](https://github.com/pluginslab/wp-agentic-admin/issues/220)
+**Target milestone:** v0.13 — WordPress.org Submission (with v0.14 spillover)
+**Date:** 2026-05-23
+**Plugin version at audit:** 0.12.0
+
+## Framing — what this audit actually is
+
+The issue describes "WordPress 7.0 just shipped and introduces refreshed admin UI styling." After diffing the shipped 7.0 files against 6.9.4 locally, **that framing is partially wrong**. The legacy admin shell (`wp-admin/css/common.css`, button classes, color tokens, card primitives) is visually unchanged in 7.0. Custom admin screens will not look "out of date" on 7.0 by default.
+
+What *did* change and *does* affect this plugin:
+
+1. **`@wordpress/components` jumped from 30.6.5 (in WP 6.9) to 32.2.0 (in WP 7.0)** with breaking default changes — most notably `__nextHasNoMarginBottom = true` on every form control. Stacked controls in our Settings tab will visually collapse.
+2. **`@wordpress/admin-ui` removed from WP Core.** Still maintained in Gutenberg. We don't use it directly today, but anything that imports `@wordpress/admin-ui` from the core script handle will break.
+3. **CSS class `.components-notice__action` removed** from the bundled stylesheet.
+4. **Menu item height 40px → 32px** (denser dropdowns).
+5. **`Button` font-weight changed to 499.**
+6. **`DimensionControl` removed.**
+7. **`Sandbox` default `allowSameOrigin` is now `false`.**
+
+So the real audit goal is twofold:
+- **(A) Functional safety:** survive the 30 → 32 component breaking changes before submission.
+- **(B) Hygiene:** reduce hand-rolled UI in favor of `@wordpress/components`, and adopt WP design tokens (`var(--wp-admin-theme-color)` etc.) so user color schemes apply. This work isn't *forced* by WP 7.0 but it's the right time, and the issue body explicitly asks for it.
+
+## Reference — WP 7.0 component & CSS changes that matter
+
+Sources: `data/wp-includes/css/dist/components/style.css` vs `data/wp-includes.6.9.4.bak/...`; Gutenberg `wp/7.0` CHANGELOG (https://github.com/WordPress/gutenberg/blob/wp/7.0/packages/components/CHANGELOG.md).
+
+### Breaking defaults — components likely already used in our codebase
+
+| Component | Change | Where we use it |
+|---|---|---|
+| `BaseControl`, `TextControl`, `TextareaControl`, `SelectControl`, `CheckboxControl`, **`ToggleControl`**, `ToggleGroupControl`, `SearchControl`, `RangeControl`, `ComboboxControl`, `FormTokenField`, `TreeSelect`, `FocalPointPicker` | `__nextHasNoMarginBottom` defaults to **`true`** (32.0.0). Stacked controls no longer have ~24px bottom margin. | `PluginAbilitiesPanel.jsx` (ToggleControl). All other controls are hand-rolled today — but if we adopt them in fixes, this default applies. |
+| `Button` | font-weight changed to `499` (30.7.0) | Used in `App.jsx`, `ChatContainer.jsx`. May render slightly lighter. |
+| Menu/`DropdownMenu` items | Default item height 40px → 32px (32.0.0) | `ChatInput.jsx` (Dropdown), `MessageItem.jsx` ability list. |
+| `Snackbar` | Default timeout shortened | `ChatContainer.jsx` |
+| `Notice` | `.components-notice__action` CSS class removed; action button now supports `disabled` and may have both `url` and `onClick` | `App.jsx`, `WebGPUFallback.jsx`, `ChatContainer.jsx`. We don't currently target `.components-notice__action` in our SCSS — safe. |
+
+### Removed / replaced
+
+- **`@wordpress/admin-ui`** — bundled in 6.9, not in 7.0. We don't import this — safe.
+- **`DimensionControl`** — removed (32.0.0). We don't use it — safe.
+
+### New that we could adopt
+
+- `Card` `size` prop now accepts an **object** for per-edge padding (30.8.0).
+- `Notice` action button supports `disabled` + can combine `url` and `onClick` (good for the MCP card's "Copy endpoint URL" + "Open docs" pattern).
+- `ConfirmDialog` has new `isBusy` prop (useful for tool execution confirmations).
+- New CSS variable `--wp-components-color-gray-400`.
+
+### Design tokens to adopt (independent of WP 7.0 — WordPress has shipped these for years)
+
+Our `src/extensions/styles/main.scss` contains 62 literal `#2271b1`, 30 literal `#1d2327`, etc. — never `var(--wp-admin-theme-color)`. User color schemes (Modern, Light, Coffee, …) are not respected. WP exposes these on `:root` from `wp-admin/css/forms.css` (7.0) / `common.css` (6.9):
+
+- `--wp-admin-theme-color` (default `#3858e9` in modern, `#2271b1` legacy)
+- `--wp-admin-theme-color-darker-10`, `-darker-20`
+- `--wp-admin-border-width-focus`
+
+## Inventory — our UI surfaces
+
+Fifteen React surfaces plus three SCSS stylesheets. **Most components already use `@wordpress/components` to a non-trivial degree.** The component-library adoption is in better shape than the issue body suggests.
+
+| Surface | File | LOC | `@wordpress/components` used | Status |
+|---|---|---|---|---|
+| Main admin app shell | `src/extensions/App.jsx` | 295 | `Notice`, `Button` | OK |
+| Chat sidebar mount (admin bar) | `src/extensions/admin-sidebar.js` + `styles/admin-sidebar.scss` (326 LOC) | 89 | — | DOM wiring, no React UI to migrate |
+| Chat sidebar React shell | `src/extensions/components/AdminSidebar.jsx` | 214 | `Button` (after close-button PR) | OK after close-button PR |
+| Chat conversation | `src/extensions/components/ChatContainer.jsx` | 993 | `Button`, `Modal`, `Notice`, `Snackbar` | partial; remaining custom areas are the conversation flow itself |
+| Chat input bar | `src/extensions/components/ChatInput.jsx` | 368 | `Dropdown`, `Icon` | mostly custom (product-specific input design) |
+| Individual message + tool-call card | `src/extensions/components/MessageItem.jsx` | 856 | (none) | **truly bespoke design language** — Perplexity-style chat bubbles. Intentional. |
+| Model loader status | `src/extensions/components/ModelStatus.jsx` | 854 | `Button`, `DropdownMenu`, `MenuGroup`, `MenuItem`, `Spinner` (+ `@wordpress/icons`) | already migrated to component primitives; remaining custom is product-specific progress UI |
+| Settings tab | `src/extensions/components/SettingsTab.jsx` | 591 | `Button`, `Card`, `CardHeader`, `CardBody`, `SelectControl`, `Notice`, `ToggleControl` | **already heavily migrated**. Minor nits in §2 below. |
+| Abilities catalog | `src/extensions/components/AbilityBrowser.jsx` | 385 | `Spinner` | could adopt `Card` for the per-ability tiles |
+| Plugin abilities panel | `src/extensions/components/PluginAbilitiesPanel.jsx` | 389 | `Spinner`, `ToggleControl` | OK |
+| Ability picker (in chat input) | `src/extensions/components/AbilityPicker.jsx` | 131 | — | custom dropdown |
+| File view (tool result) | `src/extensions/components/FileView.jsx` | 80 | — | custom |
+| WebGPU fallback | `src/extensions/components/WebGPUFallback.jsx` | 161 | `Notice`, `ExternalLink` | OK |
+| Editor sidebar (Gutenberg plugin) | `src/extensions/components/EditorSidebar.jsx` + `styles/editor-sidebar.scss` (246 LOC) | 137 | — | out of scope for this audit (separate surface) |
+| Parked | `src/extensions/components/VoiceButton.jsx` | 400 | — | parked per roadmap |
+
+**Correction note:** an earlier draft of this audit claimed `SettingsTab.jsx` and `ModelStatus.jsx` had zero `@wordpress/components` usage. That was wrong — the inventory script missed multi-line imports (`import {\n` with the brace on its own line). Both files are migrated to a reasonable degree. The v0.13 fix list below was tightened accordingly.
+
+**SCSS:**
+- `src/extensions/styles/main.scss` — **3,599 LOC**, 62× literal `#2271b1`, zero `var(--wp-*)` token usage
+- `src/extensions/styles/admin-sidebar.scss` — 326 LOC
+- `src/extensions/styles/editor-sidebar.scss` — 246 LOC
+
+## Per-surface findings
+
+Screenshots in `docs/audit-assets/wp-7.0-before/`.
+
+### 1. Main admin page (`agentic-admin`)
+
+
+
+| # | Finding | Priority | Recommended fix |
+|---|---|---|---|
+| 1.1 | Hand-rolled tab strip ("Chat / Abilities / Plugin Abilities" + "Settings" gear button) using `wp-agentic-admin-tab` class | P1 | Use `@wordpress/components` `TabPanel` or `Tabs` (32.0.0). Keeps keyboard nav + a11y for free. |
+| 1.2 | "Copy All" + "Clear Chat" buttons at top of chat panel are hand-rolled | P2 | Use `Button` `variant="tertiary"` for "Copy All", `variant="secondary"` for "Clear Chat" (`isDestructive` if confirming). |
+| 1.3 | Greeting bubble has an inline timestamp + copy button — custom card | P2 | Acceptable as a custom chat-bubble design (this is the product's identity). No fix. |
+
+### 2. Settings tab
+
+**Current state on `dev` (no MCP yet):**
+
+
+**Future state after PR #216 merges (includes MCP Endpoint card):**
+
+
+The "HTTP 404" visible in the second screenshot is **not a bug** — it was a stale build artifact: a JS bundle built on the `feat/001-mcp-server-endpoint` branch was still on disk when viewing the dev branch (which doesn't have the PHP routes yet). After rebuilding on dev, the MCP card disappears as expected. PR #216 ships both halves together.
+
+| # | Finding | Priority | Recommended fix |
+|---|---|---|---|
+| 2.1 | KB progress bar uses hand-rolled `
` with hardcoded `#007cba` | P2 | Replace with `ProgressBar` (stabilised in @wordpress/components 32.0.0) or tokenise the literal. |
+| 2.2 | KB status display is a hand-rolled `
` with custom class | P2 | Definition-list pattern is fine, or migrate to `__experimentalGrid`. Low priority. |
+| 2.3 | "Context Window per Model" + "Remote Provider Context Window" h3 headings sit OUTSIDE any `Card` (KB / GPU Info / Thinking Mode each have a `CardHeader`). Inconsistent grouping. | P2 | Wrap each section's heading in `CardHeader` for consistency, OR pull all section headings out of cards. Cosmetic. |
+| 2.4 | "Disable thinking before tool selection" + "Disable thinking after tool results" rendered as two stacked `ToggleControl`s | P2 | These are independent toggles, so `ToggleControl` is correct. Worth verifying spacing in WP 7.0 since `__nextHasNoMarginBottom` defaults to `true` — controls may visually collapse. |
+| 2.5 | Inline `style={{...}}` calls scattered across the file | P3 | Move to SCSS. Pure hygiene. |
+| 2.6 | MCP Endpoint card (post-#216) has its own custom toggle and "Save MCP settings" button | P1 (post-#216) | Replace custom toggle with `ToggleControl`. Replace Save button with `Button variant="primary"`. Migrate when #216 lands. |
+
+### 3. Abilities tab
+
+
+
+| # | Finding | Priority | Recommended fix |
+|---|---|---|---|
+| 3.1 | Two-column card grid is clean and consistent; layout is fine | — | Keep. |
+| 3.2 | Each ability card is hand-rolled with title + description + monospace ID + Execute button | P2 | Migrate the card shell to `Card`/`CardHeader`/`CardBody`/`CardFooter`. Keep the monospace ID block as-is (custom). |
+| 3.3 | Highlighted (red border) ability cards (e.g. "Clear Log Records", "Deactivate Plugin") | P2 | If marking destructive abilities, set `isDestructive` on the Execute `Button` instead of the card border, or use a `Notice` `status="warning"` inside the card. |
+| 3.4 | "Execute" button on every card | P2 | `Button variant="primary"` or `variant="secondary"` consistent with severity. |
+
+### 4. Plugin Abilities tab (empty state)
+
+
+
+| # | Finding | Priority | Recommended fix |
+|---|---|---|---|
+| 4.1 | Empty state uses a `Notice` (blue left border) inside a card | — | Looks correct. Keep. |
+| 4.2 | Did not capture a populated state (would need a third-party plugin exposing Abilities API) | — | Capture after fixes land, or add a fixture plugin to e2e screenshots. |
+
+### 5. Admin-bar chat sidebar
+
+
+
+| # | Finding | Priority | Recommended fix |
+|---|---|---|---|
+| 5.1 | Right-rail sidebar (~400px) opens cleanly on any wp-admin page | — | Pattern works. Keep. |
+| 5.2 | Header ("AI Assistant" + status pill + 3-dot menu) hand-rolled | P2 | `Card` + `__experimentalHStack` for header layout; menu via `DropdownMenu` (note: 32px item height in 7.0). |
+| 5.3 | Bottom input area duplicated from main view | P2 | Already shared as `ChatInput.jsx`. Confirm no divergent styling. |
+| 5.4 | No close (×) button visible in header — relies on overlay click or Escape | P1 | Add explicit close `Button icon={close}` for discoverability. |
+
+### 6. Model status / loader (`ModelStatus.jsx`)
+
+Not captured in isolation (would need to trigger model load from cold). 854 LOC, zero `@wordpress/components`.
+
+| # | Finding | Priority | Recommended fix |
+|---|---|---|---|
+| 6.1 | Fully hand-rolled progress UI + model picker + GPU detection display | P1 | This is the largest single hand-rolled surface. Out of scope to rewrite for v0.13 — keep on v0.14 list. |
+| 6.2 | Status pill at bottom of every view ("Qwen 3 1.7B (Q4 F16) ready" + PERSISTENT + Context meter) | P2 | The pill itself is fine. Context meter (the inline progress bar) could become `ProgressBar` (32.0.0 stabilised). |
+
+### 7. SCSS — design tokens
+
+| # | Finding | Priority | Recommended fix |
+|---|---|---|---|
+| 7.1 | 62× literal `#2271b1` — accent color hardcoded; user color schemes ignored | P1 | Replace with `var(--wp-admin-theme-color)`. Fall back via `@supports` or just commit; WP has shipped this token since 5.7. |
+| 7.2 | Other literal tokenisable colors (`#1d2327` text, `#646970` muted, `#c3c4c7` border, `#f6f7f7` surface) | P2 | These are stable across WP themes and aren't tokenised in core. Acceptable as literals, but move to one `$variables` SCSS partial so they're swappable. |
+| 7.3 | No tests for color-scheme override | P2 | Add an e2e check that activates a non-default color scheme (e.g. "Modern") and screenshots the plugin's primary accent. |
+
+## Prioritized fix list — release split
+
+### v0.13 (submission gate) — must-do
+
+These are functional safety items that block a confident WP 7.0 submission. The list narrowed from the initial draft after (a) discovering the "MCP 404" was a stale-build artifact, not a bug, and (b) re-inventorying showed `SettingsTab.jsx` and `ModelStatus.jsx` were already migrated to `@wordpress/components`.
+
+1. **[P1] Replace 62× `#2271b1` literal with `var(--wp-admin-theme-color)`** in `main.scss` + `admin-sidebar.scss` — done in `feat/220-wp-admin-theme-token`
+2. **[P1] Add explicit close button to admin-bar sidebar header** — done in `feat/220-admin-sidebar-close-button`
+
+Both PRs include before/after screenshots in this audit's `audit-assets/wp-7.0-before/`.
+
+### v0.14 (post-submission polish)
+
+3. **[P2] SettingsTab nits**: KB progress bar `
` → `ProgressBar` (or token), wrap Context-Window section heading in `CardHeader` for consistency, move inline styles to SCSS.
+4. **[P2] Verify `__nextHasNoMarginBottom = true` default doesn't visually collapse stacked `ToggleControl`s in Thinking Mode** on WP 7.0.
+5. **[P2] Migrate MCP Endpoint card to `ToggleControl` + `Button variant="primary"`** — depends on #216 landing first.
+6. **[P2] Abilities tab → `Card` primitives** for per-ability tiles.
+7. **[P2] Tabs → `TabPanel`/`Tabs`** (keyboard nav + a11y for free).
+8. **[P2] Move SCSS literal colors to a `$tokens.scss` partial.**
+9. **[P2] Capture populated Plugin Abilities tab state** (add a fixture plugin to e2e).
+10. **[P2] ModelStatus.jsx review** — already uses `Button`/`DropdownMenu`/`Spinner`. Remaining custom is the progress UI; treat as its own PR if/when needed.
+
+## Out of scope
+
+- **EditorSidebar.jsx** — Gutenberg block-editor side panel. Separate audit when we revisit the editor flow.
+- **VoiceButton.jsx** — parked per [v1 roadmap](.claude/plans/...).
+- Information architecture changes to the Settings tab (sections, ordering). Issue body explicitly says "Restructuring the Settings IA is a separate issue if needed."
+
+## Verification done during audit
+
+- Local docker stack upgraded from WP 6.9.4 → 7.0 (manual core swap, `data/wp-admin.6.9.4.bak/` + `data/wp-includes.6.9.4.bak/` retained for diff)
+- WP 7.0 dashboard rendered (`docs/audit-assets/wp-7.0-before/00-wp-dashboard.png`)
+- All 4 plugin tabs + admin-bar sidebar captured
+- CSS diff: `wp-admin/css/common.css` 6.9.4 → 7.0 — no removed/renamed admin classes
+- Component CSS diff: only `.components-notice__action` removed from the bundled stylesheet — we do not target it
+
+## Screenshot index
+
+- `00-wp-dashboard.png` — WP 7.0 baseline (no plugin)
+- `01-plugin-main-initial.png` — plugin main page, Chat tab
+- `02-settings-tab-full.png` — Settings tab as it appears with stale MCP build (post-#216 preview)
+- `02b-settings-tab-on-dev.png` — Settings tab as it actually exists on `dev` today
+- `03-abilities-tab.png` — Abilities catalog (full page)
+- `04-plugin-abilities-tab.png` — Plugin Abilities tab, empty state
+- `05-admin-sidebar-on-posts.png` — admin-bar chat sidebar overlaid on Posts list
+- `06-after-token-migration.png` — plugin main after `var(--wp-admin-theme-color)` migration, default Fresh scheme (visually identical to baseline)
+- `07-token-with-modern-scheme.png` — token migration with Modern color scheme active
+- `08-token-with-sunrise-scheme.png` — token migration with Sunrise scheme: orange accents now flow into plugin UI
+- `09-admin-sidebar-with-close.png` — admin-bar sidebar after close-button PR (× icon top-right)
diff --git a/docs/audit-assets/wp-7.0-before/00-wp-dashboard.png b/docs/audit-assets/wp-7.0-before/00-wp-dashboard.png
new file mode 100644
index 0000000..6db7ea1
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/00-wp-dashboard.png differ
diff --git a/docs/audit-assets/wp-7.0-before/01-plugin-main-initial.png b/docs/audit-assets/wp-7.0-before/01-plugin-main-initial.png
new file mode 100644
index 0000000..72ae410
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/01-plugin-main-initial.png differ
diff --git a/docs/audit-assets/wp-7.0-before/02-settings-tab-full.png b/docs/audit-assets/wp-7.0-before/02-settings-tab-full.png
new file mode 100644
index 0000000..ba04628
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/02-settings-tab-full.png differ
diff --git a/docs/audit-assets/wp-7.0-before/02b-settings-tab-on-dev.png b/docs/audit-assets/wp-7.0-before/02b-settings-tab-on-dev.png
new file mode 100644
index 0000000..333f46d
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/02b-settings-tab-on-dev.png differ
diff --git a/docs/audit-assets/wp-7.0-before/03-abilities-tab.png b/docs/audit-assets/wp-7.0-before/03-abilities-tab.png
new file mode 100644
index 0000000..c843cbc
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/03-abilities-tab.png differ
diff --git a/docs/audit-assets/wp-7.0-before/04-plugin-abilities-tab.png b/docs/audit-assets/wp-7.0-before/04-plugin-abilities-tab.png
new file mode 100644
index 0000000..ade583a
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/04-plugin-abilities-tab.png differ
diff --git a/docs/audit-assets/wp-7.0-before/05-admin-sidebar-on-posts.png b/docs/audit-assets/wp-7.0-before/05-admin-sidebar-on-posts.png
new file mode 100644
index 0000000..afd1c9c
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/05-admin-sidebar-on-posts.png differ
diff --git a/docs/audit-assets/wp-7.0-before/06-after-token-migration.png b/docs/audit-assets/wp-7.0-before/06-after-token-migration.png
new file mode 100644
index 0000000..7ebd12c
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/06-after-token-migration.png differ
diff --git a/docs/audit-assets/wp-7.0-before/07-token-with-modern-scheme.png b/docs/audit-assets/wp-7.0-before/07-token-with-modern-scheme.png
new file mode 100644
index 0000000..9f08493
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/07-token-with-modern-scheme.png differ
diff --git a/docs/audit-assets/wp-7.0-before/08-token-with-sunrise-scheme.png b/docs/audit-assets/wp-7.0-before/08-token-with-sunrise-scheme.png
new file mode 100644
index 0000000..9ec1e9b
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/08-token-with-sunrise-scheme.png differ
diff --git a/docs/audit-assets/wp-7.0-before/09-admin-sidebar-with-close.png b/docs/audit-assets/wp-7.0-before/09-admin-sidebar-with-close.png
new file mode 100644
index 0000000..ea74cc3
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/09-admin-sidebar-with-close.png differ
diff --git a/docs/audit-assets/wp-7.0-before/10-observe-close-button.png b/docs/audit-assets/wp-7.0-before/10-observe-close-button.png
new file mode 100644
index 0000000..ca6fe49
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/10-observe-close-button.png differ
diff --git a/docs/audit-assets/wp-7.0-before/a1-settings-via-tabpanel.png b/docs/audit-assets/wp-7.0-before/a1-settings-via-tabpanel.png
new file mode 100644
index 0000000..94262c9
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/a1-settings-via-tabpanel.png differ
diff --git a/docs/audit-assets/wp-7.0-before/a1-tabs-tabpanel.png b/docs/audit-assets/wp-7.0-before/a1-tabs-tabpanel.png
new file mode 100644
index 0000000..e2f9867
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/a1-tabs-tabpanel.png differ
diff --git a/docs/audit-assets/wp-7.0-before/a2-abilities-cards.png b/docs/audit-assets/wp-7.0-before/a2-abilities-cards.png
new file mode 100644
index 0000000..016249e
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/a2-abilities-cards.png differ
diff --git a/docs/audit-assets/wp-7.0-before/a5-settings-tables.png b/docs/audit-assets/wp-7.0-before/a5-settings-tables.png
new file mode 100644
index 0000000..4b22514
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/a5-settings-tables.png differ
diff --git a/docs/audit-assets/wp-7.0-before/a6-chat-hstack.png b/docs/audit-assets/wp-7.0-before/a6-chat-hstack.png
new file mode 100644
index 0000000..17fa35f
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/a6-chat-hstack.png differ
diff --git a/docs/audit-assets/wp-7.0-before/a7-plugin-abilities-panel.png b/docs/audit-assets/wp-7.0-before/a7-plugin-abilities-panel.png
new file mode 100644
index 0000000..b2df0f3
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/a7-plugin-abilities-panel.png differ
diff --git a/docs/audit-assets/wp-7.0-before/b1-bundle-dropdown.png b/docs/audit-assets/wp-7.0-before/b1-bundle-dropdown.png
new file mode 100644
index 0000000..28c6096
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/b1-bundle-dropdown.png differ
diff --git a/docs/audit-assets/wp-7.0-before/b1-composer.png b/docs/audit-assets/wp-7.0-before/b1-composer.png
new file mode 100644
index 0000000..affdbe6
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/b1-composer.png differ
diff --git a/docs/audit-assets/wp-7.0-before/b2-model-status.png b/docs/audit-assets/wp-7.0-before/b2-model-status.png
new file mode 100644
index 0000000..9054188
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/b2-model-status.png differ
diff --git a/docs/audit-assets/wp-7.0-before/c1-chat-rewrite.png b/docs/audit-assets/wp-7.0-before/c1-chat-rewrite.png
new file mode 100644
index 0000000..cb5a1e9
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/c1-chat-rewrite.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-chat-tab.png b/docs/audit-assets/wp-7.0-before/glitch-chat-tab.png
new file mode 100644
index 0000000..a493834
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-chat-tab.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-1.png b/docs/audit-assets/wp-7.0-before/glitch-fix-1.png
new file mode 100644
index 0000000..e5d58da
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-1.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-abilities-padding.png b/docs/audit-assets/wp-7.0-before/glitch-fix-abilities-padding.png
new file mode 100644
index 0000000..e4b65a7
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-abilities-padding.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-abilities.png b/docs/audit-assets/wp-7.0-before/glitch-fix-abilities.png
new file mode 100644
index 0000000..addbd26
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-abilities.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius-2.png b/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius-2.png
new file mode 100644
index 0000000..cfd6d8b
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius-2.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius-3.png b/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius-3.png
new file mode 100644
index 0000000..1c66cbd
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius-3.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius.png b/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius.png
new file mode 100644
index 0000000..bc727ff
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-card-radius.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-chatheader-border.png b/docs/audit-assets/wp-7.0-before/glitch-fix-chatheader-border.png
new file mode 100644
index 0000000..bb72c96
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-chatheader-border.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-connector-empty.png b/docs/audit-assets/wp-7.0-before/glitch-fix-connector-empty.png
new file mode 100644
index 0000000..0560361
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-connector-empty.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-fullwidth.png b/docs/audit-assets/wp-7.0-before/glitch-fix-fullwidth.png
new file mode 100644
index 0000000..b06a441
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-fullwidth.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-p-margin.png b/docs/audit-assets/wp-7.0-before/glitch-fix-p-margin.png
new file mode 100644
index 0000000..6de13e4
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-p-margin.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-panel-shadow.png b/docs/audit-assets/wp-7.0-before/glitch-fix-panel-shadow.png
new file mode 100644
index 0000000..0e65f3e
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-panel-shadow.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-pill-in-toolbar-2.png b/docs/audit-assets/wp-7.0-before/glitch-fix-pill-in-toolbar-2.png
new file mode 100644
index 0000000..b38275c
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-pill-in-toolbar-2.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-pill-in-toolbar.png b/docs/audit-assets/wp-7.0-before/glitch-fix-pill-in-toolbar.png
new file mode 100644
index 0000000..b38275c
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-pill-in-toolbar.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-pill-left.png b/docs/audit-assets/wp-7.0-before/glitch-fix-pill-left.png
new file mode 100644
index 0000000..362047e
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-pill-left.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-plugin-abilities.png b/docs/audit-assets/wp-7.0-before/glitch-fix-plugin-abilities.png
new file mode 100644
index 0000000..e4514cf
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-plugin-abilities.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-settings.png b/docs/audit-assets/wp-7.0-before/glitch-fix-settings.png
new file mode 100644
index 0000000..35eb783
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-settings.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-2.png b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-2.png
new file mode 100644
index 0000000..c7f0a0b
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-2.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-3.png b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-3.png
new file mode 100644
index 0000000..c7f0a0b
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-3.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-4.png b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-4.png
new file mode 100644
index 0000000..2862a15
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar-4.png differ
diff --git a/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar.png b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar.png
new file mode 100644
index 0000000..cbeef22
Binary files /dev/null and b/docs/audit-assets/wp-7.0-before/glitch-fix-sidebar.png differ
diff --git a/includes/class-admin-bar.php b/includes/class-admin-bar.php
deleted file mode 100644
index 69b62b9..0000000
--- a/includes/class-admin-bar.php
+++ /dev/null
@@ -1,156 +0,0 @@
-add_node(
- array(
- 'id' => 'wp-agentic-admin-sidebar-toggle',
- 'parent' => 'top-secondary',
- 'title' => '' . esc_html__( 'AI Assistant', 'agentic-admin' ) . '',
- 'href' => '#',
- 'meta' => array(
- 'class' => 'wp-agentic-admin-toggle',
- 'title' => __( 'AI Assistant', 'agentic-admin' ),
- ),
- )
- );
- }
-
- /**
- * Enqueue sidebar scripts and styles on all admin pages.
- *
- * @param string $hook The current admin page hook.
- */
- public function enqueue_scripts( string $hook ): void {
- if ( ! current_user_can( 'manage_options' ) ) {
- return;
- }
-
- // Skip on the plugin's own settings page — the full app is already loaded.
- if ( 'toplevel_page_agentic-admin' === $hook ) {
- return;
- }
-
- $asset_file = WP_AGENTIC_ADMIN_PLUGIN_DIR . 'build-extensions/admin-sidebar.asset.php';
-
- if ( ! file_exists( $asset_file ) ) {
- return;
- }
-
- $asset = require $asset_file;
- $deps = isset( $asset['dependencies'] ) ? (array) $asset['dependencies'] : array( 'wp-element' );
- $ver = isset( $asset['version'] ) ? $asset['version'] : WP_AGENTIC_ADMIN_VERSION;
-
- // Enqueue WordPress components styles.
- wp_enqueue_style( 'wp-components' );
-
- // Enqueue sidebar styles.
- $css_file = WP_AGENTIC_ADMIN_PLUGIN_DIR . 'build-extensions/admin-sidebar.css';
- if ( file_exists( $css_file ) ) {
- wp_enqueue_style(
- 'wp-agentic-admin-sidebar-style',
- WP_AGENTIC_ADMIN_PLUGIN_URL . 'build-extensions/admin-sidebar.css',
- array( 'wp-components', 'dashicons' ),
- filemtime( $css_file )
- );
- }
-
- // Register and enqueue sidebar script.
- wp_register_script(
- 'wp-agentic-admin-sidebar',
- WP_AGENTIC_ADMIN_PLUGIN_URL . 'build-extensions/admin-sidebar.js',
- $deps,
- $ver,
- true
- );
-
- // Localize with the same data as the admin page.
- wp_localize_script(
- 'wp-agentic-admin-sidebar',
- 'wpAgenticAdmin',
- Admin_Page::get_localized_data()
- );
-
- wp_enqueue_script( 'wp-agentic-admin-sidebar' );
- }
-
- /**
- * Render the sidebar container in the admin footer.
- */
- public function render_sidebar_container(): void {
- if ( ! current_user_can( 'manage_options' ) ) {
- return;
- }
-
- // Don't render on the plugin page — the full app handles it.
- $screen = get_current_screen();
- if ( $screen && 'toplevel_page_agentic-admin' === $screen->id ) {
- return;
- }
- ?>
-
-
- esc_url_raw( rest_url( 'wp-abilities/v1' ) ),
+ 'restRoot' => esc_url_raw( rest_url() ),
+ 'connectorsRestUrl' => esc_url_raw( rest_url( 'wp-agentic-admin/v1/connectors' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'userId' => get_current_user_id(),
'pluginUrl' => esc_url( WP_AGENTIC_ADMIN_PLUGIN_URL ),
diff --git a/includes/class-connectors.php b/includes/class-connectors.php
new file mode 100644
index 0000000..4c78a96
--- /dev/null
+++ b/includes/class-connectors.php
@@ -0,0 +1,459 @@
+ 'GET',
+ 'callback' => array( static::class, 'list_connectors' ),
+ 'permission_callback' => array( static::class, 'check_permission' ),
+ )
+ );
+
+ \register_rest_route(
+ 'wp-agentic-admin/v1',
+ '/connectors/chat/completions',
+ array(
+ 'methods' => 'POST',
+ 'callback' => array( static::class, 'chat_completion' ),
+ 'permission_callback' => array( static::class, 'check_permission' ),
+ 'args' => array(
+ 'connector_id' => array(
+ 'required' => true,
+ 'type' => 'string',
+ ),
+ 'model_id' => array(
+ 'required' => false,
+ 'type' => 'string',
+ 'default' => '',
+ ),
+ 'messages' => array(
+ 'required' => true,
+ 'type' => 'array',
+ ),
+ ),
+ )
+ );
+ }
+
+ /**
+ * Admin-only permission check.
+ *
+ * @return bool
+ */
+ public static function check_permission(): bool {
+ return \current_user_can( 'manage_options' );
+ }
+
+ /**
+ * GET /v1/connectors — list AI provider connectors.
+ *
+ * @return \WP_REST_Response
+ */
+ public static function list_connectors(): \WP_REST_Response {
+ $out = array(
+ 'wp_supports_connectors' => function_exists( '\\wp_get_connectors' ),
+ 'options_url' => \admin_url( 'options-connectors.php' ),
+ 'connectors' => array(),
+ );
+
+ if ( ! function_exists( '\\wp_get_connectors' ) ) {
+ return new \WP_REST_Response( $out, 200 );
+ }
+
+ $all = \wp_get_connectors();
+ $ai_registry = null;
+ if ( class_exists( '\\WordPress\\AiClient\\AiClient' ) ) {
+ try {
+ $ai_registry = \WordPress\AiClient\AiClient::defaultRegistry();
+ } catch ( \Exception $e ) {
+ $ai_registry = null;
+ }
+ }
+
+ foreach ( $all as $id => $data ) {
+ if ( ! isset( $data['type'] ) || 'ai_provider' !== $data['type'] ) {
+ continue;
+ }
+
+ $is_connected = false;
+ if ( null !== $ai_registry ) {
+ try {
+ $is_connected = $ai_registry->hasProvider( $id )
+ && $ai_registry->isProviderConfigured( $id );
+ } catch ( \Exception $e ) {
+ $is_connected = false;
+ }
+ }
+
+ $models = array();
+ if ( $is_connected && null !== $ai_registry ) {
+ try {
+ $provider_class = $ai_registry->getProviderClassName( $id );
+ if ( $provider_class && method_exists( $provider_class, 'modelMetadataDirectory' ) ) {
+ $directory = $provider_class::modelMetadataDirectory();
+ foreach ( $directory->listModelMetadata() as $meta ) {
+ $models[] = array(
+ 'id' => $meta->getId(),
+ 'name' => $meta->getName(),
+ );
+ }
+ }
+ } catch ( \Exception $e ) {
+ $models = array();
+ }
+ }
+
+ $out['connectors'][] = array(
+ 'id' => $id,
+ 'name' => $data['name'] ?? $id,
+ 'description' => $data['description'] ?? '',
+ 'logo_url' => $data['logo_url'] ?? null,
+ 'is_connected' => $is_connected,
+ 'models' => $models,
+ );
+ }
+
+ return new \WP_REST_Response( $out, 200 );
+ }
+
+ /**
+ * POST /v1/connectors/chat/completions — proxy a chat completion through
+ * the AI Client to a configured connector's provider.
+ *
+ * KNOWN LIMITATIONS (documented for honesty, will be addressed in follow-up):
+ * - Non-streaming only. AI Client doesn't expose a streaming API for
+ * text generation, so this returns the full assistant message at once.
+ * - No tool/function calling support yet.
+ *
+ * Guards: enforces MAX_MESSAGES, MAX_TOTAL_CHARS, and a per-user
+ * RATE_LIMIT_PER_MINUTE via a transient. These cap potential paid-
+ * provider spend triggered through this endpoint.
+ *
+ * @param \WP_REST_Request $request REST request.
+ * @return \WP_REST_Response|\WP_Error
+ */
+ public static function chat_completion( \WP_REST_Request $request ) {
+ if ( ! class_exists( '\\WordPress\\AiClient\\AiClient' ) ) {
+ return new \WP_Error(
+ 'wpaa_no_ai_client',
+ 'WP AI Client is not available (requires WordPress 7.0+).',
+ array( 'status' => 501 )
+ );
+ }
+
+ $connector_id = (string) $request->get_param( 'connector_id' );
+ $model_id = (string) $request->get_param( 'model_id' );
+ $messages = (array) $request->get_param( 'messages' );
+
+ if ( '' === $connector_id || empty( $messages ) ) {
+ return new \WP_Error(
+ 'wpaa_bad_request',
+ 'connector_id and messages are required.',
+ array( 'status' => 400 )
+ );
+ }
+
+ if ( count( $messages ) > self::MAX_MESSAGES ) {
+ return new \WP_Error(
+ 'wpaa_too_many_messages',
+ sprintf( 'Too many messages: limit is %d per request.', self::MAX_MESSAGES ),
+ array( 'status' => 413 )
+ );
+ }
+
+ $total_chars = 0;
+ foreach ( $messages as $msg ) {
+ if ( is_array( $msg ) && isset( $msg['content'] ) ) {
+ $total_chars += strlen( (string) $msg['content'] );
+ }
+ }
+ if ( $total_chars > self::MAX_TOTAL_CHARS ) {
+ return new \WP_Error(
+ 'wpaa_payload_too_large',
+ sprintf( 'Combined message content exceeds %d characters.', self::MAX_TOTAL_CHARS ),
+ array( 'status' => 413 )
+ );
+ }
+
+ $rate_limit_error = self::check_rate_limit();
+ if ( null !== $rate_limit_error ) {
+ return $rate_limit_error;
+ }
+
+ try {
+ $registry = \WordPress\AiClient\AiClient::defaultRegistry();
+ if ( ! $registry->hasProvider( $connector_id ) ) {
+ return new \WP_Error(
+ 'wpaa_unknown_connector',
+ sprintf( 'Connector "%s" is not registered with the AI Client.', $connector_id ),
+ array( 'status' => 404 )
+ );
+ }
+ if ( ! $registry->isProviderConfigured( $connector_id ) ) {
+ return new \WP_Error(
+ 'wpaa_unconfigured_connector',
+ sprintf( 'Connector "%s" is not configured (missing API key).', $connector_id ),
+ array( 'status' => 400 )
+ );
+ }
+
+ // Resolve a specific model if requested; fall back to AI Client
+ // auto-discovery if none provided or resolution fails.
+ $model = null;
+ if ( '' !== $model_id ) {
+ try {
+ $provider = $registry->getProviderModel( $connector_id, $model_id );
+ $model = $provider;
+ } catch ( \Exception $e ) {
+ $model = null;
+ }
+ }
+
+ $result = self::generate_with_structured_messages( $messages, $model );
+ $text = method_exists( $result, 'toText' ) ? $result->toText() : (string) $result;
+
+ // Return an OpenAI-shaped non-streaming response so the existing
+ // chat orchestrator code path can consume it without changes.
+ $response = array(
+ 'id' => 'chatcmpl-' . wp_generate_uuid4(),
+ 'object' => 'chat.completion',
+ 'created' => time(),
+ 'model' => $connector_id,
+ 'choices' => array(
+ array(
+ 'index' => 0,
+ 'message' => array(
+ 'role' => 'assistant',
+ 'content' => $text,
+ ),
+ 'finish_reason' => 'stop',
+ ),
+ ),
+ );
+
+ return new \WP_REST_Response( $response, 200 );
+ } catch ( \Exception $e ) {
+ return new \WP_Error(
+ 'wpaa_connector_error',
+ $e->getMessage(),
+ array( 'status' => 500 )
+ );
+ }
+ }
+
+ /**
+ * Generate a text result through the AI Client using structured
+ * roles so the connector can preserve user / assistant turns and
+ * pass any system instruction as a top-level model directive.
+ *
+ * Falls back to a single concatenated prompt only if the AI Client
+ * structural builder isn't available at runtime.
+ *
+ * @param array> $messages OpenAI-style chat messages.
+ * @param mixed|null $model Optional resolved AI Client model.
+ * @return mixed The generated AI Client text result.
+ */
+ private static function generate_with_structured_messages( array $messages, $model ) {
+ $system_instructions = array();
+ $turns = array();
+
+ foreach ( $messages as $msg ) {
+ if ( ! is_array( $msg ) || ! isset( $msg['content'] ) ) {
+ continue;
+ }
+ $role = isset( $msg['role'] ) ? (string) $msg['role'] : 'user';
+ $content = (string) $msg['content'];
+
+ if ( 'system' === $role ) {
+ $system_instructions[] = $content;
+ continue;
+ }
+ $turns[] = array(
+ 'role' => $role,
+ 'content' => $content,
+ );
+ }
+
+ // History must start with a user turn (most providers — Anthropic
+ // in particular — reject leading assistant turns). Drop synthetic
+ // assistant-led messages like the welcome card until the first
+ // real user message.
+ while ( ! empty( $turns ) && 'user' !== $turns[0]['role'] ) {
+ array_shift( $turns );
+ }
+
+ $structural_supported = class_exists( '\\WordPress\\AiClient\\AiClient' )
+ && class_exists( '\\WordPress\\AiClient\\Messages\\DTO\\Message' )
+ && class_exists( '\\WordPress\\AiClient\\Messages\\DTO\\MessagePart' )
+ && class_exists( '\\WordPress\\AiClient\\Messages\\Enums\\MessageRoleEnum' );
+
+ if ( ! $structural_supported || empty( $turns ) ) {
+ // Final fallback: lossy concatenation. Only reached when the
+ // AI Client builder classes are missing or the message list
+ // had no user/assistant turns at all.
+ $prompt = self::flatten_messages_lossy(
+ array_merge(
+ array_map(
+ static function ( $s ) {
+ return array(
+ 'role' => 'system',
+ 'content' => $s,
+ );
+ },
+ $system_instructions
+ ),
+ $turns
+ )
+ );
+ return null === $model
+ ? \WordPress\AiClient\AiClient::generateTextResult( $prompt )
+ : \WordPress\AiClient\AiClient::generateTextResult( $prompt, $model );
+ }
+
+ $role_enum = '\\WordPress\\AiClient\\Messages\\Enums\\MessageRoleEnum';
+ $messages_dto = array();
+ foreach ( $turns as $turn ) {
+ $enum_role = 'assistant' === $turn['role']
+ ? $role_enum::model()
+ : $role_enum::user();
+ $messages_dto[] = new \WordPress\AiClient\Messages\DTO\Message(
+ $enum_role,
+ array( new \WordPress\AiClient\Messages\DTO\MessagePart( $turn['content'] ) )
+ );
+ }
+
+ // Use the latest turn as the active prompt and pass the rest as history,
+ // matching PromptBuilder's expected shape.
+ $latest = array_pop( $messages_dto );
+ $history = $messages_dto;
+
+ $builder = \WordPress\AiClient\AiClient::prompt();
+ foreach ( $latest->getParts() as $part ) {
+ $builder = $builder->withMessageParts( $part );
+ }
+ if ( ! empty( $history ) ) {
+ $builder = $builder->withHistory( ...$history );
+ }
+ if ( ! empty( $system_instructions ) ) {
+ $builder = $builder->usingSystemInstruction(
+ implode( "\n\n", $system_instructions )
+ );
+ }
+ if ( null !== $model ) {
+ $builder = $builder->usingModel( $model );
+ }
+
+ return $builder->generateTextResult();
+ }
+
+ /**
+ * Concatenate messages into a single prompt as a last-resort fallback
+ * when the structural Message DTOs are unavailable.
+ *
+ * @param array $messages OpenAI-style chat messages.
+ * @return string
+ */
+ private static function flatten_messages_lossy( array $messages ): string {
+ $lines = array();
+ foreach ( $messages as $msg ) {
+ if ( ! is_array( $msg ) || ! isset( $msg['content'] ) ) {
+ continue;
+ }
+ $role = isset( $msg['role'] ) ? ucfirst( (string) $msg['role'] ) : 'User';
+ $lines[] = $role . ': ' . (string) $msg['content'];
+ }
+ return implode( "\n\n", $lines );
+ }
+
+ /**
+ * Per-user rate limit for chat completions.
+ *
+ * Caps at RATE_LIMIT_PER_MINUTE requests per user per rolling 60s
+ * window via a transient counter. Returns a WP_Error 429 when the
+ * cap is exceeded, null otherwise.
+ *
+ * @return \WP_Error|null
+ */
+ private static function check_rate_limit(): ?\WP_Error {
+ $user_id = \get_current_user_id();
+ if ( ! $user_id ) {
+ return null;
+ }
+
+ $key = 'wpaa_conn_rl_' . $user_id;
+ $count = (int) \get_transient( $key );
+
+ if ( $count >= self::RATE_LIMIT_PER_MINUTE ) {
+ return new \WP_Error(
+ 'wpaa_rate_limited',
+ sprintf(
+ 'Connector chat completions are rate-limited to %d requests per minute.',
+ self::RATE_LIMIT_PER_MINUTE
+ ),
+ array( 'status' => 429 )
+ );
+ }
+
+ \set_transient( $key, $count + 1, MINUTE_IN_SECONDS );
+ return null;
+ }
+}
diff --git a/includes/class-editor-sidebar.php b/includes/class-editor-sidebar.php
deleted file mode 100644
index 9a7b1dd..0000000
--- a/includes/class-editor-sidebar.php
+++ /dev/null
@@ -1,103 +0,0 @@
-=10"
}
},
+ "node_modules/@wordpress/hooks": {
+ "version": "4.46.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.46.0.tgz",
+ "integrity": "sha512-fsKw4dmw4voIRoKc8t0XRREQlFvwj9XS/jTXvkh6mqRYCDpaEnrdB2Ji5jgbRXEMPU0GKVGMeAn5Wwi56gjBMg==",
+ "dev": true,
+ "license": "GPL-2.0-or-later",
+ "engines": {
+ "node": ">=18.12.0",
+ "npm": ">=8.19.2"
+ }
+ },
"node_modules/@wordpress/icons": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-12.0.0.tgz",
diff --git a/package.json b/package.json
index 8b4582c..4384341 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"export:feedback": "bash scripts/export-feedback.sh"
},
"devDependencies": {
+ "@wordpress/hooks": "^4.46.0",
"@wordpress/scripts": "^31.0.0",
"del-cli": "^5.1.0"
},
diff --git a/readme.txt b/readme.txt
index 55d567d..c1390e9 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,8 +1,8 @@
-=== Agentic Admin for WordPress ===
+=== Agentic Admin ===
Contributors: pluginslab
Tags: ai, sre, site reliability, webllm, abilities api
Requires at least: 6.9
-Tested up to: 6.9
+Tested up to: 7.0
Requires PHP: 8.2
Stable tag: 0.11.0
License: GPL-2.0-or-later
@@ -12,7 +12,7 @@ A privacy-first AI Site Reliability Engineer running entirely in the browser via
== Description ==
-Agentic Admin for WordPress transforms your WordPress admin panel into an intelligent command center. Instead of navigating through multiple screens to diagnose issues, you simply describe your problem in plain English.
+Agentic Admin transforms your WordPress admin panel into an intelligent command center. Instead of navigating through multiple screens to diagnose issues, you simply describe your problem in plain English.
= Features =
@@ -36,10 +36,50 @@ Agentic Admin for WordPress transforms your WordPress admin panel into an intell
4. Wait for the AI model to download (one-time, ~1.2GB for Qwen 3 1.7B or ~4.5GB for Qwen 2.5 7B)
5. Start chatting!
+== Screenshots ==
+
+1. The Agentic Admin chat tab in wp-admin, mid-conversation. The model has just answered a question about installed plugins by calling the `plugin-list` tool locally — full ReAct trace (user question, thought process, tool call, answer) visible.
+2. First-run model download in progress. The Qwen 3 1.7B weights (~1.2 GB) are fetched from the MLC-AI / HuggingFace CDN — once per browser, cancellable, cached for subsequent sessions.
+3. The Abilities browser, listing every tool the assistant can call against the WordPress Abilities API on this site.
+4. Settings panel. Build the local knowledge base, see detected GPU + VRAM, tune context-window size per model based on your hardware, toggle thinking mode, and switch between the local engine (WebLLM + WebGPU), a remote OpenAI-compatible endpoint, or the WordPress 7.0 Connector.
+5. Multi-step workflow execution. "Do a performance check" is recognized as a 2-step workflow — the assistant runs `site-health` and `error-log-read` in sequence, then summarizes the environment (WP version, PHP, memory, debug mode, error log status) in one answer.
+6. WordPress 7.0 AI Connector integration. The Connector tab picks up any AI provider registered via WP 7.0's built-in Connector API — Anthropic, Google, OpenAI, or any third-party `ai_provider` plugin — and uses it as the model backend with zero extra setup.
+
+== External services ==
+
+This plugin runs AI locally in your browser by default. No prompts or admin data are sent to any server unless you explicitly enable the external LLM provider. The following external services are contacted under specific, disclosed conditions:
+
+**Model weights CDN (MLC-AI / HuggingFace)** — Always for the local engine.
+On first use the browser downloads the selected model (Qwen 3 1.7B by default, ~1.2 GB) from `https://huggingface.co/mlc-ai/` and `https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/`. Only HTTP GET requests for static model files are made; no prompts, admin data, or telemetry are sent. Weights are cached in the browser; subsequent sessions are offline.
+HuggingFace terms: https://huggingface.co/terms-of-service — Privacy: https://huggingface.co/privacy
+
+**Transformers.js CDN (jsDelivr)** — Only when the local knowledge base is enabled in settings.
+The embedding model used to index documentation is loaded from `https://cdn.jsdelivr.net/npm/@xenova/transformers@3.8.1/`. Only the JS bundle and embedding model files are fetched; no prompts or admin data are sent.
+jsDelivr terms: https://www.jsdelivr.com/terms — Privacy: https://www.jsdelivr.com/privacy-policy-jsdelivr-net
+
+**External LLM provider (user-configured)** — Only when you switch the engine from "Local" to "Remote" in settings.
+When enabled, chat messages, tool descriptions, and tool results are sent through this plugin's REST proxy (`/wp-json/wp-agentic-admin/v1/llm-proxy/`) to the OpenAI-compatible endpoint URL you configure (e.g. Ollama, LM Studio, vLLM, OpenAI, Groq, Together). You choose the endpoint; the plugin does not preselect or default to any third-party provider. No data is sent until you save an endpoint and start a chat in Remote mode.
+
+**DuckDuckGo HTML search** — Only when the optional `web-search` ability is invoked by the assistant.
+The user's search query is sent to `https://html.duckduckgo.com/html/` over GET. No WordPress user data is sent.
+DuckDuckGo terms: https://duckduckgo.com/terms — Privacy: https://duckduckgo.com/privacy
+
+**NVD CVE database (NIST)** — Only when the optional `plugin-vulnerability-scan` ability is invoked.
+Installed plugin names and versions are sent to `https://services.nvd.nist.gov/rest/json/cves/2.0` to check for known CVEs.
+NVD terms: https://nvd.nist.gov/general/terms-of-use — Privacy: https://www.nist.gov/privacy-policy
+
+**MITRE CVE API** — Only when the optional `plugin-vulnerability-scan` ability follows up on a CVE identifier.
+The CVE ID (e.g. `CVE-2024-12345`) is sent to `https://cveawg.mitre.org/api/cve/` for details. No WordPress user data is sent.
+MITRE terms: https://www.cve.org/Legal/TermsOfUse — Privacy: https://www.cve.org/Legal/PrivacyPolicy
+
+**WordPress.org plugin checksums** — Only when the optional `verify-plugin-checksums` ability is invoked.
+Installed plugin slugs and versions are sent to `https://downloads.wordpress.org/plugin-checksums/` and `https://plugins.svn.wordpress.org/` to verify file integrity against the official WordPress.org distribution.
+WordPress.org policies: https://wordpress.org/about/privacy/
+
== Changelog ==
= 0.11.0 =
-* Renamed: Plugin is now "Agentic Admin for WordPress". Text domain "agentic-admin", function prefix agentic_admin_*. WordPress.org submission-ready.
+* Renamed: Plugin is now "Agentic Admin". Text domain "agentic-admin", function prefix agentic_admin_*. WordPress.org submission-ready.
* Removed: feedback system, WebMCP bridge, voice input, and three low-value abilities (backup-check, opcode-cache-status, disk-usage). Code preserved in git history; voice + write-file + content-generate + plugin-ecosystem abilities parked for v1.x as opt-in via WP_AGENTIC_ADMIN_ENABLE_LABS constant.
* Security: blocked sensitive-column reads (user_email, user_pass) in query-database to prevent reconnaissance attacks (#166). Hardened query length cap and read-only verb gate.
* Security: escaped output in functions-abilities.php (#121). Added sw-loader.php access-control rationale (#123). Documented direct-DB-call rationale in db-optimize and database-check.
diff --git a/src/extensions/App.jsx b/src/extensions/App.jsx
index 316f17f..cbed631 100644
--- a/src/extensions/App.jsx
+++ b/src/extensions/App.jsx
@@ -4,7 +4,7 @@
*/
import { useState, useEffect, useCallback } from '@wordpress/element';
-import { Notice, Button } from '@wordpress/components';
+import { Notice, TabPanel } from '@wordpress/components';
import { cog } from '@wordpress/icons';
import ChatContainer from './components/ChatContainer';
import AbilityBrowser from './components/AbilityBrowser';
@@ -21,10 +21,6 @@ const App = () => {
const [ modelReady, setModelReady ] = useState( false );
const [ webGPUError, setWebGPUError ] = useState( null );
const [ isExecuting, setIsExecuting ] = useState( false );
- // Active view — 'chat' | 'abilities' | 'plugin-abilities' | 'settings'.
- // Settings is just another tab (positioned visually on the right via CSS).
- const [ activeView, setActiveView ] = useState( 'chat' );
- // Track initialization phase: 'checking' during initial checks, 'loading' when auto-loading, null when done
const [ initPhase, setInitPhase ] = useState( 'checking' );
const [ initMessage, setInitMessage ] = useState(
'Checking WebGPU support...'
@@ -100,6 +96,38 @@ const App = () => {
return;
}
+ if ( savedProvider === 'connector' ) {
+ const connectorId = localStorage.getItem(
+ 'agentic_admin_connector_id'
+ );
+ const connectorModel =
+ localStorage.getItem(
+ 'agentic_admin_connector_model'
+ ) || '';
+ if ( connectorId ) {
+ log.info(
+ 'Connector provider saved, auto-connecting...'
+ );
+ setInitPhase( 'loading' );
+ setInitMessage( 'Connecting to connector...' );
+ setInitProgress( 35 );
+ try {
+ await modelLoader.loadConnector(
+ connectorId,
+ connectorModel
+ );
+ setModelReady( true );
+ } catch ( loadErr ) {
+ log.error(
+ 'Auto-connect connector failed:',
+ loadErr
+ );
+ }
+ }
+ setInitPhase( null );
+ return;
+ }
+
// Check if local model is cached
setInitMessage( 'Checking cache...' );
setInitProgress( 30 );
@@ -191,27 +219,16 @@ const App = () => {
);
}
- /**
- * Tab panel configuration
- */
const tabs = [
- {
- name: 'chat',
- title: 'Chat',
- },
+ { name: 'chat', title: 'Chat' },
{ name: 'abilities', title: 'Abilities' },
{ name: 'plugin-abilities', title: 'Plugin Abilities' },
+ { name: 'settings', title: 'Settings', icon: cog },
];
- /**
- * Render content for the active view.
- *
- * @return {JSX.Element} The rendered view.
- */
- const renderActiveView = () => {
- switch ( activeView ) {
+ const renderTab = ( tab ) => {
+ switch ( tab.name ) {
case 'chat':
- // If WebGPU has a fatal error, show fallback
if ( webGPUError && ! modelReady ) {
return (
{
return (
- ) }
+ { /* Loading state is shown inline in the composer toolbar as
+ a Spinner + percent — see in ChatInput.
+ This component only renders the provider config Card
+ (when not-loaded or error) from here on. */ }
{ /* Provider selection and controls — shown when not loaded */ }
{ ( status === 'not-loaded' || status === 'error' ) && (
-
- The AI model runs entirely in your browser using
- WebGPU. The first load will download model data
- (250MB-1GB depending on model), which is cached
- for future use. Using a Service Worker, the
- model stays loaded as you navigate wp-admin - no
- reload needed! No data is sent to external
- servers.
-
-
-
- 💡
-
-
- Performance Tip
- LLM thinking can be slow on integrated GPUs.
- For better performance in Chrome, visit{ ' ' }
-
- chrome://flags/#force-high-performance-gpu
- { ' ' }
- and enable "Force high performance
- GPU".
-
+
+
+ The AI model runs entirely in your
+ browser using WebGPU. The first load
+ will download model data (250MB-1GB
+ depending on model), which is cached for
+ future use. Using a Service Worker, the
+ model stays loaded as you navigate
+ wp-admin — no reload needed! No data is
+ sent to external servers.
+
+
+ Performance Tip: LLM
+ thinking can be slow on integrated GPUs.
+ For better performance in Chrome, visit{ ' ' }
+
+ chrome://flags/#force-high-performance-gpu
+ { ' ' }
+ and enable "Force high performance
+ GPU".
+
+
) }
-
- Connect to any OpenAI-compatible API endpoint
- (Ollama, LM Studio, vLLM, OpenAI, Groq,
- Together, etc.). Enter the base URL and fetch
- available models. API keys are stored in your
- browser only.
-
- No plugin abilities found yet. Plugins that support the
- WordPress Abilities API will appear here automatically.
-
-
-
+
+ No plugin abilities found yet. Plugins that support the
+ WordPress Abilities API will appear here automatically.
+
);
}
@@ -281,54 +172,42 @@ const PluginAbilitiesPanel = () => {
).length;
return (
-
-
+
+
Plugin Abilities
-
+
Other plugins on your site offer abilities the AI can use.
Enable the ones you need — but keep an eye on the budget
bar. The AI has limited memory, so you can't enable
everything at once.
-
+
-
-
- { budget.percentage >= 90 && (
-
-
- The AI is running low on memory. Turn off some abilities
- so it has room to think and respond.
-
-
+ { budget.percentage > 25 && (
+
+ Enabling these plugin abilities is using a noticeable share
+ of the model's context window. Disable any you
+ don't need to keep room for the conversation.
+
) }
-
- Build a local search index from your site's code,
- database schema, WordPress API signatures, and reference
- documentation. The AI assistant automatically consults
- this knowledge base when answering questions.
-
-
- { kbStatus && ! kbBuilding && (
-
+
-
-
-
- Last built
-
-
{ timeAgo( kbStatus.lastIndexed ) }
-
-
-
- Total chunks
-
-
- { kbStatus.totalChunks.toLocaleString() }
-
-
-
-
- Code files
-
-
{ kbStatus.codeFiles }
-
-
-
- DB tables
-
-
{ kbStatus.schemaTables }
-
-
-
- API signatures
-
-
{ kbStatus.apiChunks } chunks
-
-
-
- Reference docs
-
-
{ kbStatus.docsChunks } chunks
-
-
-
- ) }
+
+
+ Build a local search index from your
+ site's code, database schema, WordPress
+ API signatures, and reference documentation.
+ The AI assistant automatically consults this
+ knowledge base when answering questions.
+
- The context window determines how much conversation history and
- tool data the model can process. Larger windows use more GPU
- memory for the KV cache. Choose based on your available VRAM.
-
-
- { savedNotice === model.id && (
-
- Context window updated. Changes take effect
- on next model load.
-
- ) }
-
-
- );
- } ) }
-
-
Remote Provider Context Window
-
- When using a remote LLM provider (Ollama, LM Studio, OpenAI,
- etc.), this sets the context window size for token tracking.
- Remote models typically support much larger contexts than local
- WebLLM models.
-
-
+
+
+
+ Context Window per Model
+
+
+ The context window determines how much conversation
+ history and tool data the model can process. Larger
+ windows use more GPU memory for the KV cache. Choose
+ based on your available VRAM.
+