WPPilot 1.3.0
Restoring a revision could not run at all on 1.1.0 through 1.2.1, and behind that fatal it reported restores that never happened as successful. Change ledger entries now name the agent that made each write.
Install wppilot-1.3.0.zip. The GitHub "Source code" archive is not installable: it unpacks to wppilot-1.3.0/ where WordPress needs wppilot/, and it carries no vendor/.
Fixed
wppilot/restore-revisionwas fatal on every call. The change ledger captures a before-image on every ability call, and its capture for this ability handedwp_get_post_revision()a cast expression. That parameter is declared by reference, so PHP 8 raisedArgument #1 ($post) could not be passed by reference— a fatal Error, not a notice, with nothing in the MCP layer catching it. Every call returned a 500 before the post was touched. No other ability shared the fault.- The same ability read failure as success.
wp_restore_post_revision()answersnullon error,falsewhen the revision carried no restorable fields, and the post id on success. Onlynullwas refused, and theis_wp_error()branch guarding it was unreachable, because that function does not return one. Afalseanswer returned a success payload naming a post that had never been written. Only a positive id is accepted now, which also covers the0thatwp_update_post()answers when it refuses a write.
Added
- Per-agent attribution in the change ledger. Claude Code, Cursor and Codex connect as the same administrator on most sites, so an entry naming only the WordPress user could not say which of them made a change. The credential can: an OAuth client id or an application-password UUID is issued per agent and stays stable across its requests. Entries now carry the credential, its label, and the client name and version the agent introduced itself with, and
wppilot/list-changesreturns them. - Identity is resolved once, at the MCP entry point, because that is the only place both halves are reachable — the credential comes from request-local OAuth state or WordPress' application-password global, and the client name needs the request object. The ledger reads it back later in the same request.
- A write with no agent behind it — wp-admin, WP-CLI, cron, another plugin calling an ability directly — is recorded as
directrather than credited to the last agent seen. OAuth client ids are stored hashed, as they already were in the connections table. mago.toml. The static analyzer has shipped inrequire-devsince 1.0.0 with no configuration, so it answered "No files found to lint" and never ran. It is what found the by-reference fatal.php-versionis pinned to the plugin's declared 8.0 floor, so 8.1-only syntax is reported rather than shipped to sites the plugin claims to support.
Upgrading
No new abilities, no permission changes, and existing connections keep working. Ability count is unchanged at 103.
Verification
401 unit tests, 625 assertions. Six are new, written against doubles that reproduce the WordPress contracts exactly: wp_get_post_revision() declares its parameter by reference, so calling it is the assertion, and wp_restore_post_revision() can answer all three of its documented shapes. Reverting either fix fails the suite.