refactor: extract gateway transcript append helper

This commit is contained in:
Peter Steinberger
2026-02-22 14:43:44 +00:00
parent 8e6b465fa8
commit 142c0a7f7d
5 changed files with 121 additions and 109 deletions

View File

@@ -1,62 +1,37 @@
import fs from "node:fs";
import { describe, expect, it, vi } from "vitest";
import { createMockSessionEntry, createTranscriptFixtureSync } from "./chat.test-helpers.js";
import type { GatewayRequestContext } from "./types.js";
import { describe, expect, it } from "vitest";
import { appendInjectedAssistantMessageToTranscript } from "./chat-transcript-inject.js";
import { createTranscriptFixtureSync } from "./chat.test-helpers.js";
// Guardrail: Ensure gateway "injected" assistant transcript messages are appended via SessionManager,
// so they are attached to the current leaf with a `parentId` and do not sever compaction history.
describe("gateway chat.inject transcript writes", () => {
it("appends a Pi session entry that includes parentId", async () => {
const sessionId = "sess-1";
const { transcriptPath } = createTranscriptFixtureSync({
const { dir, transcriptPath } = createTranscriptFixtureSync({
prefix: "openclaw-chat-inject-",
sessionId,
sessionId: "sess-1",
});
vi.doMock("../session-utils.js", async (importOriginal) => {
const original = await importOriginal<typeof import("../session-utils.js")>();
return {
...original,
loadSessionEntry: () =>
createMockSessionEntry({
transcriptPath,
sessionId,
canonicalKey: "k1",
}),
};
});
try {
const appended = appendInjectedAssistantMessageToTranscript({
transcriptPath,
message: "hello",
});
expect(appended.ok).toBe(true);
expect(appended.messageId).toBeTruthy();
const { chatHandlers } = await import("./chat.js");
const lines = fs.readFileSync(transcriptPath, "utf-8").split(/\r?\n/).filter(Boolean);
expect(lines.length).toBeGreaterThanOrEqual(2);
const respond = vi.fn();
type InjectCtx = Pick<GatewayRequestContext, "broadcast" | "nodeSendToSession">;
const context: InjectCtx = {
broadcast: vi.fn() as unknown as InjectCtx["broadcast"],
nodeSendToSession: vi.fn() as unknown as InjectCtx["nodeSendToSession"],
};
await chatHandlers["chat.inject"]({
params: { sessionKey: "k1", message: "hello" },
respond,
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: context as unknown as GatewayRequestContext,
});
const last = JSON.parse(lines.at(-1) as string) as Record<string, unknown>;
expect(last.type).toBe("message");
expect(respond).toHaveBeenCalled();
const [, payload, error] = respond.mock.calls.at(-1) ?? [];
expect(error).toBeUndefined();
expect(payload).toMatchObject({ ok: true });
const lines = fs.readFileSync(transcriptPath, "utf-8").split(/\r?\n/).filter(Boolean);
expect(lines.length).toBeGreaterThanOrEqual(2);
const last = JSON.parse(lines.at(-1) as string) as Record<string, unknown>;
expect(last.type).toBe("message");
// The regression we saw: raw jsonl appends omitted this field entirely.
expect(Object.prototype.hasOwnProperty.call(last, "parentId")).toBe(true);
expect(last).toHaveProperty("id");
expect(last).toHaveProperty("message");
// The regression we saw: raw jsonl appends omitted this field entirely.
expect(Object.prototype.hasOwnProperty.call(last, "parentId")).toBe(true);
expect(last).toHaveProperty("id");
expect(last).toHaveProperty("message");
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});