feat(v3.12.0): media pipeline, AI image generation, capability probe, companion v2.9.0
Some checks failed
Release / Test before release (push) Has been cancelled
Release / Publish to PyPI (push) Has been cancelled
Release / Publish to Docker Hub (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled

Three-month batch sync from internal repo (~80 commits) covering Tracks F.5a, F.7e, F.8, F.17, F.18, F.X.

WordPress media pipeline
- Pillow-based optimization, AI image generation (OpenAI / Stability / Replicate / Google Nano Banana / OpenRouter), chunked + resumable uploads, bulk delete/reassign, idempotent retries.

Capability discovery (F.7e)
- Per-site credential probe + adapters for WordPress / WooCommerce / Gitea, tier-fit unions granted ∪ roles, capability badge UI with HTMX partial re-check, install hint in every companion-unreachable error.

Companion plugin overhaul
- Renamed wordpress-plugin/airano-mcp-seo-bridge → wordpress-plugin/airano-mcp-bridge.
- Eight new endpoints: /capabilities, /bulk-meta, /export, /cache-purge, /transient-flush, /site-health, /audit-hook, /upload-and-attach.
- wp.org Plugin Check pass: i18n, WP_Filesystem, scheme allowlist on audit-hook URL.

Other
- Gitea ergonomics (F.17): batch files, tree, search, compare, releases, fork.
- Opportunistic bcrypt upgrade for legacy SHA-256 admin keys (F.8).
- n8n refactor: structured errors, capability probe, missing tools backfilled.
- Idempotency-Key dedup for AI media upload retries; WP client fast-fails on unreachable sites.

Docs
- README + CLAUDE.md drop the fixed "633 tools" claim. The total grows with each release; per-plugin approximations + dashboard-surfaced counts replace it.
- Tools/Tests badges removed in favour of "Plugins: 10".

Deployment
- PyPI mirror chain, optional BUILD_HTTP_PROXY, Alpine→Yandex apk mirror, Debian-slim Plan-B Dockerfile, mirror.gcr.io variant.

CI
- Black + Ruff clean on Python 3.12; pytest tests/ green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 16:25:58 +02:00
parent 788439e377
commit f203ca88de
140 changed files with 23802 additions and 2253 deletions

97
docs/media-error-codes.md Normal file
View File

@@ -0,0 +1,97 @@
# Media Upload Error Codes (F.5a.6.2)
All media-upload tools return structured JSON errors shaped as:
```json
{
"error_code": "TOO_LARGE",
"message": "File is 12345678 bytes; limit is 10485760 bytes ...",
"details": { "size": 12345678, "max": 10485760 }
}
```
The `error_code` values below are **stable**: renaming or removing one is
a breaking change. The source of truth is
[`core/media_error_codes.py`](../core/media_error_codes.py) (the set
`MEDIA_ERROR_CODES`) and the stability test in
[`tests/plugins/wordpress/test_media_error_taxonomy.py`](../tests/plugins/wordpress/test_media_error_taxonomy.py).
## Input / validation
| Code | When it fires |
| ----------------- | ------------------------------------------------------------------------------------- |
| `BAD_BASE64` | The supplied base64 payload (full upload or single chunk) fails to decode. |
| `BAD_MODE` | `mode` argument to attach tools is not `append` or `replace`. |
| `BAD_ROLE` | `role` argument to attach tools is not `main` or `gallery`. |
| `BAD_SIZE` | Chunked `total_bytes` is `<= 0`. |
| `BAD_SOURCE` | Attach-upload helper given a `source` other than `base64` / `url`. |
| `EMPTY_FILE` | Decoded payload is zero bytes. |
| `MEDIA_NOT_FOUND` | A supplied `media_id` does not exist in the WP media library. |
| `MIME_REJECTED` | Sniffed MIME is not in the allow-list (`ALLOWED_MIMES`). |
| `MISSING_FIELD` | A required field is missing (e.g. `data` for base64, `url` for URL sideload). |
| `SSRF` | URL resolves to private/loopback/link-local/metadata IP or is on the host blocklist. |
| `TOO_LARGE` | Payload exceeds `WP_MEDIA_MAX_MB` / streamed download exceeds the byte cap. |
| `URL_FETCH_FAILED`| Remote URL returned `>= 400` while downloading. |
## WordPress REST upstream
| Code | When it fires |
| ----------------- | ------------------------------------------------------------------------------------- |
| `WP_413` | WordPress rejected the upload with HTTP 413 (server `upload_max_filesize` too low). |
| `WP_AUTH` | WordPress rejected auth (401/403). Application Password likely invalid/expired. |
| `WP_BAD_RESPONSE` | Upload accepted but WP returned a non-JSON body. |
| `WP_CREDENTIALS_MISSING` | (WC sites only) Tool needs a WP Application Password to hit `/wp/v2/media`. WC Consumer Key + Secret do not authenticate the WP core REST. Add `wp_username` + `wp_app_password` in Connection Settings → advanced. |
| `WP_<status>` | Any other non-2xx status from WP — e.g. `WP_400`, `WP_500`. Dynamic. |
## Companion plugin `upload-chunk` route (F.5a.7)
These only fire when MCPHub chose the companion `/airano-mcp/v1/upload-chunk` route
(because the probe advertises the helper and the payload exceeds `upload_max_filesize`).
A companion failure is **non-fatal**: MCPHub falls back to the standard `/wp/v2/media`
route on any error here, so these codes usually surface in logs, not to end users.
| Code | When it fires |
| ------------------------ | --------------------------------------------------------------------- |
| `COMPANION_BAD_RESPONSE` | Companion route returned 2xx but the body was not parseable JSON. |
| `COMPANION_<status>` | Any non-2xx status from the companion route — e.g. `COMPANION_500`. |
## Chunked upload session
| Code | When it fires |
| ------------------- | ---------------------------------------------------------------------------- |
| `BAD_STATE` | Session exists but is not in `open` state (already finalized/aborted). |
| `CHECKSUM_MISMATCH` | Assembled sha256 does not match value supplied at `start`. |
| `CHUNK_CHECKSUM` | Per-chunk sha256 does not match the value supplied with the chunk. |
| `CHUNK_ORDER` | Chunk index does not match `next_chunk`. |
| `CHUNK_OVERFLOW` | Appending this chunk would exceed declared `total_bytes`. |
| `EXPIRED` | Session's TTL has elapsed. |
| `INCOMPLETE` | Finalize called before all declared bytes arrived. |
| `NO_SESSION` | `session_id` is unknown. |
| `QUOTA_EXCEEDED` | User already has `MCPHUB_UPLOAD_MAX_CONCURRENT` open sessions. |
| `SESSION_TOO_LARGE` | Declared `total_bytes` exceeds the hard session cap (default 500 MB). |
## AI generation providers
| Code | When it fires |
| ----------------------- | ---------------------------------------------------------------------- |
| `GENERATION_FAILED` | Generic provider failure not covered by a more specific code. |
| `NO_PROVIDER_KEY` | No per-user key stored and no env fallback for the selected provider. |
| `PROVIDER_AUTH` | Provider rejected the supplied API key. |
| `PROVIDER_BAD_REQUEST` | Provider returned 4xx — prompt or size was invalid. |
| `PROVIDER_BAD_RESPONSE` | Provider returned a 2xx with an unexpected shape. |
| `PROVIDER_QUOTA` | Provider returned 429 / quota error. |
| `PROVIDER_TIMEOUT` | Provider timed out. |
| `PROVIDER_UNAVAILABLE` | Provider returned 5xx / circuit open. |
| `PROVIDER_UNKNOWN` | `provider` argument is not one of the registered providers. |
## Rate / policy
| Code | When it fires |
| -------------------- | --------------------------------------------------------------------- |
| `TOOL_RATE_LIMITED` | Per-tool, per-user cap exceeded (see `core/tool_rate_limiter.py`). |
## Catchall
| Code | When it fires |
| ---------- | ---------------------------------------------------------------------------------- |
| `INTERNAL` | Unexpected exception reached the top of a tool handler. Details in `message`. |