Commit Graph

5 Commits

Author SHA1 Message Date
Kevin (OpenClaw Bot) 41c406e1ab feat(a2a): v1.0 upgrade + full code review fixes
Fable 5 pass: 40 turns, $13.56, 109k output tokens.

## A2A v1.0 upgrade
- SCREAMING_SNAKE task states (TASK_STATE_COMPLETED etc)
- ROLE_USER/ROLE_AGENT message roles
- Unified Parts (no kind field, member-presence discrimination)
- Agent Card: supportedInterfaces[], provider, capabilities.extendedAgentCard
- SSE: member-discriminated statusUpdate/artifactUpdate, closure=terminal
- contextId inside Message (not top-level params)
- ISO 8601 millisecond timestamps, createdAt/lastModified on Task
- New operations: tasks/list, tasks/subscribe
- input-required state reachable via [INPUT_REQUIRED] hint

## Security & correctness (all must-fix from review)
- Slash-command bypass removed — remote peers can't invoke operator commands
- Per-peer token auth (A2A_PEER_TOKENS) replaces self-asserted params.peer
- _pending_replies keyed by task_id with per-context FIFO (no cross-talk)
- Timeout returns TASK_STATE_FAILED, not completed
- reset_turns uses task's context from store (was silent no-op)
- Error codes: spec codes only for spec semantics, custom -32050..-32052
- Real latency metric (was fake 0.0)

## Dead features wired
- Push notifications: inline configuration.taskPushNotificationConfig in
  message/send + tasks/pushNotificationConfig/create. HMAC-signed e2e.
- Dynamic Agent Cards: skills from live tools.registry, A2A_ADVERTISED_TOOLSETS
- Persistence: new a2a_history(context_id) tool recalls conversations
- Dead helpers cut: rate_limit_status, is_open_mode, verify_push_signature,
  turn_count, check_bearer

## Architecture
- TurnTracker/RateLimiter/TaskStore on adapter instance (was module-global)
- Handler class at module level (was untestable closure)
- on_processing_complete for failure/cancel paths
- SSE hang fix: keepalive header no longer prevents socket closure

## a2a_orchestrate kept per user instruction
- best mode: only successful replies considered (long error can't win)
- all-error case: explicit 'All peers failed' listing
- Client paths deduped into _send_task helper

## Tests
- inspect.getsource() tests replaced with behavioral coverage
- 133 total: 118 unit + 15 integration
- v1.0 spec compliance, peer-token auth, FIFO replies, timeout→FAILED,
  tasks/get-after-complete, streaming SSE parse, subscribe replay,
  anti-loop rejection, 429s, push e2e, input-required e2e, orchestrate

## Docs
- DESIGN.md out-of-scope synced with reality
- README and plugin.yaml updated

Still TODO (in DESIGN.md): file/data Parts, push-config get/list/delete,
tenant, gRPC/HTTP+JSON bindings, true mid-turn task abort.
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) 37481dccf4 fix(a2a): security hardening from code review
Critical fixes:
- SSRF protection: validate push notification callback URLs (block
  internal/private/loopback/metadata, enforce http/https only)
- Request body size limit: 1MB max (prevents memory exhaustion DoS)
- Thread safety: module-level locks for turn tracking, rate limiting,
  and pending task registry (was lazily initialized, racy)
- Peer identity: fall back to client IP when 'peer' field absent
  (prevents rate limiting collapse to single 'unknown' bucket)

Minor fixes:
- Watchdog survives reconnect: clear _watchdog_stop in connect()
- Redact error messages before sending to peers
- Remove dead _streaming_queues state
- Fix duplicate tags key in Agent Card skills
- Always send contextId in a2a_call (fixes client/server mismatch)
- Clear push_callbacks on disconnect
- SSE streaming cleanup via try/finally

16 new tests covering SSRF, body size, thread safety, watchdog
reconnect, error redaction, contextId consistency.
Tests: 97 passed, 3 deselected, 0 failed.
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) c6b0e3a80e feat(a2a): Phase 2+3 — SSE streaming, push notifications, anti-loop, orchestrate
Phase 2 (production features):
- SSE streaming: message/stream endpoint with proper event formatting
  (submitted → working → completed → done), keepalive pings
