fix(omp): reopened chats no longer show extra "Custom message" rows (#6198)

After a daemon restart, an OMP chat replays from its session file. Three
kinds of entries that the live timeline never shows were replayed as
collapsed "Custom message" rows:

- the rule-violation reminder OMP adds after a tool call matches one of
  its rules, a developer message that opens with
  <system-reminder reason="rule_violation" ...>, while only the bare
  <system-reminder> tag was hidden
- the ttsr_injection entry OMP records alongside that reminder
- the fileMention message an @-mentioned file adds, and the other OMP
  message roles the live timeline drops

Closes #6131

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-06 10:02:12 +02:00
committed by GitHub
parent 81a5e5bc61
commit 2dbfd1e0f8
2 changed files with 106 additions and 2 deletions
@@ -252,6 +252,90 @@ describe("OMP history mapper", () => {
]); ]);
}); });
test("replays a rule-violation run with the same rows the live timeline showed", async () => {
const dir = mkdtempSync(join(tmpdir(), "omp-rule-reminder-history-"));
const sessionFile = join(dir, "session.jsonl");
writeFileSync(
sessionFile,
[
{ type: "session", id: "root" },
{
type: "message",
id: "prompt",
parentId: "root",
message: {
role: "user",
content: "Read @notes.md, then write tiny.ts",
},
},
{
type: "message",
id: "mention",
parentId: "prompt",
message: {
role: "fileMention",
files: [
{
path: "notes.md",
content: "[notes.md#468D]\n1:# notes",
lineCount: 1,
},
],
},
},
{
type: "ttsr_injection",
id: "injection",
parentId: "mention",
injectedRules: ["ts-no-tiny-functions"],
},
{
type: "message",
id: "rule-reminder",
parentId: "injection",
message: {
role: "developer",
content: [
{
type: "text",
text: '<system-reminder reason="rule_violation" rule="ts-no-tiny-functions" path="builtin-defaults:ts-no-tiny-functions">\nAvoid tiny functions.\n</system-reminder>',
},
],
},
},
{
type: "message",
id: "answer",
parentId: "rule-reminder",
message: {
role: "assistant",
content: [{ type: "text", text: "FINAL_ANSWER" }],
},
},
]
.map((entry) => JSON.stringify(entry))
.join("\n"),
);
const events: AgentStreamEvent[] = [];
for await (const event of streamOmpHistory({
sessionFile,
provider: "omp",
}))
events.push(event);
expect(events.map((event) => event.item)).toEqual([
{
type: "user_message",
text: "Read @notes.md, then write tiny.ts",
messageId: "prompt",
},
{
type: "assistant_message",
text: "FINAL_ANSWER",
messageId: "omp-history-assistant-1",
},
]);
});
test("coalesces replayed subagent poll calls by target set", async () => { test("coalesces replayed subagent poll calls by target set", async () => {
const events = await collectHistory([ const events = await collectHistory([
{ {
@@ -360,8 +360,7 @@ function mapEntryMessage(entry: OmpSessionEntry): OmpAgentMessage | null {
content.length === 1 && content.length === 1 &&
content[0]?.type === "text" && content[0]?.type === "text" &&
typeof content[0].text === "string" && typeof content[0].text === "string" &&
content[0].text.trimStart().startsWith("<system-reminder>") && isSystemReminder(content[0].text)
content[0].text.trimEnd().endsWith("</system-reminder>")
) { ) {
return null; return null;
} }
@@ -370,6 +369,19 @@ function mapEntryMessage(entry: OmpSessionEntry): OmpAgentMessage | null {
if (["user", "assistant", "toolResult", "custom", "bashExecution"].includes(message.role)) { if (["user", "assistant", "toolResult", "custom", "bashExecution"].includes(message.role)) {
return message as unknown as OmpAgentMessage; return message as unknown as OmpAgentMessage;
} }
// OMP roles the live timeline does not render either, such as the fileMention that carries
// an @-mentioned file's content.
if (
[
"pythonExecution",
"hookMessage",
"branchSummary",
"compactionSummary",
"fileMention",
].includes(message.role)
) {
return null;
}
return visibleFallback(message.role, message); return visibleFallback(message.role, message);
} }
if (entry.type === "custom_message") { if (entry.type === "custom_message") {
@@ -399,6 +411,13 @@ function mapCustomMessageEntry(entry: OmpSessionEntry): OmpAgentMessage | null {
} as OmpAgentMessage; } as OmpAgentMessage;
} }
// OMP's rule-violation reminder carries attributes: <system-reminder reason="rule_violation" ...>
function isSystemReminder(text: string): boolean {
return (
/^<system-reminder[\s>]/.test(text.trimStart()) && text.trimEnd().endsWith("</system-reminder>")
);
}
function isControlEntryType(type: string): boolean { function isControlEntryType(type: string): boolean {
return ( return (
type === "session" || type === "session" ||
@@ -410,6 +429,7 @@ function isControlEntryType(type: string): boolean {
type === "system_prompt" || type === "system_prompt" ||
type === "model_change" || type === "model_change" ||
type === "thinking_level_change" || type === "thinking_level_change" ||
type === "ttsr_injection" ||
type === "tool_execution" || type === "tool_execution" ||
type.startsWith("tool_execution_") type.startsWith("tool_execution_")
); );