- Use DELETE journal mode for session DBs instead of WAL. WAL doesn't sync reliably across Docker volume mounts (VirtioFS), causing dropped writes and duplicate deliveries. - Add 20s idle detection to end the query stream. The concurrent poll tracks SDK activity via a new 'activity' provider event. When no SDK events arrive for 20s and no messages are pending, the stream ends and the poll loop continues. - Add touchProcessing heartbeat so the host can distinguish active agents from idle ones by checking status_changed recency. - Catch query errors in the poll loop and write error responses to messages_out instead of crashing the process. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
import type { AgentProvider, AgentQuery, ProviderEvent, QueryInput } from './types.js';
|
|
|
|
/**
|
|
* Mock provider for testing. Returns canned responses.
|
|
* Supports push() — queued messages produce additional results.
|
|
*/
|
|
export class MockProvider implements AgentProvider {
|
|
private responseFactory: (prompt: string) => string;
|
|
|
|
constructor(responseFactory?: (prompt: string) => string) {
|
|
this.responseFactory = responseFactory ?? ((prompt) => `Mock response to: ${prompt.slice(0, 100)}`);
|
|
}
|
|
|
|
query(input: QueryInput): AgentQuery {
|
|
const pending: string[] = [];
|
|
let waiting: (() => void) | null = null;
|
|
let ended = false;
|
|
let aborted = false;
|
|
const responseFactory = this.responseFactory;
|
|
|
|
const events: AsyncIterable<ProviderEvent> = {
|
|
async *[Symbol.asyncIterator]() {
|
|
yield { type: 'activity' };
|
|
yield { type: 'init', sessionId: `mock-session-${Date.now()}` };
|
|
|
|
// Process initial prompt
|
|
yield { type: 'activity' };
|
|
yield { type: 'result', text: responseFactory(input.prompt) };
|
|
|
|
// Process any pushed follow-ups
|
|
while (!ended && !aborted) {
|
|
if (pending.length > 0) {
|
|
const msg = pending.shift()!;
|
|
yield { type: 'result', text: responseFactory(msg) };
|
|
continue;
|
|
}
|
|
// Wait for push() or end()
|
|
await new Promise<void>((resolve) => {
|
|
waiting = resolve;
|
|
});
|
|
waiting = null;
|
|
}
|
|
|
|
// Drain remaining
|
|
while (pending.length > 0) {
|
|
const msg = pending.shift()!;
|
|
yield { type: 'result', text: responseFactory(msg) };
|
|
}
|
|
},
|
|
};
|
|
|
|
return {
|
|
push(message: string) {
|
|
pending.push(message);
|
|
waiting?.();
|
|
},
|
|
end() {
|
|
ended = true;
|
|
waiting?.();
|
|
},
|
|
events,
|
|
abort() {
|
|
aborted = true;
|
|
waiting?.();
|
|
},
|
|
};
|
|
}
|
|
}
|