Per-connection protocol message control#892
Conversation
…bled predicate Add per-connection control over protocol text frames (CF_AGENT_IDENTITY, CF_AGENT_STATE, CF_AGENT_MCP_SERVERS). Override shouldSendProtocolMessages() to return false for connections that should not receive these frames. This enables binary-only clients (e.g. MQTT devices) to connect without receiving JSON text frames, while still participating in RPC and regular messaging. Changes: - Add shouldSendProtocolMessages(connection, ctx) hook (default: true) - Add isConnectionProtocolEnabled(connection) predicate helper - Update onConnect to skip protocol messages when hook returns false - Update state and MCP broadcasts to exclude no-protocol connections - Generalize connection state wrapping to handle multiple internal flags - Add comprehensive test suite (12 tests) Closes #881
🦋 Changeset detectedLatest commit: 097da58 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
|
/bonk looks like you missed one, |
|
The code is already correct. Looking at the implementation:
The Is there a specific scenario where you're seeing |
Everything is done. Here's a summary of what was implemented and the tradeoffs/edge cases analysis:
Implementation Summary
What was added
shouldSendProtocolMessages(connection, ctx)— overridable hook onAgent(default:true). Called once on connect to determine if protocol text frames should be sent.isConnectionProtocolEnabled(connection)— public predicate to check protocol status at runtime.Internal plumbing:
_cf_no_protocolflag stored in connection state (survives hibernation)_broadcastProtocol()helper that filters out no-protocol connections before callingbroadcast()_setConnectionNoProtocol()private setter_cf_readonlyand_cf_no_protocol) rather than just the readonly keyFiles changed
packages/agents/src/index.ts— core implementation (+174/-43 lines)packages/agents/src/tests/agents/protocol-messages.ts— test agentpackages/agents/src/tests/protocol-messages.test.ts— 11 tests across 7 describe blockspackages/agents/src/tests/agents/index.ts,worker.ts,wrangler.jsonc— test infrastructureTest coverage (11 tests)
isConnectionProtocolEnabledreturns correct valuesconnection.stateTradeoffs and Edge Cases
1. Broadcast performance with many no-protocol connections
The
_broadcastProtocol()method iterates over all connections viagetConnections()to build the exclusion list, then passes it tobroadcast(). If you have hundreds of no-protocol connections, this builds a large array on every state change. The alternative would be to overridebroadcast()itself, but that would break the partyserver contract. For the expected use case (a few binary clients alongside web clients), this is fine.2. Not dynamically settable (by design)
Per the request, this is evaluated once on connect and stored as a flag. There is no
setConnectionProtocolEnabled()method. If you need to change a connection's protocol status, you'd need to disconnect and reconnect. This simplifies the mental model and avoids edge cases around mid-session protocol toggling.3. RPC still sends JSON text frames
No-protocol connections still receive RPC responses as JSON text frames. This is intentional — the feature suppresses only the automatic protocol messages (identity, state sync, MCP servers), not user-initiated communication. If a binary-only client needs pure binary communication, they should avoid calling RPC methods that return JSON. This could be a point of confusion.
4. Client-initiated state updates still work
A no-protocol connection can still send
CF_AGENT_STATEmessages to update state, and those updates will be processed and broadcast to other (protocol-enabled) connections. The no-protocol flag only controls outbound protocol messages.5. Interaction with
sendIdentityOnConnect: falseWhen
sendIdentityOnConnectisfalseat the agent level ANDshouldSendProtocolMessagesreturnsfalsefor a connection, the behavior is consistent — no identity is sent either way. The per-connection hook is a strict superset of the static option for the affected connections.6. Workflow broadcasts are NOT filtered
The
_workflow_broadcast()method (used byAgentWorkflow) callsthis.broadcast()directly and does NOT go through_broadcastProtocol(). This is intentional — workflow messages are application-level, not protocol-level. If this should also be filtered, it would be a separate decision.7.
observabilityevents still fireThe "Connection established" observability event still fires for no-protocol connections. This is correct — observability should track all connections regardless of protocol status.
8. Connection state wrapping generalization
The wrapping in
_ensureConnectionWrappedwas refactored from single-key (_cf_readonly) to multi-key (CF_INTERNAL_KEYSarray). This is more maintainable but slightly changes the internal mechanics — instead of destructuring a single key, it iterates over the key list. The behavior is identical for existing readonly functionality (verified by all 17 readonly tests passing).Closes #881
github run