feat(agent): added non-interrupting aside message support

- Added a new aside-message source on `Agent` and exposed it in `AgentLoopConfig` as `getAsideMessages`.
- Updated the agent loop to poll aside messages after tool batches and before yielding, merging them with follow-up messages before continuing.
- Changed coding-agent session and yield queue handling to pull queued background messages via `drainMessages` at step boundaries instead of streaming-only injection.
This commit is contained in:
can1357
2026-06-08 06:08:43 +02:00
parent c7c0b67b8c
commit de03d7f3d1
5 changed files with 55 additions and 10 deletions
@@ -10,7 +10,7 @@ export interface YieldDispatcher<P> {
export interface YieldQueueOptions {
isStreaming: () => boolean;
injectStreaming(msg: AgentMessage): void;
injectStreaming?(msg: AgentMessage): void;
injectIdle(messages: AgentMessage[]): Promise<void>;
scheduleIdleFlush(run: () => Promise<void>): void;
}
@@ -85,7 +85,7 @@ export class YieldQueue {
if (!message) continue;
if (mode === "streaming") {
try {
this.#options.injectStreaming(message);
this.#options.injectStreaming?.(message);
} catch (error) {
logger.warn("Yield queue streaming dispatch failed", { kind, error: formatError(error) });
}
@@ -102,6 +102,24 @@ export class YieldQueue {
}
}
/**
* Build and remove all queued messages, applying each dispatcher's staleness
* filter. No injection side effects — used for pull-based delivery at agent
* step boundaries (see `Agent.setAsideMessageProvider`), so background-job
* completions and late diagnostics reach the model between requests without
* the agent having to stop.
*/
drainMessages(): AgentMessage[] {
const messages: AgentMessage[] = [];
for (const [kind, dispatcher] of this.#dispatchers) {
const entries = this.#drain(kind);
if (entries.length === 0) continue;
const message = this.#build(kind, dispatcher, entries);
if (message) messages.push(message);
}
return messages;
}
clear(): void {
this.#entries.clear();
this.#idleFlushPending = false;