Add WP_Agent_Channel base class for messaging transports#99
Merged
Conversation
Introduces `AgentsAPI\AI\Channels\WP_Agent_Channel`, a reusable abstract base for transports that connect external messaging surfaces (Telegram, Slack, WhatsApp, Email, …) to a chat ability. The base class owns the agent loop: validate → extract_message → run_agent → deliver_result → lifecycle hooks → session persistence Concrete channels only fill in the transport-specific I/O. Default `run_agent()` calls the chat ability under the slug returned by the `wp_agent_channel_chat_ability` filter (default `openclawp/chat`); a sentinel error code `silent_skip` lets `validate()` drop a message without firing `send_error()` (loop prevention, allowlist misses). Test coverage: tests/channels-smoke.php — 19 assertions covering the happy path, multi-turn session continuity, distinct external_ids, empty-message short-circuit, agent-error → send_error routing, the filter override seam, async dispatch via wp_schedule_single_event, and silent_skip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
…branch Addresses Homeboy lint + PHPStan findings on PR #99: - Replace `?:` short ternaries with explicit ternaries (PHPCS rule). - `unset( $data )` in default `validate()` / `run_agent()` stubs so the unused-parameter warning is silenced for base-class no-ops that subclasses override to actually use $data. - Remove the `is_array( $result )` defensive branch in `deliver_result()`. After the `is_wp_error()` early return, `$result` is narrowed to array per the docblock, so PHPStan flagged the check as always-true. The type system is the one enforcing the invariant; the runtime check was redundant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 7, 2026
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces
AgentsAPI\AI\Channels\WP_Agent_Channel, a reusable abstract base for transports that connect external messaging surfaces (Telegram, Slack, WhatsApp, Email, …) to a chat ability registered through the WordPress Abilities API.The base class owns the agent loop end-to-end:
Concrete channels only fill in the transport-specific I/O — extract a user message from the webhook payload, send a reply, lifecycle hooks for typing indicators / reactions / stats. The default
run_agent()calls the chat ability under the slug returned by thewp_agent_channel_chat_abilityfilter (defaultopenclawp/chat); override to plug in a different runtime.Why this matters
Mirrors a pattern that's been validated in production at WordPress.com for Telegram and Slack agents. Lifting the portable surface into agents-api means consumers (openclaWP, Data Machine, Intelligence, future plugins) can ship new channels by extending one class instead of reinventing the agent dispatch + session continuity + loop-prevention plumbing each time.
The first public consumer is
OpenclaWP_Wacli_Channel(WhatsApp via openclaw/wacli) in lezama/openclawp; see the companion PR there.Surface
get_external_id_provider(),get_external_id(),get_client_name()— used to scope sessions across redeploys and for tracing.extract_message(),send_response(),send_error(), optionalsend_notification().get_job_action()— returning a hook name makesreceive()schedule a single async event; returning''runs synchronously inside the receive call.validate(),on_processing_start(),on_processing_end(),on_complete().validate()returning aWP_Errorwith codeWP_Agent_Channel::SILENT_SKIP_CODEdrops the message silently — nosend_error(), no agent invocation. Use for own-bot echoes, allowlist misses, and well-formed-but-uninteresting webhook events.md5(provider:external_id). Subclasses can overridesession_storage_key()to scope per-user, per-blog, or to a custom store.Filter
Test plan
tests/channels-smoke.php— 19 pure-PHP assertions covering happy path, multi-turn session continuity, distinctexternal_idisolation, empty-message short-circuit, agent-error routed tosend_error, filter override, async dispatch viawp_schedule_single_event, andsilent_skip.tests/bootstrap-smoke.phpupdated to includeChannelsin the agent-native source-directory list (300 / 300 assertions pass).Implementer notes
run_agent()callswp_get_ability($slug)->execute(['agent' => …, 'message' => …, 'session_id' => …]). Subclasses can override for a different runtime — directwp_ai_client_prompt(), an external HTTP service, or a host-specific factory.deliver_result()understands two result shapes:{ reply: string, session_id?: string }(single-turn) and{ messages: [ { role, content } ] }(multi-message). Overrideextract_replies()for exotic shapes.add_filteron the chat ability's permission callback, scoped to the request.🤖 Generated with Claude Code