=== AcrossAI Abilities Manager === Contributors: raftaar1191 Donate link: https://github.com/acrosswp/acrossai-abilities-manager Tags: abilities, ability management, access control, site management, ai Requires at least: 6.9 Tested up to: 7.0 Requires PHP: 8.1 Stable tag: 0.0.32 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Manage every WordPress ability registered on your site — view, search, override, and bulk-control ability metadata from a single admin page. == Description == AcrossAI Abilities Manager gives site administrators full visibility and control over every ability registered via the WordPress Abilities API (`wp_get_ability()`). **Features:** * **Browse all abilities** — a searchable, sortable, paginated table listing every registered ability with slug, provider, source, and current status. * **Toggle allow/disallow** — enable or disable any ability site-wide with a single click. Changes are saved instantly without a page reload. * **Edit ability metadata** — override `readonly`, `destructive`, `idempotent`, `show_in_rest`, `show_in_mcp`, `mcp_type`, and `mcp_servers` fields per ability using a tri-state system (Yes / No / Inherit from registry). * **Reset overrides** — restore any ability back to its registry defaults with one click. * **Bulk actions** — allow, disallow, or reset up to 50 abilities at once. * **Ability Library** — enable or disable add-on ability groups from a dedicated Library page, with All/Specific mode controls per group. * **Add-ons page** — browse companion plugins from the WordPress admin. WordPress.org-hosted add-ons install / activate / deactivate in place; add-ons distributed elsewhere link out to the vendor's site so you can install them via Plugins → Add New → Upload Plugin. * **MCP server list** — view all registered MCP servers when the MCP Adapter plugin is active. * **Debugging → Conflict Testing** — toggle any installed plugin's *effective* active state without ever writing to `wp_options.active_plugins`. Seven WP Abilities API abilities (`acrossai/conflict-test-list-plugins`, `-get-overrides`, `-set-override`, `-bulk-set-overrides`, `-clear-overrides`, `-deploy-mu-plugin`, `-remove-mu-plugin`) let a REST client, MCP AI client, or another plugin reproduce a plugin conflict for a browser session or a support call, then restore the site to its exact prior state by clearing one JSON file. Overrides cascade through WP 6.5+ `Requires Plugins:` headers by default. Every `active=true` write is guarded by a WordPress-core-style `plugin_sandbox_scrape` probe, so a broken plugin can never leave the site in a state where every subsequent page load fatals — the override is refused instead. Feature 061. All overrides are stored in a dedicated database table. The WordPress ability registry is never modified — only the fields that differ from registry defaults are persisted. **Security:** * All endpoints require `manage_options` capability. * All state-changing requests are protected by WordPress nonce verification. * All input is sanitized; all output is escaped. **Third-party integrations (optional):** * **MCP Adapter plugin** — if active, the plugin displays a list of registered MCP servers inside the ability edit panel. No data is sent to any external service. The MCP Adapter plugin communicates only with your own WordPress installation. This plugin's own code makes no external HTTP requests. One admin-only surface can contact an external service on your behalf: the AcrossAI → Add-ons page installs WordPress.org-hosted companion plugins directly through WordPress core's own plugin installer (`api.wordpress.org` + `downloads.wordpress.org`). Add-ons registered with any other source (e.g. GitHub, Freemius) are shown as external "Get add-on ↗" links that open the vendor's site in a new browser tab — the plugin does not download or install them itself. The AcrossAI → Consultations submenu renders a static call-to-action button that opens `calendly.com` in a new browser tab only after the administrator clicks it — no third-party asset is loaded inside wp-admin. Full disclosure — including what data is transmitted to each service and links to their terms + privacy policies — is in the **External Services** section below. == Installation == 1. Upload the `acrossai-abilities-manager` folder to `/wp-content/plugins/`. 2. Activate the plugin through the **Plugins** menu in WordPress. 3. Navigate to **AcrossAI Abilities Manager** in the WordPress admin menu. **Add-ons:** 1. Go to **AcrossAI → Add-ons** to browse available companion plugins. 2. All add-ons are free and hosted on WordPress.org; each card offers a one-click Install / Activate / Deactivate action via the standard WordPress plugin installer. == Frequently Asked Questions == = Does this plugin support Multisite? = No. This plugin has not been tested on WordPress Multisite installations. = Does this plugin modify the WordPress ability registry? = No. The plugin stores only overrides — fields that differ from the registry defaults. The ability registry itself (`wp_get_ability()`) is never modified. = What happens when I reset an override? = The override row is deleted from the database. The ability will inherit its values from the registry again. = What is the Ability Library? = The Library page lets you enable or disable ability groups registered by add-on plugins. Each group shows an ON/OFF master toggle and an All/Specific mode selector. In Specific mode, individual ability slots can be toggled independently. = What is the MCP Adapter integration? = If the MCP Adapter plugin is active on your site, AcrossAI Abilities Manager will display the list of registered MCP servers in the ability edit panel. This is entirely optional — the plugin works without the MCP Adapter. = Does this plugin make external HTTP requests? = The plugin's own code makes no external HTTP requests. Two admin-only surfaces trigger external connections on behalf of an authenticated administrator: * **AcrossAI → Consultations** submenu — renders a static call-to-action button that links to `https://calendly.com/acrossai/using-ai-in-wordpress` and opens in a new browser tab. The plugin does not load any Calendly script, iframe, or asset inside wp-admin. Calendly is only contacted if the administrator explicitly clicks the button — at which point their browser navigates directly to `calendly.com`, exactly as with any external hyperlink. * **AcrossAI → Add-ons** submenu — installs WordPress.org-hosted companion plugins in place through WordPress core's `plugins_api()` + `Plugin_Upgrader` (contacts `api.wordpress.org` + `downloads.wordpress.org`). Add-ons registered with any other source (e.g. GitHub, Freemius) render as external "Get add-on ↗" links that open the vendor's site in a new browser tab — the plugin does not download or install those add-ons itself. Users install off-directory add-ons via WP admin's standard **Plugins → Add New → Upload Plugin** flow (or via the vendor's own installer once the paid plugin is activated). Full disclosure — including what data is transmitted, and links to each service's terms + privacy policy — is in the **External Services** section of this readme. == Screenshots == 1. The Abilities Manager admin page — searchable, sortable ability table. 2. The edit drawer — tri-state override controls for each ability field. 3. Bulk actions toolbar for allow/disallow/reset across multiple abilities. 4. The Ability Library page — enable/disable add-on ability groups. 5. The Add-ons page — browse free companion plugins. 6. Settings — Display (abilities-per-page) and Upload Media Abilities (allowed-MIME list + Add file types). == External Services == This plugin connects to the following external services on your behalf. Each connection is triggered by a specific admin-only action and is disclosed here per the WordPress.org plugin directory guidelines. **1. Calendly external link (`calendly.com`)** *What it is:* Calendly is a third-party scheduling service. The AcrossAI → Consultations submenu displays a static call-to-action button that links out to a Calendly booking page for AcrossAI consultations ("Using AI in WordPress"). *When it is contacted:* Never on page render. The Consultations submenu at `/wp-admin/admin.php?page=acrossai-consultations` is a self-contained wp-admin page — it does not load any Calendly script, iframe, cookie, or asset. Calendly is only contacted if the administrator explicitly clicks the "Book a Consultation" button, at which point their browser navigates directly to `https://calendly.com/acrossai/using-ai-in-wordpress` in a new tab (`target="_blank" rel="noopener noreferrer"`). This is identical to clicking any external hyperlink from an admin page. *What is loaded on the Consultations page:* Nothing from Calendly. The page renders self-contained HTML + CSS. The only external asset referenced by the page is Google Fonts (Space Grotesk + IBM Plex Sans via `fonts.googleapis.com`) — permitted under the "third-party CDNs beyond fonts" carve-out in the WordPress plugin guidelines. *What data is transmitted to Calendly:* Nothing by this plugin. If the administrator clicks the CTA button, their browser navigates directly to Calendly and sends standard browser metadata (IP address, User-Agent, referrer) to Calendly as with any external link. If the administrator then chooses to book a consultation on Calendly's own site, any information they enter into Calendly's booking form (name, email address, meeting preferences, etc.) is transmitted to and processed by Calendly. This plugin does not intercept, store, or forward that data. *Terms of service:* https://calendly.com/pages/terms *Privacy policy:* https://calendly.com/pages/privacy **2. WordPress.org plugin directory (`api.wordpress.org` and `downloads.wordpress.org`)** *What it is:* The Add-ons page (`/wp-admin/admin.php?page=acrossai-addons`) uses the WordPress.org plugin directory to install free companion plugins directly from wp-admin. *When it is contacted:* Only when an authenticated administrator (`install_plugins` capability) clicks the "Install" button on a card whose `source` is `wordpress.org`. Contact happens through WordPress core's own `plugins_api()` and `Plugin_Upgrader` — this plugin does not issue direct HTTP requests. Add-ons registered with any other source (e.g. `github`, `freemius`) are rendered as external "Get add-on ↗" links that open the vendor's site in a new browser tab; the plugin does NOT download or install those add-ons itself, so no request is made to the vendor's servers from wp-admin. *What data is transmitted:* The WordPress core plugin API request payload (site URL, WP version, PHP version, locale) as per WordPress core's standard update check protocol. *Terms of service:* https://wordpress.org/about/terms/ *Privacy policy:* https://wordpress.org/about/privacy/ **3. WordPress.org core version-check API (`api.wordpress.org/core/version-check/1.7/`)** Called only when an administrator invokes the `core/rollback-wp-core` ability (registered under the Core category) and the local core-version cache has expired. Rate-bounded to at most one request per day per locale per site via a site-transient cache. This is a WordPress-core-hosted API — no data beyond the standard WordPress core version-check request payload is transmitted. Same wp.org terms + privacy policy as service #2 above. == Privacy Policy == This plugin does not itself collect, store, or transmit any user data to any third party. Several admin-only actions can cause external services to receive data — all are described in the External Services section above and are triggered only by an authenticated administrator: * The AcrossAI → Consultations admin page displays a static call-to-action button. Merely loading the Consultations page sends no data to Calendly — no Calendly script, iframe, or asset is loaded inside wp-admin. If the administrator clicks the CTA button, their browser opens `calendly.com/acrossai/using-ai-in-wordpress` in a new tab, at which point standard browser metadata (IP, User-Agent, referrer) is sent to Calendly and Calendly's own privacy policy applies. If they then book a consultation on Calendly's site, information they enter into Calendly's form (name, email, meeting details) is transmitted to Calendly. * Installing a WordPress.org-hosted add-on from the AcrossAI → Add-ons page contacts the WordPress.org plugin directory via WordPress core's own `plugins_api()` and `Plugin_Upgrader` (`api.wordpress.org` + `downloads.wordpress.org`). Add-ons distributed elsewhere (e.g. GitHub, Freemius) are rendered as external "Get add-on ↗" links that open the vendor's site in a new browser tab — the plugin itself does not download or install those add-ons, so no request is sent to the vendor's servers from wp-admin. If the administrator clicks the external link, their browser navigates directly to the vendor and standard browser metadata (IP, User-Agent, referrer) is sent to the vendor as with any external hyperlink. * Invoking the `core/rollback-wp-core` ability contacts the WordPress.org core version-check API (a WordPress-core-hosted service) via the standard WordPress update API. No data is sent to any external server without an explicit administrator action. == Changelog == = Unreleased = = 0.0.32 - 2026-08-28 = **Release theme: token-efficient AI callers.** Two closely related shifts. First, response payloads shrink dramatically for the common "small edit" and "just tell me the block structure" intents. Second, ability descriptions gain author-declared hints pointing AI callers at cheaper sibling abilities when their intent maps to one. Every change is either strictly additive or opt-out only via an admin toggle — no ability's execute() behaviour changes. **Token-efficiency default for six content writers.** `content/create-page`, `content/update-page`, `content/create-post`, `content/update-post`, `content/create-cpt-item`, `content/update-cpt-item` gain a new optional input `return_content:boolean, default:false`. When false (the default), the response's `page` / `post` / `item` object strips three large fields — `post_content`, `post_content_filtered`, `post_excerpt` — and adds `content_bytes:integer` so callers still see the saved payload size at a glance. **Why.** A single-word edit on a ~97 KB page via `content/update-page` previously round-tripped ~60 K LLM tokens (the caller sent the whole new body and the ability echoed the same body back). With this default, the echo drops to ~0 tokens — the caller pays only for the upload it already had to make. Fine-grained edits via `blocks/update-post-block` remain ~10× cheaper still because they never touch the surrounding content. **BREAKING for callers reading `response.page.post_content` (or `.post` / `.item` equivalents).** Existing callers that need the saved content back — e.g. to diff against what they sent — must pass `return_content:true` explicitly. The three stripped fields remain queryable via `content/get-page` / `content/get-post` after the write. **Not affected.** Every other content ability (get / list / delete / block-tree operations / meta ops) is unchanged. The block-tree writers (`blocks/update-post-block`, `blocks/add-block`, etc.) already returned just the modified block, not the whole page — nothing to strip. **Same default now applies to nine block-editor writers.** `blocks/create-block-pattern`, `blocks/create-block-template`, `blocks/update-block-template`, `blocks/create-block-template-part`, `blocks/update-block-template-part`, `blocks/create-block-style-variation`, `blocks/update-block-style-variation`, `blocks/create-global-style`, and `blocks/update-global-style` gain the same `return_content:boolean, default:false` input. Response objects (`pattern` / `template` / `part` / `variation` / `record`) strip the large `content` (pattern/template/template-part markup) or `data` (variation/theme.json JSON) field by default and add `content_bytes:integer`. For `Variation_Db::to_row` and `Global_Styles_Db::to_row`, the writers now pass the caller's `$return_content` through instead of hardcoding `true` — the helpers skip `decode_content()` when the payload isn't wanted (CPU saving on the hot path). BREAKING for callers reading `response.pattern.content`, `response.template.content`, `response.part.content`, `response.variation.data`, or `response.record.data` — pass `return_content:true` explicitly. `blocks/update-block-pattern` is unchanged — it already returned a lightweight location descriptor. **New ability `blocks/outline-post-blocks`.** Returns a flat, depth-first index of a post's block tree — canonical path, block type, child count, byte size, and a short text preview — without any block content. Cheap way for an LLM caller to locate a block before editing it: `blocks/get-post-blocks` on a large page can be hundreds of kilobytes because it returns every block's full `innerHTML`; this ability returns kilobytes for the same post. Paths use the same raw-`parse_blocks()` index scheme as `Block_Tree`, so a path returned here is drop-in usable with `blocks/add-block`, `blocks/update-post-block`, and `blocks/remove-block`. Paths are positional — a write can re-serialize the post and shift raw indices — so the response includes `post_modified_gmt` for staleness detection; re-outline after each write rather than caching paths. Filters (`block_names`, `contains`, `max_text`, `depth`, `include_attrs`, `max_results`) compose. `contains` matches only within the extracted text preview (up to `max_text` characters); raise `max_text` for deeper substring searches. Whitespace nodes (`parse_blocks` entries with null `blockName`) are excluded from output but still consume index positions — same convention `Block_Tree` already uses. `readonly`, `idempotent`, `non-destructive`. **New — Ability Suggestions framework (Feature 095).** Ability authors can now declare a small list of other abilities an AI caller might use instead — a token-saving hint mechanism mirroring Feature 088's `suggested_plugins()`. Each ability class can override a new protected method `suggested_abilities()` returning `array`; entries surface under `args.meta.acrossai.suggested_abilities` on `mcp-adapter-get-ability-info` (not on discover-abilities — details-only surface, avoids discovery bloat). Hints are strictly advisory — nothing about the original ability's execution changes. Four initial ability overrides ship in this release: `content/update-page`, `content/update-post`, `content/update-cpt-item` each suggest `blocks/outline-post-blocks` + `blocks/update-post-block` for narrow edits (~29K tokens saved on a 97 KB page); `blocks/get-post-blocks` suggests `blocks/outline-post-blocks` when only paths are needed (~28K tokens saved on the same page). New admin toggle "Disable ability suggestions" on the Abilities settings tab (between "Plugin Suggestions" and "Uninstall Settings") strips the field site-wide (option key `acrossai_disable_ability_suggestions`, default `0` = feature enabled). Uninstall cleans up the option when "delete all data" is on. An ability with no override produces a byte-identical payload to what it produced before Feature 095 — no schema drift, no phantom empty list. **Ten more `suggested_abilities()` overrides added to the Feature 095 hint catalog.** `content/get-page`, `content/get-post`, `content/get-cpt-item` each hint that `blocks/outline-post-blocks` is far cheaper (~20×) when the caller only needs to locate a block or see the structure. `blocks/read-theme-json` hints that `blocks/get-style-guide` returns a normalized token summary (~5–8×) when the caller wants design tokens, not the raw spec. `options/list-options`, `media/list-media`, `users/list-users`, `blocks/list-block-templates`, `blocks/list-block-patterns`, `blocks/list-global-styles` each hint that their corresponding targeted-read siblings (`options/get-option`, `media/get-media`, `users/get-user`, `blocks/read-block-template`, `blocks/read-block-pattern`, `blocks/read-global-style`) are 5–30× cheaper when the caller already knows the identifier — list is for discovery, get/read is for retrieval. = 0.0.31 - 2026-08-26 = **Release theme: File Manager consolidation + hardening + audit log.** Bundles the six-feature series (089 → 094) that turned `file-manager/*` from a loose collection of read/write primitives into a self-contained subsystem with an admin tab, allowlist-and-content-filter enforcement, and an append-only audit log with pre-image backups. Also cuts the inline `.bak.` scheme in `Delete_File` (BREAKING for callers of `response.backup` — new canonical field is `response.backup_path`). **Feature 094 — File Manager Audit Log + Backup Harness (partial).** Consumes the four Backup & Audit option keys shipped as scaffold in PR #144 and enforced-toggle-only in PR #146. New `Audit_Trail` utility owns pre-image backups (into `wp-content/acrossai-file-manager-backups//`) + append-only log (into `wp-content/acrossai-file-manager-logs/acrossai-file-manager.log`) + amortised 1-in-10 cleanup + stats. Both storage locations get a `Deny from all` `.htaccess` on first creation. All I/O goes through `WP_Filesystem`. **New ability:** `file-manager/get-changelog` — tails the last N entries (default 100, max 500) via MCP. Honours the read allowlist. Empty log returns a friendly message, not an error. `manage_options` gated. **Log entry format** (blank-line separated, one entry per mutation): [YYYY-MM-DD HH:MM:SS UTC] Ability: file-manager/ File: User: (ID:) IP: Size: -> bytes Destination: (COPY / MOVE only) Backup: Context: **New action hook:** `do_action('acrossai_file_manager_log_entry', $entry)` fires after every log write. Subscribers (Slack, Datadog, SIEM…) receive the parsed entry as an assoc array. Zero cost when no subscribers. **New optional `context` input field** on wired abilities (`delete-file`, `edit-file`, `create-directory` in this PR — more in the follow-up). Schema max 2000 chars; log writer truncates to 500 chars via `sanitize_text_field` before persisting. **BREAKING — `file-manager/delete-file` `backup` response field.** The inline `.bak.