fix(ws): suppress deferred-API RPC error events for opt-in callers (#1265)

Co-authored-by: m57 <41645758+Hy4ri@users.noreply.github.com>
This commit is contained in:
marciodps
2026-09-25 21:20:32 +03:00
committed by GitHub
co-authored by m57
parent ae9003decd
commit 8f40f028e0
2 changed files with 21 additions and 2 deletions
@@ -775,6 +775,7 @@ object HermesWsClient {
val method: String, val method: String,
val deferred: CompletableDeferred<Any?>, val deferred: CompletableDeferred<Any?>,
var timeoutJob: Job? = null, var timeoutJob: Job? = null,
val suppressErrorEvent: Boolean = false,
) )
/** Tracks in-flight [request] calls by their JSON-RPC id. */ /** Tracks in-flight [request] calls by their JSON-RPC id. */
@@ -794,11 +795,12 @@ object HermesWsClient {
method: String, method: String,
params: Map<String, Any> = emptyMap(), params: Map<String, Any> = emptyMap(),
timeoutMs: Long = REQUEST_TIMEOUT_MS, timeoutMs: Long = REQUEST_TIMEOUT_MS,
suppressErrorEvent: Boolean = false,
): CompletableDeferred<Any?> { ): CompletableDeferred<Any?> {
val deferred = CompletableDeferred<Any?>() val deferred = CompletableDeferred<Any?>()
val id = val id =
send(method, params) { reqId -> send(method, params) { reqId ->
pendingCalls[reqId] = PendingCall(method, deferred) pendingCalls[reqId] = PendingCall(method, deferred, suppressErrorEvent = suppressErrorEvent)
} }
deferred.invokeOnCompletion { cause -> deferred.invokeOnCompletion { cause ->
if (cause is CancellationException) { if (cause is CancellationException) {
@@ -1458,6 +1460,16 @@ object HermesWsClient {
disconnectIfIdleInBackground() disconnectIfIdleInBackground()
} }
} }
if (pendingCalls[event.id]?.suppressErrorEvent == true) {
// Opt-in suppression: the caller already handles the
// failure through the CompletableDeferred, so the event
// copy would only surface a duplicate UI banner for
// optional features (e.g. "subagent.list" on gateways
// without the method, issue #1089). Default request()
// behavior still emits the event for shared consumers.
resolvePending(event.id, null, event.error)
return
}
removeQueuedMessage(event.id) removeQueuedMessage(event.id)
resolvePending(event.id, null, event.error) resolvePending(event.id, null, event.error)
} }
@@ -37,6 +37,7 @@ object SubagentRepository {
.request( .request(
WsMethods.SUBAGENT_LIST, WsMethods.SUBAGENT_LIST,
mapOf("session_id" to sessionId), mapOf("session_id" to sessionId),
suppressErrorEvent = true,
).await() ).await()
decode<SubagentListResponse>(result) decode<SubagentListResponse>(result)
} catch (e: Exception) { } catch (e: Exception) {
@@ -65,7 +66,13 @@ object SubagentRepository {
if (sessionId.isNotBlank()) { if (sessionId.isNotBlank()) {
params["session_id"] = sessionId params["session_id"] = sessionId
} }
val result = HermesWsClient.request(WsMethods.SUBAGENT_TAIL, params).await() val result =
HermesWsClient
.request(
WsMethods.SUBAGENT_TAIL,
params,
suppressErrorEvent = true,
).await()
return decode<SubagentTailResponse>(result) return decode<SubagentTailResponse>(result)
} }