[Web runtime] Add HTTP SSE action support - #1598
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Several unresolved HTTP action correctness, reliability, and discoverability issues remain.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds HTTP Server-Sent Events support for action goals, including routing, client handling, tests, documentation, and CLI capability reporting.
Changes:
- Adds HTTP action SSE streaming for feedback and results.
- Updates the web client and TypeScript declarations.
- Adds integration coverage and CLI support reporting.
File summaries
| File | Findings |
|---|---|
web/index.d.ts |
No findings. |
web/client.js |
Moderate: generate unique action handle IDs and reject undefined terminal results. Nit: update outdated HTTP action transport documentation. |
test/test-web-action.js |
No findings. |
lib/runtime/transports/http.js |
Moderate: map unavailable actions to 503, buffer feedback before acceptance, and add action-stream heartbeats. Nit: include action routes in the OpenAPI document. |
bin/rclnodejs-web.js |
No findings. |
Review details
Suppressed comments (3)
lib/runtime/transports/http.js:36
- The dispatcher emits
code: 'action_unavailable'when an exposed action server is unavailable, but this new mapping omits it, soHttpActionConnectionfalls through to HTTP 500. Map this backend-unavailable case to 503 so HTTP clients can distinguish it from an internal action failure.
goal_rejected: 409,
action_failed: 500,
unknown_goal_id: 400,
lib/runtime/transports/http.js:425
feedbackcan arrive before_handleActionGoalResponse()sends the{ok:true}acknowledgement—the WebSocket client explicitly registers goals before sending to handle this race. This path writes that feedback immediately, so the HTTP stream can violate the documented accepted → feedback order and clients waiting foracceptedcan miss it; buffer feedback until the acceptance frame is emitted.
// Feedback delivery — zero or more, only while streaming.
if (frame.event === 'feedback') {
this._ensureStream();
this._writeEvent('feedback', frame.payload);
lib/runtime/transports/http.js:468
- Unlike
HttpSseConnection, this action stream has no heartbeat after the accepted event. A long-running goal that emits no feedback can therefore be closed by an idle proxy or client, causing_pumpActionStreamto reject withconnection_lostwhile the server continues the goal (HTTP disconnects do not cancel it). Reuse the configured SSE keep-alive behavior for action streams or provide an equivalent action heartbeat.
this.res.writeHead(200, {
'content-type': 'text/event-stream; charset=utf-8',
'cache-control': 'no-cache, no-transform',
connection: 'keep-alive',
'x-accel-buffering': 'no',
- Files reviewed: 3/5 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| * Also exposes `action` capabilities as a one-shot streaming request: | ||
| * | ||
| * POST /capability/action/<name> (text/event-stream response) | ||
| * | ||
| * The goal is sent as the JSON body; the response streams `accepted`, |
There was a problem hiding this comment.
🟡 Changes recommended
In-flight client streams cannot be closed, and server streams lack configured heartbeats.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 5/7 changed files
- Comments generated: 2
- Review effort level: Balanced
| this.res.writeHead(200, { | ||
| 'content-type': 'text/event-stream; charset=utf-8', | ||
| 'cache-control': 'no-cache, no-transform', | ||
| connection: 'keep-alive', | ||
| 'x-accel-buffering': 'no', |
| res = await fetch(url, { | ||
| method: 'POST', | ||
| headers: { 'content-type': 'application/json' }, | ||
| body: JSON.stringify(payload ?? {}), | ||
| }); |
No description provided.