fix(omp): honor hidden custom messages (#2280)

This commit is contained in:
Byeonghoon Yoo
2026-07-24 01:41:36 +09:00
committed by GitHub
parent 8e063f0dfc
commit 5e47cff58e
7 changed files with 116 additions and 13 deletions

View File

@@ -260,6 +260,23 @@ describe("OMP agent client and session", () => {
expect(omp.completedTurnCount()).toBe(1);
});
test("omits live custom messages when display is false", async () => {
const omp = new OmpHarness();
await omp.start();
await expect(
omp.runPromptAfterExtensionNotice("hello OMP", "model turn completed", false),
).resolves.toMatchObject({ finalText: expect.stringContaining("model turn completed") });
expect(omp.timeline()).toEqual([
{ type: "user_message", text: "hello OMP", messageId: "user-1" },
{
type: "assistant_message",
text: "model turn completed",
messageId: "omp-assistant-1",
},
]);
});
test("does not complete a queued model turn from OMP's local-only hint", async () => {
const omp = new OmpHarness();
await omp.start();

View File

@@ -66,6 +66,7 @@ import {
} from "./provider-config.js";
export { formatOmpVersionSupport, resolveOmpDiagnosticPaths } from "./provider-config.js";
import { OmpSubagentCardTracker, type OmpSubagentCardScheduler } from "./subagent-card-tracker.js";
import { shouldDisplayOmpCustomMessage } from "./custom-message.js";
import { getUserMessageText } from "./message-history.js";
import { materializeProviderImage } from "../provider-image-output.js";
import { OmpCliRuntime } from "./cli-runtime.js";
@@ -2064,15 +2065,17 @@ export class OmpAgentSession implements AgentSession {
return;
}
if (event.message.role === "custom") {
const text = getUserMessageText(event.message.content);
if (text) {
const advisorItem = mapOmpAdvisorMessageToToolCall(event.message, text);
this.emit({
type: "timeline",
provider: this.provider,
turnId,
item: advisorItem ?? { type: "assistant_message", text },
});
if (shouldDisplayOmpCustomMessage(event.message)) {
const text = getUserMessageText(event.message.content);
if (text) {
const advisorItem = mapOmpAdvisorMessageToToolCall(event.message, text);
this.emit({
type: "timeline",
provider: this.provider,
turnId,
item: advisorItem ?? { type: "assistant_message", text },
});
}
}
if (!this.activeTurnHasUserMessage) {
this.completeTurn(turnId, []);

View File

@@ -0,0 +1,7 @@
import type { OmpAgentMessage } from "./rpc-types.js";
type OmpCustomMessage = Extract<OmpAgentMessage, { role: "custom" }>;
export function shouldDisplayOmpCustomMessage(message: OmpCustomMessage): boolean {
return Reflect.get(message, "display") !== false;
}

View File

@@ -209,6 +209,54 @@ describe("OMP history mapper", () => {
]);
});
test("omits replayed custom messages only when display is false", async () => {
await expect(
collectHistory(
[
{ role: "user", content: "first prompt" },
{ role: "custom", content: "hidden reminder", display: false },
{ role: "custom", content: "visible explicit custom", display: true },
{ role: "custom", content: "visible legacy custom" },
{
role: "assistant",
content: [{ type: "text", text: "assistant reply" }],
responseId: "assistant-history",
},
],
[{ id: "entry-user-1", text: "first prompt" }],
),
).resolves.toEqual([
{
type: "timeline",
provider: "omp",
item: {
type: "user_message",
text: "first prompt",
messageId: "entry-user-1",
},
},
{
type: "timeline",
provider: "omp",
item: { type: "assistant_message", text: "visible explicit custom" },
},
{
type: "timeline",
provider: "omp",
item: { type: "assistant_message", text: "visible legacy custom" },
},
{
type: "timeline",
provider: "omp",
item: {
type: "assistant_message",
text: "assistant reply",
messageId: "assistant-history",
},
},
]);
});
test("suppresses replayed raw todo tool calls through the OMP detail hook", async () => {
await expect(
collectHistory([

View File

@@ -1,5 +1,6 @@
import type { AgentStreamEvent, AgentTimelineItem, ToolCallDetail } from "../../agent-sdk-types.js";
import type { OmpAgentMessage, OmpImageContent, OmpTextContent } from "./rpc-types.js";
import { shouldDisplayOmpCustomMessage } from "./custom-message.js";
import {
extractTextFromToolResult,
mapToolDetail,
@@ -117,6 +118,9 @@ export class OmpHistoryMapper {
private mapCustomMessage(
message: Extract<OmpAgentMessage, { role: "custom" }>,
): AgentStreamEvent[] {
if (!shouldDisplayOmpCustomMessage(message)) {
return [];
}
const text = getUserMessageText(message.content);
const mappedEvent = text ? this.hooks.mapCustomMessage?.(message, text, this.provider) : null;
if (mappedEvent) {

View File

@@ -201,16 +201,25 @@ export class OmpHarness {
return { completion };
}
async runPromptAfterExtensionNotice(input: string, output: string): Promise<unknown> {
async runPromptAfterExtensionNotice(
input: string,
output: string,
display?: boolean,
): Promise<unknown> {
const session = this.requireSession();
const promptStarted = this.omp.latestSession().nextPrompt();
const run = session.run(input);
await promptStarted;
const runtime = this.omp.latestSession();
const message = {
role: "custom" as const,
content: "extension inventory changed",
...(display === undefined ? {} : { display }),
};
runtime.beginTurn();
runtime.acceptPrompt(input, "user-1");
runtime.acceptCustomMessage("extension inventory changed");
runtime.finishTurn({ role: "custom", content: "extension inventory changed" });
runtime.emit({ type: "message_end", message });
runtime.finishTurn(message);
runtime.beginTurn();
runtime.streamAssistantText(output);
runtime.finishTurn();

View File

@@ -1,8 +1,9 @@
import { describe, expect, test } from "vitest";
import { parseToolArgs, parseToolResult } from "./tool-call-detail.js";
import { OmpAvailableCommandsUpdateEventSchema } from "./rpc-types.js";
import { OmpAgentMessageSchema, OmpAvailableCommandsUpdateEventSchema } from "./rpc-types.js";
import { mapOmpToolDetail } from "./tool-call-mapper.js";
import { shouldDisplayOmpCustomMessage } from "./custom-message.js";
describe("OMP 17 RPC compatibility", () => {
test("parses source-attributed command updates", () => {
@@ -16,6 +17,20 @@ describe("OMP 17 RPC compatibility", () => {
]);
});
test("keeps non-false custom display metadata backward compatible", () => {
const message = OmpAgentMessageSchema.parse({
role: "custom",
content: "visible custom message",
display: null,
});
expect(message).toMatchObject({ display: null });
if (message.role !== "custom") {
throw new Error("Expected a custom OMP message");
}
expect(shouldDisplayOmpCustomMessage(message)).toBe(true);
});
test("maps subscribed custom tool events without assuming built-in names", () => {
const event = {
type: "tool_execution_start",