Skip to content

Commit d28d352

Browse files
brettchienclaude
andcommitted
feat(acp): serve /acp from the embedded openab run gateway
#1260 mounted the ACP endpoint only on the standalone `openab-gateway` binary (`serve()`). Fleet deployments run the unified binary's embedded gateway (`openab run`, main.rs) instead, which never called `serve()` — so `/acp` was unreachable there. Mount `/acp` on the embedded gateway too (gated by `OPENAB_ACP_ENABLED`), and route ACP replies back through the unified adapter's `dispatch_reply` (`platform == "acp"`). Requires the embedded gateway to be active (at least one platform / `[gateway]` configured). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 68d0748 commit d28d352

3 files changed

Lines changed: 30 additions & 3 deletions

File tree

docs/adr/acp-server-websocket-phase1.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ third-party ACP clients (Zed, JetBrains, …) interoperate — no custom method
2525

2626
## 2. Decision — Phase 1 primitive surface (ACP-conformant)
2727

28-
Transport: `GET /acp`, feature-gated `acp` + runtime `OPENAB_ACP_ENABLED`. Token auth
29-
on the WS upgrade via timing-safe compare (`subtle::ConstantTimeEq`,
30-
`OPENAB_ACP_AUTH_KEY`). JSON-RPC 2.0; non-`"2.0"` rejected with `-32600`.
28+
Transport: `GET /acp`, feature-gated `acp` + runtime `OPENAB_ACP_ENABLED`. Mounted on
29+
**both** the standalone `openab-gateway` binary (`serve()`) **and** the embedded
30+
gateway of `openab run` (the unified binary) — so fleet deployments that run
31+
`openab run` (not the standalone gateway) serve ACP too. Requires the embedded
32+
gateway to be active (at least one platform/`[gateway]` configured). ACP replies are
33+
routed back via the unified adapter's `dispatch_reply` (`platform == "acp"`).
34+
35+
Token auth on the WS upgrade via timing-safe compare (`subtle::ConstantTimeEq`,
36+
`OPENAB_ACP_AUTH_KEY`; unset ⇒ unauthenticated). JSON-RPC 2.0; non-`"2.0"` rejected
37+
with `-32600`.
3138

3239
### Client → Agent (requests)
3340

src/main.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,20 @@ async fn main() -> anyhow::Result<()> {
11131113
);
11141114
}
11151115

1116+
// ACP server endpoint — mount on the embedded gateway so `openab run`
1117+
// (not just the standalone gateway binary) serves ACP over WebSocket.
1118+
#[cfg(feature = "acp")]
1119+
if std::env::var("OPENAB_ACP_ENABLED")
1120+
.map(|v| v == "true" || v == "1")
1121+
.unwrap_or(false)
1122+
{
1123+
info!("unified: ACP server endpoint enabled at /acp");
1124+
app = app.route(
1125+
"/acp",
1126+
axum::routing::get(openab_gateway::adapters::acp_server::ws_upgrade),
1127+
);
1128+
}
1129+
11161130
let app = app.with_state(gw_state.clone());
11171131

11181132
// Bridge task: receive events from adapters via event_tx, dispatch to core

src/unified_adapter.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ impl UnifiedGatewayAdapter {
8989
.await;
9090
}
9191
}
92+
#[cfg(feature = "acp")]
93+
"acp" => {
94+
if let Some(ref registry) = self.gw_state.acp_reply_registry {
95+
openab_gateway::adapters::acp_server::handle_reply(reply, registry).await;
96+
}
97+
}
9298
other => {
9399
tracing::warn!(platform = other, "unified adapter: unknown platform, cannot route reply");
94100
}

0 commit comments

Comments
 (0)