Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/context-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2410,6 +2410,7 @@ export function buildContextEngineFactory(
if (beforeTurnQueryHint) {
try {
const beforeTurnTimeout = cfg.beforeTurnTimeoutMs ?? 5000;
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
const btResult = await Promise.race([
client.beforeTurnKernel({
sessionId,
Expand All @@ -2420,10 +2421,14 @@ export function buildContextEngineFactory(
cursor: undefined,
isHeartbeat: false,
} as unknown as Parameters<typeof client.beforeTurnKernel>[0]),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`BeforeTurnKernel timed out after ${beforeTurnTimeout}ms`)), beforeTurnTimeout)
),
]);
new Promise<never>((_, reject) => {
timeoutHandle = setTimeout(() => reject(new Error(`BeforeTurnKernel timed out after ${beforeTurnTimeout}ms`)), beforeTurnTimeout);
}),
]).finally(() => {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
});
const maxMemories = cfg.beforeTurnMaxMemories ?? 5;
const clamped = btResult.predictions && btResult.predictions.length > maxMemories
? selectTopByRelevance(btResult.predictions, strippedPrompt, maxMemories)
Expand Down
6 changes: 0 additions & 6 deletions test/integration/markdown-ingest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ import path from "node:path";

import { createMarkdownIngestionHandle, type FsDirentLike } from "../../src/markdown-ingest.js";

type FsDirentLike = {
name: string;
isDirectory(): boolean;
isFile(): boolean;
};

class FakeRpcClient {
calls: Array<{ method: string; params: unknown }> = [];
documents = new Map<string, { text: string; tokenizerId: string; coreDoc: boolean; sourceMeta: Record<string, unknown> }>();
Expand Down
52 changes: 52 additions & 0 deletions test/unit/context-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,58 @@ function makeMessage(role: string, content: string, id?: string) {
return { role, content, ...(id ? { id } : {}) };
}

test("context engine clears BeforeTurnKernel timeout after successful retrieval", async () => {
class BeforeTurnClient extends FakeClient {
async beforeTurnKernel(params: Record<string, unknown>) {
this.calls.push({ method: "beforeTurnKernel", params });
return { predictions: [] };
}
}

const originalSetTimeout = globalThis.setTimeout;
const originalClearTimeout = globalThis.clearTimeout;
const scheduled = new Set<ReturnType<typeof setTimeout>>();
const cleared = new Set<ReturnType<typeof setTimeout>>();

globalThis.setTimeout = ((...args: Parameters<typeof setTimeout>) => {
const handle = Reflect.apply(originalSetTimeout, globalThis, args) as ReturnType<typeof setTimeout>;
scheduled.add(handle);
return handle;
}) as typeof setTimeout;
globalThis.clearTimeout = ((handle?: Parameters<typeof clearTimeout>[0]) => {
if (handle) {
cleared.add(handle as ReturnType<typeof setTimeout>);
}
return Reflect.apply(originalClearTimeout, globalThis, [handle]);
}) as typeof clearTimeout;

try {
const client = new BeforeTurnClient();
const engine = buildContextEngineFactory(fakeRuntime(client), {
userId: "fixed-user",
beforeTurnTimeoutMs: 60_000,
});

await engine.assemble({
sessionId: "s1-before-turn-clears-timeout",
sessionKey: "sk1",
messages: [makeMessage("user", "what do you remember?")],
prompt: "what do you remember?",
tokenBudget: 4000,
});

assert.equal(client.calls.filter((call) => call.method === "beforeTurnKernel").length, 1);
assert.equal(scheduled.size, 1);
assert.deepEqual(cleared, scheduled, "successful before-turn retrieval should clear its timeout");
} finally {
for (const handle of scheduled) {
originalClearTimeout(handle);
}
globalThis.setTimeout = originalSetTimeout;
globalThis.clearTimeout = originalClearTimeout;
}
});

function openClawMetadataEnvelope(userText: string): string {
return [
"Conversation info (untrusted metadata):",
Expand Down