- Push notifications: HMAC-SHA256 signed webhooks via
  tasks/pushNotification/set, auto-fired on task completion
- Rate limiting: token-bucket per peer (A2A_RATE_LIMIT, default 60/min)
- Metrics: /metrics endpoint with counters, latency tracking, uptime
- Orphaned task watchdog: background thread cleans stale tasks (>300s)

Phase 3 (OpenClaw patterns):
- Anti-loop ping-pong: per-context turn counter with configurable
  max (A2A_MAX_PINGPONG_TURNS, default 5, max 20)
- Async durable messaging: pending task registry with register/
  complete/orphaned/clear lifecycle
- Capability-based routing: a2a_orchestrate tool with fan-out modes
  (all/first/best), matches peers by capabilities in config
- Dynamic Agent Cards: skills_from_real_toolsets() builds skill cards
  from actual toolset registry, not just names
- Trusted-peer approval (#56434): A2A_TRUSTED_PEERS env/config,
  is_trusted_peer() gate in inbound handler
- Task completion notifications (#56435): build_task includes
  status.message + artifacts for completed/failed states

Agent Card version bumped to 0.2.0, capabilities now advertise
streaming=True and pushNotifications=True.

Tests: 81 passed (45 existing + 36 new), 0 failed.
2026-08-02 15:10:15 -07:00
Kevin (OpenClaw Bot) 436e5a9cb5 fix(a2a): integrate all follow-up fixes for #41711
Consolidates 5 follow-up PRs onto the a2a-work branch:

1. Reply-capture fix (#56437): adapter.send() now only resolves the
   blocked RPC Future when metadata['notify'] is True (the gateway's
   final-reply marker). Interim sends no longer short-circuit the
   response. Also accepts **kwargs in connect() for reconnect compat.

2. Slash command passthrough (#53743): wrap_inbound() passes /-prefixed
   text through unwrapped so the gateway command processor sees it.
   Fixes /sethome deadlock during A2A onboarding. Documented security
   trade-off (bearer auth at network layer compensates).

3. Routable URL in Agent Card (#53736): _build_card() now derives URL
   from A2A_PUBLIC_URL env > X-Forwarded-Host/Host header > bind host.
   Fixes k8s bug where Agent Card advertised 0.0.0.0.

4. contextId multi-turn memory (#53756): _handle_inbound_task() now
   checks top-level params.contextId first (A2A spec), falls back to
   params.message.contextId (legacy). Outbound a2a_call also sends
   contextId at both top-level and inside message.

5. Type checker fixes (#53759): TypedDict for _SCHEMAS, _FunctionSchema,
   _ToolSchema. Removes str() band-aid casts.

All 45 tests pass including new tests for each fix.
Zero core files modified — only plugins/platforms/a2a/ and tests/.

Credits: @davidrobertson (#56437), @knoal (#53736, #53743, #53756,
#53759), @kuangmi-bit (slash command bug report), @gfdsa (k8s URL bug
report), @shivasymbl (#45996 userContext OBO).
2026-08-02 15:10:15 -07:00
teknium1 837003b1ed feat(a2a): consolidated Agent-to-Agent protocol plugin (closes #514)
Single platform-adapter plugin under plugins/platforms/a2a/ — zero core
edits — that supersedes the entire A2A PR/issue cluster. Built on the
ctx.register_platform + ctx.register_tool surface the codebase now exposes.

Outbound (a2a toolset): a2a_discover / a2a_call / a2a_list let the agent
call any A2A-compliant peer over JSON-RPC message/send. Inbound (platform
adapter): a stdlib http.server serves an Agent Card at
/.well-known/agent.json and routes incoming tasks into the agent's LIVE
gateway session (the #11025 insight) — same agent, full memory — returning
the reply over A2A.

Security on by default: no bearer token => 127.0.0.1-only bind; constant-
time bearer auth; inbound prompt-injection filtering + untrusted-peer
framing; outbound credential redaction; append-only audit log; per-context
conversation persistence outside the compaction pipeline.

Stdlib only (no a2a-sdk). 37 tests incl. a live HTTP round-trip
(card + message/send + reply) and a bearer-auth 401 path.
2026-08-02 15:10:15 -07:00