Files
openclaw/src/gateway/server-methods/chat.transcript-writes.guardrail.test.ts
Tak Hoffman 0cf93b8fa7 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

24 lines
1.2 KiB
TypeScript

import fs from "node:fs";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
// Guardrail: the "empty post-compaction context" regression came from gateway code appending
// Pi transcript message entries as raw JSONL without `parentId`.
//
// This test is intentionally simple and file-local: if someone reintroduces direct JSONL appends
// against `transcriptPath`, Pi's SessionManager parent chain can break again.
describe("gateway chat transcript writes (guardrail)", () => {
it("does not append transcript messages via raw fs.appendFileSync(transcriptPath, ...)", () => {
const chatTs = fileURLToPath(new URL("./chat.ts", import.meta.url));
const src = fs.readFileSync(chatTs, "utf-8");
// Disallow raw appends against the resolved transcript path variable.
// (The transcript header creation via writeFileSync is OK; the bug class is raw message appends.)
expect(src.includes("fs.appendFileSync(transcriptPath")).toBe(false);
// Ensure we keep using SessionManager for transcript message appends.
expect(src).toContain("SessionManager.open(transcriptPath)");
expect(src).toContain("appendMessage(");
});
});