test: cover complete fake agent flow

This commit is contained in:
Wenxin Duan
2026-09-08 10:35:11 +08:00
parent c4a88e4aae
commit 89ce7973c3
8 changed files with 406 additions and 2 deletions
+1
View File
@@ -19,6 +19,7 @@ export * from "./sessions/in-memory-session-registry.js";
export * from "./sessions/session-registry.js";
export * from "./skills/skill-catalog.js";
export * from "./skills/skill-event-inference.js";
export * from "./testing/fake-agent-session.js";
export * from "./tools/mock-ads-metrics.js";
export * from "./tools/save-report-draft.js";
export * from "./tools/tool-policy.js";
@@ -82,8 +82,17 @@ export class HarnessSession {
eventStore: this.#eventStore,
...(skillInference === undefined ? {} : { skillInference }),
});
let eventQueue = Promise.resolve();
let eventError: unknown;
const unsubscribe = port.subscribe((event) => {
void adapter.observe(event);
eventQueue = eventQueue.then(async () => {
if (eventError !== undefined) return;
try {
await adapter.observe(event);
} catch (error) {
eventError = error;
}
});
});
const active: ActiveRun = {
runId,
@@ -108,7 +117,11 @@ export class HarnessSession {
void Promise.resolve()
.then(() => port.prompt(message.text, { images }))
.then(() => this.#complete(active))
.then(async () => {
await eventQueue;
if (eventError !== undefined) throw eventError;
this.#complete(active);
})
.catch((error: unknown) => this.#fail(active, error));
return { runId };
}
@@ -0,0 +1,46 @@
import type { ImageContent } from "@earendil-works/pi-ai";
import type { AgentSessionPort } from "../agent/agent-session-port.js";
type Listener = (event: unknown) => void;
type Driver = (
session: FakeAgentSession,
prompt: { text: string; images: ImageContent[] },
) => Promise<void>;
export class FakeAgentSession implements AgentSessionPort {
readonly #listeners = new Set<Listener>();
readonly prompts: Array<{ text: string; images: ImageContent[] }> = [];
public aborted = false;
public disposed = false;
public constructor(private readonly driver: Driver) {}
public subscribe(listener: Listener): () => void {
this.#listeners.add(listener);
return () => this.#listeners.delete(listener);
}
public async prompt(text: string, options: { images: ImageContent[] }): Promise<void> {
const prompt = { text, images: options.images.map((image) => ({ ...image })) };
this.prompts.push(prompt);
await this.driver(this, prompt);
}
public emit(event: unknown): void {
for (const listener of [...this.#listeners]) listener(event);
}
public async flush(): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, 0));
}
public abort(): Promise<void> {
this.aborted = true;
return Promise.resolve();
}
public dispose(): void {
this.disposed = true;
this.#listeners.clear();
}
}