fix(session): isolate event listener failures in agent fan-out
Both `AgentSession.#emit` (session/agent-session.ts) and `Agent.#emit`
(packages/agent/src/agent.ts) iterated listeners with no error isolation.
A synchronous throw in any subscriber aborted the for-loop, so later
subscribers (TUI rendering, ACP bridge, task executor progress,
hindsight) silently missed events. Many listeners — see
`modes/controllers/event-controller.ts:141` and
`modes/controllers/input-controller.ts:576` — are registered as
`async (event) => { await this.handleEvent(event); }`; the returned
Promise was dropped, so any rejection became an unhandled rejection.
Wrap each listener invocation in try/catch and attach a `.catch` to any
returned thenable. Errors are logged via `logger.warn` (already imported
in agent-session.ts) and `console.error` (agent.ts has no logger
dependency, keep it that way).
Test: new `test/session/emit-listener-isolation.test.ts` registers two
listeners on both classes; first listener throws (or returns a rejecting
Promise); asserts the second listener still receives the event AND no
`unhandledRejection` fires. 4 cases (sync+async × Agent+AgentSession).
All fail on current main; all pass with the fix.
This commit is contained in:
@@ -1307,10 +1307,25 @@ export class AgentSession {
|
||||
|
||||
/** Emit an event to all listeners */
|
||||
#emit(event: AgentSessionEvent): void {
|
||||
// Copy array before iteration to avoid mutation during iteration
|
||||
// Copy array before iteration to avoid mutation during iteration.
|
||||
const listeners = [...this.#eventListeners];
|
||||
for (const l of listeners) {
|
||||
l(event);
|
||||
try {
|
||||
const result = l(event);
|
||||
// Listener may be an async function whose returned Promise we don't await;
|
||||
// attach a catch so a rejection does not become an unhandled rejection.
|
||||
if (result && typeof (result as Promise<unknown>).then === "function") {
|
||||
(result as Promise<unknown>).catch(err => {
|
||||
logger.warn("AgentSession listener rejected", {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn("AgentSession listener threw", {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user