mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
ad1e946c7a
* feat(a2a): add A2A v1.0 agent-interop channel plugin Adds `a2a` as a bundled, default-off channel plugin so A2A-compliant agents (Hermes Agent, LangChain, CrewAI, Google ADK) can discover an OpenClaw gateway and send it tasks, and so OpenClaw can address configured peers. Inbound HTTP (`/.well-known/agent-card.json`, `/a2a/v1`) runs through the normal channel ingress path, so peer messages inherit allowlist admission, provenance framing, and session routing. The agent reply returns through the channel delivery callback and resolves the A2A task with its artifact, correlated per (peer, contextId) FIFO so concurrent sends cannot cross-talk. Wire format follows the canonical v1.0.0 spec sources (a2aproject/A2A specification/a2a.proto): PascalCase SendMessage/GetTask/CancelTask, supportedInterfaces[] rather than the 0.3-era top-level url/protocolVersion, member-presence Part oneof, TASK_STATE_*/ROLE_* enums, no Task.kind. The 0.3 dotted method names are accepted as explicit compat aliases because shipped Hermes-generation peers still send them. Security: per-peer bearer tokens compared in constant time, per-peer sliding window rate limit, 1 MiB body cap, 64 KiB inbound text cap, tasks scoped to their owning peer, outbound redirect: "error", and no inbound-controlled target URLs. With no peers configured the channel stays unconfigured and registers no routes. Live-verified against a real gateway on an isolated state dir with a mock provider: 31/31 proofs, including the agent reply round-tripping into the task artifact. That run caught two defects fixed here: - The agent card read `cfg.agents.list` directly, so gateways configured with the canonical `agents.entries` roster advertised zero skills. Now uses listAgentIds/resolveAgentConfig, which read either roster shape. - `returnImmediately` dispatches inherited the HTTP request's released work- admission root and every async task failed as GatewayDrainingError. Now reserves an independent root via runDetachedWebhookWork, matching sms/line/zalo. * fix(a2a): route outbound sends through the SSRF guard and land setup metadata CI on the first push surfaced four real defects that local `check:changed` did not cover (it runs no oxlint or typecheck lane): - Outbound peer sends used raw `fetch()`, tripping the channel/plugin no-raw-fetch boundary. They now go through `fetchWithSsrFGuard` with `maxRedirects: 0`, so A2A egress carries the same SSRF policy as every other plugin call and a redirect cannot re-target a delivered task. - The channel declared an empty setup contract while its package manifest declared none, so the bundled-channel shape contract failed. The wizard now collects `advertisedUrl` plus a first peer name/token pair, which is the minimum that leaves A2A actually configured (it stays unconfigured until one peer credential exists), and the package manifest mirrors that metadata. - Six lint errors in the test files: base-to-string on `BodyInit` bodies, an unused mock, and a shadowed `options` parameter. Request bodies now go through a typed reader that asserts the serialized-string contract before parsing. - A card assertion referenced `capabilities` without declaring it on the local response type, failing test typecheck. Outbound redirect handling moves from `redirect: "error"` to the guard's `manual` inspection; the adapter tests assert the new shape. * fix(a2a): drop route replaceExisting, accept configWrites, register runtime api CI after the interop run surfaced four more gates: - `registerPluginHttpRoute` used `replaceExisting: true`, tripping the GHSA-RQP8-Q22P-5J9Q rule: a duplicate path can silently replace another account's handler. A2A owns fixed global paths on a single account, so a duplicate registration means a stale or conflicting owner. It now fails loudly through `throwOnFailure` instead. - The channel schema rejected `channels.a2a.configWrites`, which every bundled channel must accept; added to the zod schema, the manifest schema, and the config type, then regenerated the channel metadata. - The new `input` assertion in the setup adapter needed a `// SAFETY:` line for the assertion ratchet. - `extensions/a2a/runtime-api.ts` needed registering in the runtime-api classification list, like every other bundled channel barrel. Also repairs a pre-existing incomplete `vi.mock` factory for `../infra/fs-safe.js` in the agent-delete suites. Both pass in isolation, but the shared-worker lane lets a sibling importer reach `readLocalFileSafely`, which the partial factory never exported; adding this channel changed shard composition and surfaced it. The factory now spreads the real module and overrides only `movePathToTrash`. * chore(a2a): refresh config baseline for the configWrites surface * chore(tests): adopt main's fs-safe mock repair for the agent-delete suites Main landed the same importOriginal spread for the shared-worker mock-defeat while this branch carried an equivalent fix; take main's version verbatim so the two do not conflict. * fix(a2a): isolate peer sessions and stop faking task cancellation Addresses both ClawSweeper P1 findings. Peer sessions: inbound routing never passed `dmScope`, so it fell back to `session.dmScope ?? "main"` and every authenticated peer converged on `agent:main:main` under default config. Untrusted remote content was joining the operator's own session, and peers shared conversation history. A2A now pins `per-account-channel-peer`; the peer id already embeds the A2A contextId, so each peer+context pair gets its own session regardless of global session config. Cancellation: `CancelTask` marked the task `TASK_STATE_CANCELED` and discarded the late reply, but the dispatched agent run kept going and could still use tools. The peer was told the work stopped when it had not. There is no plugin-facing seam to abort a live run, so cancellation is refused with -32004 instead of acknowledged, and the store's cancel path plus its canceled-delivery tombstone are deleted rather than left as a lying terminal state. Documented as an explicit limitation. Live-verified on a real gateway: 34/34 proofs, including three distinct `agent:main:a2a:default:direct:<peer>:<context>` sessions with zero traffic in `agent:main:main`, and both cancel spellings refused while the task stays WORKING. Re-ran the official a2a-sdk 1.1.2 Docker interop afterwards: 9/9.
24 lines
380 B
Markdown
24 lines
380 B
Markdown
---
|
|
summary: "A2A v1.0 Agent-to-Agent protocol channel plugin."
|
|
read_when:
|
|
- You are installing, configuring, or auditing the a2a plugin
|
|
title: "A2a plugin"
|
|
---
|
|
|
|
# A2a plugin
|
|
|
|
A2A v1.0 Agent-to-Agent protocol channel plugin.
|
|
|
|
## Distribution
|
|
|
|
- Package: `@openclaw/a2a`
|
|
- Install route: included in OpenClaw
|
|
|
|
## Surface
|
|
|
|
channels: `a2a`
|
|
|
|
## Related docs
|
|
|
|
- [a2a](/channels/a2a)
|