v0.54.0
Minor release: the MCP fleet-control surface gains the tools that act, and a security + reuse sweep lands across the event server.
Added
MCP fleet control gains its write half (#944). POST /mcp on the event-server Worker adds bobi_read_transcript, bobi_send_message, and bobi_lifecycle alongside 0.53.0's three read tools, so an agent can act on a fleet rather than only observe it. No new authority and no new admin vocabulary — the same FLEET_OPERATOR_TOKEN gate runs before any tool body, and the tools drive the same nine commands the sidecar already answers.
Commands resolve through a bounded server-side wait (5s, MCP_COMMAND_WAIT_MS, 0 disables): fast commands return in one tool call instead of three, slow ones return status: "pending" plus a command_id. pending means not-yet-answered, never failed.
Three behaviors are stated in the tool descriptions because an agent would otherwise believe the opposite: bobi_send_message does not return the agent's reply (the supervisor resolves chat only when the whole turn ends, minutes later — read it back with bobi_read_transcript); transcript output is untrusted third-party content, returned in its own block between explicit warnings; and bobi_lifecycle requires a reason, recorded as an audit control, with self-targeting allowed and annotated rather than refused.
Fixed
- Attribute injection in the agent-reply markdown renderer (#942) — agent output could close an
hrefattribute and land a live event handler, and agent replies are prompt-injectable from anything the agent reads. - Unvalidated register payload on the unauthenticated mint path (#942) — a bare string registered one subscription per character; a non-string element threw after an orphan bubble had been persisted.
- Unbounded request-body buffering (#942) — the 413 arrived after the memory was spent, and on a non-loopback bind the peak was unauthenticated. Now capped at 8 MiB (
BOBI_ES_MAX_BODY_BYTES). - Slack file uploads in DMs and thread replies never arrived (#945) — every
file_share-subtyped message was skipped before classification. - The circuit breaker's pause buffer was unbounded and never released (#945) — a conversation that tripped then went quiet held its buffer for the life of the process.
/channels/sendaccepted required text and delivered it nowhere (#945) — on WhatsApp the user got the file and never the sentence explaining it.- Concurrent Slack workspace registrations raced away a bot's signing secret (#945) — a fleet roll restarting both agents could 401 one app's inbound events.
Changed
- The release workflow publishes the pins the image dispatch needs (#943).
- Event-server reuse sweep (#945) — eight duplicated or dead surfaces converged.
Full detail in CHANGELOG.md.