diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 1b632e9c5..b6e533415 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed collab guest prompts being sent to models as unframed developer context, so guest messages now retain their transcript attribution while reaching the model as prioritized user interjections ([#7288](https://github.com/can1357/oh-my-pi/issues/7288)). + ## [17.2.3] - 2026-08-01 ### Changed diff --git a/packages/coding-agent/src/session/messages.ts b/packages/coding-agent/src/session/messages.ts index 4430fabdb..58626f533 100644 --- a/packages/coding-agent/src/session/messages.ts +++ b/packages/coding-agent/src/session/messages.ts @@ -24,6 +24,7 @@ import type { } from "@oh-my-pi/pi-ai"; import * as AIError from "@oh-my-pi/pi-ai/error"; import { isRecord, logger, prompt } from "@oh-my-pi/pi-utils"; +import { COLLAB_PROMPT_MESSAGE_TYPE } from "@oh-my-pi/pi-wire"; import userInterjectionTemplate from "../prompts/steering/user-interjection.md" with { type: "text" }; import { formatTitleConversationContext, type TitleConversationTurn } from "../tiny/message-preproc"; @@ -614,8 +615,18 @@ export function normalizeCustomMessagePayload( }; } -function isSteeringUserMessage(message: AgentMessage | undefined): message is UserMessage & { steering: true } { - return message?.role === "user" && message.steering === true; +type SteeringUserMessage = + | (UserMessage & { steering: true }) + | (CustomMessage & { + customType: typeof COLLAB_PROMPT_MESSAGE_TYPE; + attribution: "user"; + }); + +function isSteeringUserMessage(message: AgentMessage | undefined): message is SteeringUserMessage { + if (message?.role === "user") return message.steering === true; + return ( + message?.role === "custom" && message.customType === COLLAB_PROMPT_MESSAGE_TYPE && message.attribution === "user" + ); } function userMessageWithoutSteering(message: UserMessage): UserMessage { @@ -655,17 +666,26 @@ function getArrayContentImages(content: (TextContent | ImageContent)[]): ImageCo return images ?? []; } -function wrapSteeringUserMessage(message: UserMessage): UserMessage { +function wrapSteeringUserMessage(message: SteeringUserMessage): UserMessage { + const userMessage: UserMessage = + message.role === "user" + ? userMessageWithoutSteering(message) + : { + role: "user", + content: message.content, + attribution: "user", + timestamp: message.timestamp, + }; if (typeof message.content === "string") { - if (message.content.length === 0) return message; - return { ...userMessageWithoutSteering(message), content: renderSteeringEnvelope(message.content) }; + if (message.content.length === 0) return message.role === "user" ? message : userMessage; + return { ...userMessage, content: renderSteeringEnvelope(message.content) }; } const text = getArrayContentText(message.content); - if (text.length === 0) return message; + if (text.length === 0) return message.role === "user" ? message : userMessage; const content: (TextContent | ImageContent)[] = [{ type: "text", text: renderSteeringEnvelope(text) }]; content.push(...getArrayContentImages(message.content)); - return { ...userMessageWithoutSteering(message), content }; + return { ...userMessage, content }; } export function wrapSteeringForModel(messages: AgentMessage[]): AgentMessage[] { diff --git a/packages/coding-agent/test/session-messages.test.ts b/packages/coding-agent/test/session-messages.test.ts index 1ad89fd3f..35de44478 100644 --- a/packages/coding-agent/test/session-messages.test.ts +++ b/packages/coding-agent/test/session-messages.test.ts @@ -7,6 +7,7 @@ import { SKILL_PROMPT_MESSAGE_TYPE, wrapSteeringForModel, } from "@oh-my-pi/pi-coding-agent/session/messages"; +import { COLLAB_PROMPT_MESSAGE_TYPE } from "@oh-my-pi/pi-wire"; function expectAttribution(message: Message | undefined, expected: "user" | "agent" | undefined): void { expect(message).toBeDefined(); @@ -382,6 +383,31 @@ describe("wrapSteeringForModel", () => { expect(wrappedText).not.toContain("&"); }); + it("presents user-attributed collab prompts as wrapped user turns", () => { + const message: AgentMessage = { + role: "custom", + customType: COLLAB_PROMPT_MESSAGE_TYPE, + content: "Reply with exactly PONG", + display: true, + details: { from: "guest" }, + attribution: "user", + timestamp: 1, + }; + + const wrapped = wrapSteeringForModel([message]); + const providerMessages = convertToLlm(wrapped); + + expect(wrapped[0]?.role).toBe("user"); + expect(getUserText(wrapped[0])).toContain(""); + expect(getUserText(wrapped[0])).toContain("Reply with exactly PONG"); + expect(providerMessages).toHaveLength(1); + expect(providerMessages[0]?.role).toBe("user"); + expect(message).toMatchObject({ + role: "custom", + details: { from: "guest" }, + }); + }); + it("wraps buried steering messages too so wire bytes stay stable across turns", () => { const buried: AgentMessage = { role: "user",