Files
openclaw/src/gateway/server-methods/chat.inject.parentid.e2e.test.ts

76 lines
2.7 KiB
TypeScript
Raw Normal View History

Gateway: fix post-compaction amnesia for injected messages (#12283) * Gateway: preserve Pi transcript parentId for injected messages Thread: unknown When: 2026-02-08 20:08 CST Repo: https://github.com/openclaw/openclaw.git Branch: codex/wip/2026-02-09/compact-post-compaction-parentid-fix Problem - Post-compaction turns sometimes lost the compaction summary + kept suffix in the *next* provider request. - Root cause was session graph corruption: gateway appended "stopReason: injected" transcript lines via raw JSONL writes without `parentId`. - Pi's `SessionManager.buildSessionContext()` walks the `parentId` chain from the current leaf; missing `parentId` can sever the active branch and hide the compaction entry. Fix - Use `SessionManager.appendMessage(...)` for injected assistant transcript writes so `parentId` is set to the current leaf. - Route `chat.inject` through the same helper to avoid duplicating the broken raw JSONL append logic. Why This Matters - The compaction algorithm may be correct, but if the leaf chain is broken right after compaction, the provider payload cannot include the summary/suffix "shape" Pi expects. Testing - pnpm test src/agents/pi-embedded-helpers.post-compaction-shape.test.ts src/agents/pi-embedded-runner/run.overflow-compaction.post-context.test.ts - pnpm build Notes - This is provider-shape agnostic: it fixes transcript structure so Anthropic/Gemini/etc all see the same post-compaction context. Resume - If post-compaction looks wrong again, inspect the session transcript for entries missing `parentId` immediately after `type: compaction`. * Gateway: guardrail test for transcript parentId (chat.inject) * Gateway: guardrail against raw transcript appends (chat.ts) * Gateway: add local AGENTS.md note to preserve Pi transcript parentId chain * Changelog: note gateway post-compaction amnesia fix * Gateway: store injected transcript messages with valid stopReason * Gateway: use valid stopReason in injected fallback
2026-02-08 23:07:31 -06:00
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
2026-02-17 13:36:48 +09:00
import { CURRENT_SESSION_VERSION } from "@mariozechner/pi-coding-agent";
Gateway: fix post-compaction amnesia for injected messages (#12283) * Gateway: preserve Pi transcript parentId for injected messages Thread: unknown When: 2026-02-08 20:08 CST Repo: https://github.com/openclaw/openclaw.git Branch: codex/wip/2026-02-09/compact-post-compaction-parentid-fix Problem - Post-compaction turns sometimes lost the compaction summary + kept suffix in the *next* provider request. - Root cause was session graph corruption: gateway appended "stopReason: injected" transcript lines via raw JSONL writes without `parentId`. - Pi's `SessionManager.buildSessionContext()` walks the `parentId` chain from the current leaf; missing `parentId` can sever the active branch and hide the compaction entry. Fix - Use `SessionManager.appendMessage(...)` for injected assistant transcript writes so `parentId` is set to the current leaf. - Route `chat.inject` through the same helper to avoid duplicating the broken raw JSONL append logic. Why This Matters - The compaction algorithm may be correct, but if the leaf chain is broken right after compaction, the provider payload cannot include the summary/suffix "shape" Pi expects. Testing - pnpm test src/agents/pi-embedded-helpers.post-compaction-shape.test.ts src/agents/pi-embedded-runner/run.overflow-compaction.post-context.test.ts - pnpm build Notes - This is provider-shape agnostic: it fixes transcript structure so Anthropic/Gemini/etc all see the same post-compaction context. Resume - If post-compaction looks wrong again, inspect the session transcript for entries missing `parentId` immediately after `type: compaction`. * Gateway: guardrail test for transcript parentId (chat.inject) * Gateway: guardrail against raw transcript appends (chat.ts) * Gateway: add local AGENTS.md note to preserve Pi transcript parentId chain * Changelog: note gateway post-compaction amnesia fix * Gateway: store injected transcript messages with valid stopReason * Gateway: use valid stopReason in injected fallback
2026-02-08 23:07:31 -06:00
import { describe, expect, it, vi } from "vitest";
import type { GatewayRequestContext } from "./types.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 dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-chat-inject-"));
const transcriptPath = path.join(dir, "sess.jsonl");
// Minimal Pi session header so SessionManager can open/append safely.
fs.writeFileSync(
transcriptPath,
`${JSON.stringify({
type: "session",
version: CURRENT_SESSION_VERSION,
id: "sess-1",
timestamp: new Date(0).toISOString(),
cwd: "/tmp",
})}\n`,
"utf-8",
);
vi.doMock("../session-utils.js", async (importOriginal) => {
2026-02-17 15:47:23 +09:00
const original = await importOriginal<typeof import("../session-utils.js")>();
Gateway: fix post-compaction amnesia for injected messages (#12283) * Gateway: preserve Pi transcript parentId for injected messages Thread: unknown When: 2026-02-08 20:08 CST Repo: https://github.com/openclaw/openclaw.git Branch: codex/wip/2026-02-09/compact-post-compaction-parentid-fix Problem - Post-compaction turns sometimes lost the compaction summary + kept suffix in the *next* provider request. - Root cause was session graph corruption: gateway appended "stopReason: injected" transcript lines via raw JSONL writes without `parentId`. - Pi's `SessionManager.buildSessionContext()` walks the `parentId` chain from the current leaf; missing `parentId` can sever the active branch and hide the compaction entry. Fix - Use `SessionManager.appendMessage(...)` for injected assistant transcript writes so `parentId` is set to the current leaf. - Route `chat.inject` through the same helper to avoid duplicating the broken raw JSONL append logic. Why This Matters - The compaction algorithm may be correct, but if the leaf chain is broken right after compaction, the provider payload cannot include the summary/suffix "shape" Pi expects. Testing - pnpm test src/agents/pi-embedded-helpers.post-compaction-shape.test.ts src/agents/pi-embedded-runner/run.overflow-compaction.post-context.test.ts - pnpm build Notes - This is provider-shape agnostic: it fixes transcript structure so Anthropic/Gemini/etc all see the same post-compaction context. Resume - If post-compaction looks wrong again, inspect the session transcript for entries missing `parentId` immediately after `type: compaction`. * Gateway: guardrail test for transcript parentId (chat.inject) * Gateway: guardrail against raw transcript appends (chat.ts) * Gateway: add local AGENTS.md note to preserve Pi transcript parentId chain * Changelog: note gateway post-compaction amnesia fix * Gateway: store injected transcript messages with valid stopReason * Gateway: use valid stopReason in injected fallback
2026-02-08 23:07:31 -06:00
return {
...original,
loadSessionEntry: () => ({
storePath: path.join(dir, "sessions.json"),
Gateway: fix post-compaction amnesia for injected messages (#12283) * Gateway: preserve Pi transcript parentId for injected messages Thread: unknown When: 2026-02-08 20:08 CST Repo: https://github.com/openclaw/openclaw.git Branch: codex/wip/2026-02-09/compact-post-compaction-parentid-fix Problem - Post-compaction turns sometimes lost the compaction summary + kept suffix in the *next* provider request. - Root cause was session graph corruption: gateway appended "stopReason: injected" transcript lines via raw JSONL writes without `parentId`. - Pi's `SessionManager.buildSessionContext()` walks the `parentId` chain from the current leaf; missing `parentId` can sever the active branch and hide the compaction entry. Fix - Use `SessionManager.appendMessage(...)` for injected assistant transcript writes so `parentId` is set to the current leaf. - Route `chat.inject` through the same helper to avoid duplicating the broken raw JSONL append logic. Why This Matters - The compaction algorithm may be correct, but if the leaf chain is broken right after compaction, the provider payload cannot include the summary/suffix "shape" Pi expects. Testing - pnpm test src/agents/pi-embedded-helpers.post-compaction-shape.test.ts src/agents/pi-embedded-runner/run.overflow-compaction.post-context.test.ts - pnpm build Notes - This is provider-shape agnostic: it fixes transcript structure so Anthropic/Gemini/etc all see the same post-compaction context. Resume - If post-compaction looks wrong again, inspect the session transcript for entries missing `parentId` immediately after `type: compaction`. * Gateway: guardrail test for transcript parentId (chat.inject) * Gateway: guardrail against raw transcript appends (chat.ts) * Gateway: add local AGENTS.md note to preserve Pi transcript parentId chain * Changelog: note gateway post-compaction amnesia fix * Gateway: store injected transcript messages with valid stopReason * Gateway: use valid stopReason in injected fallback
2026-02-08 23:07:31 -06:00
entry: {
sessionId: "sess-1",
sessionFile: transcriptPath,
},
}),
};
});
const { chatHandlers } = await import("./chat.js");
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,
2026-02-17 15:47:23 +09:00
req: {} as never,
client: null as never,
isWebchatConnect: () => false,
context: context as unknown as GatewayRequestContext,
Gateway: fix post-compaction amnesia for injected messages (#12283) * Gateway: preserve Pi transcript parentId for injected messages Thread: unknown When: 2026-02-08 20:08 CST Repo: https://github.com/openclaw/openclaw.git Branch: codex/wip/2026-02-09/compact-post-compaction-parentid-fix Problem - Post-compaction turns sometimes lost the compaction summary + kept suffix in the *next* provider request. - Root cause was session graph corruption: gateway appended "stopReason: injected" transcript lines via raw JSONL writes without `parentId`. - Pi's `SessionManager.buildSessionContext()` walks the `parentId` chain from the current leaf; missing `parentId` can sever the active branch and hide the compaction entry. Fix - Use `SessionManager.appendMessage(...)` for injected assistant transcript writes so `parentId` is set to the current leaf. - Route `chat.inject` through the same helper to avoid duplicating the broken raw JSONL append logic. Why This Matters - The compaction algorithm may be correct, but if the leaf chain is broken right after compaction, the provider payload cannot include the summary/suffix "shape" Pi expects. Testing - pnpm test src/agents/pi-embedded-helpers.post-compaction-shape.test.ts src/agents/pi-embedded-runner/run.overflow-compaction.post-context.test.ts - pnpm build Notes - This is provider-shape agnostic: it fixes transcript structure so Anthropic/Gemini/etc all see the same post-compaction context. Resume - If post-compaction looks wrong again, inspect the session transcript for entries missing `parentId` immediately after `type: compaction`. * Gateway: guardrail test for transcript parentId (chat.inject) * Gateway: guardrail against raw transcript appends (chat.ts) * Gateway: add local AGENTS.md note to preserve Pi transcript parentId chain * Changelog: note gateway post-compaction amnesia fix * Gateway: store injected transcript messages with valid stopReason * Gateway: use valid stopReason in injected fallback
2026-02-08 23:07:31 -06:00
});
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");
});
});