diff --git a/packages/junior/src/chat/runtime/reply-executor.ts b/packages/junior/src/chat/runtime/reply-executor.ts index aaa30563e..b1d6156f0 100644 --- a/packages/junior/src/chat/runtime/reply-executor.ts +++ b/packages/junior/src/chat/runtime/reply-executor.ts @@ -214,7 +214,7 @@ async function loadPiMessagesForTurn(args: { if (projection.length > 0) { return { canCompact: true, - piMessages: projection, + piMessages: stripRuntimeTurnContext(projection), }; } diff --git a/packages/junior/tests/unit/misc/respond-helpers-runtime-context.test.ts b/packages/junior/tests/unit/misc/respond-helpers-runtime-context.test.ts index b4b568eb7..68ce4a076 100644 --- a/packages/junior/tests/unit/misc/respond-helpers-runtime-context.test.ts +++ b/packages/junior/tests/unit/misc/respond-helpers-runtime-context.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vitest"; import type { PiMessage } from "@/chat/pi/messages"; -import { prependMissingRuntimeTurnContext } from "@/chat/respond-helpers"; +import { + hasRuntimeTurnContext, + prependMissingRuntimeTurnContext, + stripRuntimeTurnContext, +} from "@/chat/respond-helpers"; describe("prependMissingRuntimeTurnContext", () => { it("leaves recorded bootstrap prompts unchanged", () => { @@ -47,6 +51,62 @@ describe("prependMissingRuntimeTurnContext", () => { expect(JSON.stringify(updated[0])).not.toContain("slack:C123:updated"); }); + it("injects new requester context after stripping stale projected context", () => { + // Simulates a thread started by user A, where the runtime turn context + // from the first turn was persisted into the session log projection. + // When user B sends a follow-up message, `loadPiMessagesForTurn` must + // strip the stale context before returning, so `hasRuntimeTurnContext` + // returns false and a fresh context block carrying user B's identity + // is injected instead. + const projectionWithStaleContext: PiMessage[] = [ + { + role: "user", + content: [ + { + type: "text", + text: [ + "", + "", + "- full_name: User Alpha", + "- user_name: user.alpha", + "- user_id: U_ALPHA", + "", + "", + ].join("\n"), + }, + { type: "text", text: "original question" }, + ], + timestamp: 1, + }, + { + role: "assistant", + content: [{ type: "text", text: "original answer" }], + timestamp: 2, + }, + ] as PiMessage[]; + + // Before stripping: stale context is present, so no new context would be injected + expect(hasRuntimeTurnContext(projectionWithStaleContext)).toBe(true); + + // After stripping (what loadPiMessagesForTurn now does): fresh injection is possible + const stripped = stripRuntimeTurnContext(projectionWithStaleContext); + expect(hasRuntimeTurnContext(stripped)).toBe(false); + + const userBContext = [ + "", + "", + "- user_name: user.beta", + "- user_id: U_BETA", + "", + "", + ].join("\n"); + const updated = prependMissingRuntimeTurnContext(stripped, userBContext); + + expect(JSON.stringify(updated)).toContain("user.beta"); + expect(JSON.stringify(updated)).not.toContain("user.alpha"); + expect(JSON.stringify(updated)).not.toContain("User Alpha"); + }); + it("adds bootstrap context to a pre-prompt user boundary", () => { const messages: PiMessage[] = [ {