Suppress dynamic Codex item channel progress

This commit is contained in:
Kelaw - Keshav's Agent
2026-05-06 10:16:30 +05:30
committed by Peter Steinberger
parent 8dcfc9cb69
commit 09d7e7f461
3 changed files with 29 additions and 14 deletions

View File

@@ -802,7 +802,12 @@ describe("CodexAppServerEventProjector", () => {
expect(onAgentEvent).toHaveBeenCalledWith(
expect.objectContaining({
stream: "item",
data: expect.objectContaining({ phase: "start", kind: "tool", name: "message" }),
data: expect.objectContaining({
phase: "start",
kind: "tool",
name: "message",
suppressChannelProgress: true,
}),
}),
);
expect(onAgentEvent).not.toHaveBeenCalledWith(

View File

@@ -663,7 +663,7 @@ export class CodexAppServerEventProjector {
return;
}
const meta = itemMeta(item, this.toolProgressDetailMode());
const suppressChannelProgress = shouldSynthesizeToolProgressForItem(item);
const suppressChannelProgress = shouldSuppressChannelProgressForItem(item);
this.emitAgentEvent({
stream: "item",
data: {
@@ -1163,16 +1163,21 @@ function shouldSynthesizeToolProgressForItem(item: CodexThreadItem): boolean {
case "webSearch":
case "mcpToolCall":
return true;
// Dynamic OpenClaw tool requests are emitted at the item/tool/call request
// boundary in run-attempt.ts. Re-emitting them from item notifications can
// duplicate start/result events when the app-server sends both signals.
case "dynamicToolCall":
return false;
default:
return false;
}
}
function shouldSuppressChannelProgressForItem(item: CodexThreadItem): boolean {
if (shouldSynthesizeToolProgressForItem(item)) {
return true;
}
// Dynamic OpenClaw tool requests are emitted at the item/tool/call request
// boundary in run-attempt.ts. Re-emitting item notifications to channels can
// duplicate start/result progress when the app-server sends both signals.
return item.type === "dynamicToolCall";
}
function itemToolArgs(item: CodexThreadItem): Record<string, unknown> | undefined {
if (item.type === "commandExecution") {
return sanitizeCodexAgentEventRecord({

View File

@@ -2,7 +2,10 @@ import type { Bot } from "grammy";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { resolveAutoTopicLabelConfig as resolveAutoTopicLabelConfigRuntime } from "./auto-topic-label-config.js";
import type { TelegramBotDeps } from "./bot-deps.js";
import { createTestDraftStream } from "./draft-stream.test-helpers.js";
import {
createSequencedTestDraftStream,
createTestDraftStream,
} from "./draft-stream.test-helpers.js";
type DispatchReplyWithBufferedBlockDispatcherArgs = Parameters<
TelegramBotDeps["dispatchReplyWithBufferedBlockDispatcher"]
@@ -259,6 +262,8 @@ describe("dispatchTelegramMessage draft streaming", () => {
});
const createDraftStream = (messageId?: number) => createTestDraftStream({ messageId });
const createSequencedDraftStream = (startMessageId = 1001) =>
createSequencedTestDraftStream(startMessageId);
function setupDraftStreams(params?: { answerMessageId?: number; reasoningMessageId?: number }) {
const answerDraftStream = createDraftStream(params?.answerMessageId);
@@ -1077,13 +1082,13 @@ describe("dispatchTelegramMessage draft streaming", () => {
);
expect(draftStream.forceNewMessage).not.toHaveBeenCalled();
expect(draftStream.materialize).not.toHaveBeenCalled();
expect(editMessageTelegram).toHaveBeenCalledWith(
123,
2001,
"Final after tool",
expect.any(Object),
expect(draftStream.clear).toHaveBeenCalledTimes(1);
expect(deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({
replies: [expect.objectContaining({ text: "Final after tool" })],
}),
);
expect(draftStream.clear).not.toHaveBeenCalled();
expect(editMessageTelegram).not.toHaveBeenCalled();
});
it("falls back to normal send for error payloads and clears the pending stream", async () => {