Skip to content

Commit de4c3c7

Browse files
committed
test(core,webapp): terminate in-process agent e2e runs reliably
The in-process chat.agent e2e harness runs every leg against the single globalThis API registry. When a leg closed while its run was still parked on a session waitpoint, that run stayed alive into the next leg and its later teardown unregistered the globals out from under the active run. In CI this surfaced as Noop run-metadata and "can only be used from inside task.run()" errors across most legs. close() now aborts the run and awaits full teardown instead of racing a 10s timeout; the abort disables the session-waitpoint backend so any in-flight or later wait() returns at once; and the backend keeps its pending entry until the wait resolves so disable() can abort it. Runs no longer overlap, so the shared globals stay consistent.
1 parent e2f8bf0 commit de4c3c7

2 files changed

Lines changed: 17 additions & 6 deletions

File tree

apps/webapp/test/helpers/agentHarness.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function runRealChatAgent(opts: RunRealChatAgentOptions): RunningAgent {
6161
});
6262
const apiClient = apiClientManager.clientOrThrow();
6363
const manager = new StandardSessionStreamManager(apiClient, opts.baseUrl);
64-
const { runtimeManager, restore } = installSessionWaitpointBackend(apiClient);
64+
const { backend, runtimeManager, restore } = installSessionWaitpointBackend(apiClient);
6565

6666
const taskEntry = resourceCatalog.getTask(opts.agentId);
6767
if (!taskEntry) {
@@ -74,6 +74,11 @@ export function runRealChatAgent(opts: RunRealChatAgentOptions): RunningAgent {
7474
) => Promise<unknown>;
7575

7676
const runSignal = new AbortController();
77+
runSignal.signal.addEventListener("abort", () => {
78+
try {
79+
backend.disable();
80+
} catch {}
81+
});
7782
const runId = opts.runId ?? `run_${opts.addressingKey}`;
7883

7984
const idle =
@@ -128,10 +133,7 @@ export function runRealChatAgent(opts: RunRealChatAgentOptions): RunningAgent {
128133
);
129134
} catch {}
130135
runSignal.abort();
131-
await Promise.race([
132-
done.catch(() => {}),
133-
new Promise((resolve) => setTimeout(resolve, 10_000)),
134-
]);
136+
await done.catch(() => {});
135137
},
136138
};
137139
}

packages/core/src/v3/test/session-waitpoint-backend.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function parseTimeoutMs(timeout: string | undefined): number | undefined {
5858
*/
5959
export class SessionWaitpointBackend {
6060
private readonly pending = new Map<string, PendingWait>();
61+
private disabled = false;
6162

6263
constructor(private readonly apiClient: ApiClient) {}
6364

@@ -76,11 +77,17 @@ export class SessionWaitpointBackend {
7677
}
7778

7879
async wait(waitpointFriendlyId: string): Promise<WaitpointTokenResult> {
80+
if (this.disabled) {
81+
return {
82+
ok: false,
83+
output: JSON.stringify({ message: "Session waitpoint backend disabled" }),
84+
outputType: "application/json",
85+
};
86+
}
7987
const pending = this.pending.get(waitpointFriendlyId);
8088
if (!pending) {
8189
return { ok: true };
8290
}
83-
this.pending.delete(waitpointFriendlyId);
8491

8592
const timeoutMs = parseTimeoutMs(pending.timeout);
8693
let timer: ReturnType<typeof setTimeout> | undefined;
@@ -118,10 +125,12 @@ export class SessionWaitpointBackend {
118125
if (timer) {
119126
clearTimeout(timer);
120127
}
128+
this.pending.delete(waitpointFriendlyId);
121129
}
122130
}
123131

124132
disable(): void {
133+
this.disabled = true;
125134
for (const pending of this.pending.values()) {
126135
pending.abort.abort();
127136
}

0 commit comments

Comments
 (0)