Files
2026-07-13 13:39:12 +08:00

423 lines
14 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-context-handoff-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const handoffDb = await import("../../src/lib/db/contextHandoffs.ts");
const contextHandoff = await import("../../open-sse/services/contextHandoff.ts");
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
async function waitFor(fn, timeoutMs = 1500) {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const value = await fn();
if (value) return value;
await new Promise((resolve) => setTimeout(resolve, 25));
}
return null;
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("buildHandoffSystemMessage and injectHandoffIntoBody preserve existing history", () => {
const payload = {
sessionId: "sess-1",
comboName: "relay-combo",
fromAccount: "conn-a",
summary: "Working through combo relay integration",
keyDecisions: ["use 85% pre-handoff threshold", "delete handoff after successful replay"],
taskProgress: "Need to finish tests",
activeEntities: ["combo.ts", "chat.ts"],
messageCount: 42,
model: "codex/gpt-5.4",
warningThresholdPct: 0.85,
generatedAt: "2099-04-08T12:00:00.000Z",
expiresAt: "2099-04-08T17:00:00.000Z",
};
const body = {
messages: [
{ role: "system", content: "Original system message" },
{ role: "user", content: "Continue" },
],
};
const systemMessage = contextHandoff.buildHandoffSystemMessage(payload);
const injected = contextHandoff.injectHandoffIntoBody(body, payload);
assert.match(systemMessage, /<context_handoff>/);
assert.match(systemMessage, /combo\.ts/);
assert.equal(injected.messages[0].role, "system");
assert.match(String(injected.messages[0].content), /<context_handoff>/);
assert.equal(injected.messages[1].content, "Original system message");
assert.equal(body.messages.length, 2);
});
test("injectHandoffIntoBody preserves Responses API shape for native Codex requests", () => {
const payload = {
sessionId: "sess-1",
comboName: "relay-combo",
fromAccount: "conn-a",
summary: "Keep the current plan and continue seamlessly",
keyDecisions: ["prefer Responses-native payloads"],
taskProgress: "Need to carry state across account switches",
activeEntities: ["chat.ts", "contextHandoff.ts"],
messageCount: 8,
model: "codex/gpt-5.4",
warningThresholdPct: 0.85,
generatedAt: "2099-04-08T12:00:00.000Z",
expiresAt: "2099-04-08T17:00:00.000Z",
};
const body = {
instructions: "Original instructions",
input: [
{
type: "message",
role: "user",
content: [{ type: "input_text", text: "Continue" }],
},
],
};
const injected = contextHandoff.injectHandoffIntoBody(body, payload);
assert.equal("messages" in injected, false);
assert.deepEqual(injected.input, body.input);
assert.match(String(injected.instructions), /<context_handoff>/);
assert.match(String(injected.instructions), /Original instructions/);
});
test("parseHandoffJSON accepts fenced JSON and normalizes fields", () => {
const parsed = contextHandoff.parseHandoffJSON(`\`\`\`json
{"summary":" Ready to continue ","keyDecisions":["A","B"],"taskProgress":"Pending tests","activeEntities":["file.ts","combo.ts"]}
\`\`\``);
assert.equal(parsed.summary, "Ready to continue");
assert.deepEqual(parsed.keyDecisions, ["A", "B"]);
assert.equal(parsed.taskProgress, "Pending tests");
assert.deepEqual(parsed.activeEntities, ["file.ts", "combo.ts"]);
});
test("parseHandoffJSON returns null for invalid payloads without throwing", () => {
assert.equal(contextHandoff.parseHandoffJSON("not-json"), null);
});
test("resolveContextRelayConfig preserves explicit empty handoffProviders", () => {
const resolved = contextHandoff.resolveContextRelayConfig({
handoffProviders: [],
handoffThreshold: 0.9,
maxMessagesForSummary: 15,
});
assert.deepEqual(resolved.handoffProviders, []);
assert.equal(resolved.handoffThreshold, 0.9);
assert.equal(resolved.maxMessagesForSummary, 15);
});
test("maybeGenerateHandoff skips below the warning threshold", async () => {
let called = false;
contextHandoff.maybeGenerateHandoff({
sessionId: "sess-low",
comboName: "relay-combo",
connectionId: "conn-low",
percentUsed: 0.7,
messages: [{ role: "user", content: "hello" }],
model: "codex/gpt-5.4",
expiresAt: null,
handleSingleModel: async () => {
called = true;
return new Response("{}", { status: 200 });
},
});
await new Promise((resolve) => setImmediate(resolve));
assert.equal(called, false);
assert.equal(handoffDb.getHandoff("sess-low", "relay-combo"), null);
});
test("maybeGenerateHandoff persists a structured handoff once the threshold is reached", async () => {
const calls = [];
contextHandoff.maybeGenerateHandoff({
sessionId: "sess-save",
comboName: "relay-combo",
connectionId: "conn-save",
percentUsed: 0.88,
messages: [
{ role: "user", content: "Please continue wiring the combo" },
{ role: "assistant", content: "Working on it" },
],
model: "codex/gpt-5.4",
expiresAt: "2099-04-08T17:00:00.000Z",
handleSingleModel: async (body, modelStr) => {
calls.push({ body, modelStr });
return new Response(
JSON.stringify({
choices: [
{
message: {
content: JSON.stringify({
summary: "Relay summary generated",
keyDecisions: ["Use context-relay"],
taskProgress: "Integration in progress",
activeEntities: ["combo.ts", "contextHandoff.ts"],
}),
},
},
],
}),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
},
});
const saved = await waitFor(() => handoffDb.getHandoff("sess-save", "relay-combo"));
assert.ok(saved);
assert.equal(saved.fromAccount, "conn-save");
assert.equal(saved.summary, "Relay summary generated");
assert.deepEqual(saved.keyDecisions, ["Use context-relay"]);
assert.equal(calls.length, 1);
assert.equal(calls[0].modelStr, "codex/gpt-5.4");
assert.equal(calls[0].body._omnirouteSkipContextRelay, true);
assert.equal(calls[0].body._omnirouteInternalRequest, "context-handoff");
});
test("maybeGenerateHandoff deduplicates concurrent in-flight generations for the same session", async () => {
const calls = [];
let releaseGeneration;
const gate = new Promise((resolve) => {
releaseGeneration = resolve;
});
const options = {
sessionId: "sess-dedupe",
comboName: "relay-combo",
connectionId: "conn-dedupe",
percentUsed: 0.89,
messages: [{ role: "user", content: "Generate once" }],
model: "codex/gpt-5.4",
expiresAt: "2099-01-01T00:00:00.000Z",
handleSingleModel: async () => {
calls.push("summary");
await gate;
return new Response(
JSON.stringify({
choices: [
{
message: {
content: JSON.stringify({
summary: "Only one handoff should be generated",
keyDecisions: ["dedupe in flight"],
taskProgress: "done",
activeEntities: ["contextHandoff.ts"],
}),
},
},
],
}),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
},
};
contextHandoff.maybeGenerateHandoff(options);
contextHandoff.maybeGenerateHandoff(options);
await new Promise((resolve) => setTimeout(resolve, 25));
assert.equal(calls.length, 1);
releaseGeneration();
const saved = await waitFor(() => handoffDb.getHandoff("sess-dedupe", "relay-combo"));
assert.ok(saved);
assert.equal(saved.summary, "Only one handoff should be generated");
assert.equal(calls.length, 1);
});
test("maybeGenerateHandoff allows a new attempt after a failed in-flight generation", async () => {
let calls = 0;
const options = {
sessionId: "sess-retry",
comboName: "relay-combo",
connectionId: "conn-retry",
percentUsed: 0.9,
messages: [{ role: "user", content: "Retry after failure" }],
model: "codex/gpt-5.4",
expiresAt: "2099-01-01T00:00:00.000Z",
handleSingleModel: async () => {
calls += 1;
if (calls === 1) {
return new Response("temporary failure", { status: 500 });
}
return new Response(
JSON.stringify({
choices: [
{
message: {
content: JSON.stringify({
summary: "Retry succeeded",
keyDecisions: ["lock cleared after failure"],
taskProgress: "completed",
activeEntities: ["contextHandoff.ts"],
}),
},
},
],
}),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
},
};
contextHandoff.maybeGenerateHandoff(options);
await new Promise((resolve) => setTimeout(resolve, 40));
assert.equal(handoffDb.getHandoff("sess-retry", "relay-combo"), null);
contextHandoff.maybeGenerateHandoff(options);
const saved = await waitFor(() => handoffDb.getHandoff("sess-retry", "relay-combo"));
assert.ok(saved);
assert.equal(saved.summary, "Retry succeeded");
assert.equal(calls, 2);
});
test("maybeGenerateHandoff respects explicit empty handoffProviders and skips generation", async () => {
let called = false;
contextHandoff.maybeGenerateHandoff({
sessionId: "sess-disabled",
comboName: "relay-combo",
connectionId: "conn-disabled",
percentUsed: 0.92,
messages: [{ role: "user", content: "Do not generate" }],
model: "codex/gpt-5.4",
expiresAt: null,
config: { handoffProviders: [] },
handleSingleModel: async () => {
called = true;
return new Response("{}", { status: 200 });
},
});
await new Promise((resolve) => setImmediate(resolve));
assert.equal(called, false);
assert.equal(handoffDb.getHandoff("sess-disabled", "relay-combo"), null);
});
test("context handoff DB module upserts and deletes active handoffs", () => {
handoffDb.upsertHandoff({
sessionId: "sess-db",
comboName: "relay-combo",
fromAccount: "conn-a",
summary: "First summary",
keyDecisions: ["A"],
taskProgress: "step one",
activeEntities: ["a.ts"],
messageCount: 3,
model: "codex/gpt-5.4",
warningThresholdPct: 0.85,
generatedAt: "2099-04-08T10:00:00.000Z",
expiresAt: "2099-01-01T00:00:00.000Z",
});
handoffDb.upsertHandoff({
sessionId: "sess-db",
comboName: "relay-combo",
fromAccount: "conn-b",
summary: "Updated summary",
keyDecisions: ["B"],
taskProgress: "step two",
activeEntities: ["b.ts"],
messageCount: 4,
model: "codex/gpt-5.4",
warningThresholdPct: 0.86,
generatedAt: "2099-04-08T11:00:00.000Z",
expiresAt: "2099-01-01T00:00:00.000Z",
});
const saved = handoffDb.getHandoff("sess-db", "relay-combo");
assert.equal(saved.fromAccount, "conn-b");
assert.equal(saved.summary, "Updated summary");
assert.equal(handoffDb.hasActiveHandoff("sess-db", "relay-combo"), true);
handoffDb.deleteHandoff("sess-db", "relay-combo");
assert.equal(handoffDb.getHandoff("sess-db", "relay-combo"), null);
});
test("selectMessagesForSummary filters falsy values and preserves system/developer messages", () => {
const messages: (contextHandoff.MessageLike | null | undefined | false)[] = [
null,
undefined,
{ role: "system", content: "System 1" },
false,
{ role: "user", content: "User 1" },
{ role: "developer", content: "Dev 1" },
{ role: "assistant", content: "Assistant 1" },
{ role: "user", content: "User 2" },
];
const selected = contextHandoff.selectMessagesForSummary(
messages as contextHandoff.MessageLike[],
2
);
assert.equal(selected.length, 4);
assert.equal(selected[0].role, "system");
assert.equal(selected[1].role, "developer");
assert.equal(selected[2].role, "assistant");
assert.equal(selected[3].role, "user");
assert.equal(selected[3].content, "User 2");
});
test("selectMessagesForSummary with no system messages and oversized single remaining message still produces non-empty selection", () => {
// Build a single very large non-system message that exceeds MAX_HISTORY_TOKENS_FOR_SUMMARY
// (token estimator is ~4 chars/token, so 8000 tokens ≈ 32000 chars).
const hugeContent = "x".repeat(40000);
const messages: contextHandoff.MessageLike[] = [
{ role: "user", content: "first message" },
{ role: "assistant", content: hugeContent },
];
const selected = contextHandoff.selectMessagesForSummary(messages, 10);
// The function must return at least one message rather than [] so the handoff is not silently dropped.
assert.ok(selected.length > 0, "expected at least one message to be selected");
// formatMessagesForPrompt on the result must produce a non-empty string
// (guards the regression: previously returned [] → empty historyText → handoff skipped).
const historyText = selected
.map((m, i) => {
const role = typeof m.role === "string" ? m.role : "unknown";
const content = typeof m.content === "string" ? m.content.trim() : "";
return content ? `[${i + 1}] ${role.toUpperCase()}:\n${content}` : "";
})
.filter(Boolean)
.join("\n\n");
assert.ok(historyText.length > 0, "historyText must be non-empty so the handoff is generated");
});