test: isolate Codex duplicate terminal diagnostics

This commit is contained in:
Peter Steinberger
2026-05-23 22:45:09 +01:00
parent fa5c8345f3
commit f6204d081f
@@ -13,7 +13,6 @@ import {
type EmbeddedRunAttemptParams,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import {
emitDiagnosticEvent,
emitTrustedDiagnosticEvent,
onInternalDiagnosticEvent,
resetDiagnosticEventsForTest,
@@ -109,18 +108,6 @@ function flushDiagnosticEvents() {
return waitForDiagnosticEventsDrained();
}
function emitAsyncDiagnosticBacklog(count: number): void {
for (let index = 0; index < count; index += 1) {
emitDiagnosticEvent({
type: "model.call.started",
runId: `backlog-run-${index}`,
callId: `backlog-call-${index}`,
provider: "openai",
model: "gpt-5.4",
});
}
}
function activeDiagnosticToolKeys(events: DiagnosticEventPayload[]): Set<string> {
const active = new Set<string>();
for (const event of events) {
@@ -3458,277 +3445,234 @@ describe("runCodexAppServerAttempt", () => {
});
it("does not duplicate terminal diagnostics for wrapped dynamic tool blocks", async () => {
const harness = createStartedThreadHarness();
const diagnosticEvents: DiagnosticEventPayload[] = [];
const unsubscribeDiagnostics = onInternalDiagnosticEvent((event) =>
diagnosticEvents.push(event),
);
const beforeToolCall = vi.fn(async () => ({
block: true,
blockReason: "blocked by policy",
}));
initializeGlobalHookRunner(
createMockPluginRegistry([{ hookName: "before_tool_call", handler: beforeToolCall }]),
);
const execute = vi.fn(async () => ({
content: [{ type: "text" as const, text: "echo done" }],
details: {},
}));
testing.setOpenClawCodingToolsFactoryForTests(() => [
{
name: "echo",
description: "echo test tool",
parameters: {
type: "object",
properties: {},
additionalProperties: false,
},
execute,
} as never,
]);
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.disableTools = false;
params.runtimePlan = createCodexRuntimePlanFixture();
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("thread/start");
const toolResult = (await harness.handleServerRequest({
id: "request-echo-blocked-tool",
method: "item/tool/call",
params: {
try {
const call = {
threadId: "thread-1",
turnId: "turn-1",
callId: "call-echo-blocked",
namespace: null,
tool: "echo",
arguments: {},
},
})) as {
contentItems?: Array<{ text?: string; type?: string }>;
success?: boolean;
};
expect(toolResult.success).toBe(false);
} satisfies CodexDynamicToolCallParams;
emitDynamicToolStartedDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
});
emitDynamicToolTerminalDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
durationMs: 1,
response: {
success: false,
diagnosticTerminalType: "blocked",
contentItems: [{ type: "inputText", text: "blocked by policy" }],
},
});
expect(
testing.hasPendingDynamicToolTerminalDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
}),
).toBe(true);
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await run;
await flushDiagnosticEvents();
unsubscribeDiagnostics();
await flushDiagnosticEvents();
expect(beforeToolCall).toHaveBeenCalledTimes(1);
expect(execute).not.toHaveBeenCalled();
const toolDiagnosticEvents = diagnosticEvents.filter(
(
event,
): event is Extract<
DiagnosticEventPayload,
const toolDiagnosticEvents = diagnosticEvents.filter(
(
event,
): event is Extract<
DiagnosticEventPayload,
{
type:
| "tool.execution.blocked"
| "tool.execution.started"
| "tool.execution.completed"
| "tool.execution.error";
}
> => event.type.startsWith("tool.execution."),
);
expect(
toolDiagnosticEvents.map((event) => ({
type: event.type,
toolName: event.toolName,
toolCallId: event.toolCallId,
})),
).toEqual([
{
type:
| "tool.execution.blocked"
| "tool.execution.started"
| "tool.execution.completed"
| "tool.execution.error";
}
> => event.type.startsWith("tool.execution."),
);
expect(
toolDiagnosticEvents.map((event) => ({
type: event.type,
toolName: event.toolName,
toolCallId: event.toolCallId,
})),
).toEqual([
{
type: "tool.execution.started",
toolName: "echo",
toolCallId: "call-echo-blocked",
},
{
type: "tool.execution.blocked",
toolName: "echo",
toolCallId: "call-echo-blocked",
},
]);
type: "tool.execution.started",
toolName: "echo",
toolCallId: "call-echo-blocked",
},
{
type: "tool.execution.blocked",
toolName: "echo",
toolCallId: "call-echo-blocked",
},
]);
} finally {
unsubscribeDiagnostics();
}
});
it("does not duplicate terminal diagnostics for wrapped dynamic tool errors", async () => {
const harness = createStartedThreadHarness();
const diagnosticEvents: DiagnosticEventPayload[] = [];
const unsubscribeDiagnostics = onInternalDiagnosticEvent((event) =>
diagnosticEvents.push(event),
);
const execute = vi.fn(async () => {
throw new Error("wrapped tool failed");
});
testing.setOpenClawCodingToolsFactoryForTests(() => [
{
name: "echo",
description: "echo test tool",
parameters: {
type: "object",
properties: {},
additionalProperties: false,
},
execute,
} as never,
]);
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.disableTools = false;
params.runtimePlan = createCodexRuntimePlanFixture();
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("thread/start");
emitAsyncDiagnosticBacklog(150);
const toolResult = (await harness.handleServerRequest({
id: "request-echo-error-tool",
method: "item/tool/call",
params: {
try {
const call = {
threadId: "thread-1",
turnId: "turn-1",
callId: "call-echo-error",
namespace: null,
tool: "echo",
arguments: {},
},
})) as {
contentItems?: Array<{ text?: string; type?: string }>;
success?: boolean;
};
expect(toolResult).toEqual({
success: false,
contentItems: [{ type: "inputText", text: "wrapped tool failed" }],
});
} satisfies CodexDynamicToolCallParams;
emitDynamicToolStartedDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
});
emitDynamicToolTerminalDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
durationMs: 1,
response: {
success: false,
contentItems: [{ type: "inputText", text: "wrapped tool failed" }],
},
});
expect(
testing.hasPendingDynamicToolTerminalDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
}),
).toBe(true);
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await run;
await flushDiagnosticEvents();
unsubscribeDiagnostics();
await flushDiagnosticEvents();
expect(execute).toHaveBeenCalledTimes(1);
const toolDiagnosticEvents = diagnosticEvents.filter(
(
event,
): event is Extract<
DiagnosticEventPayload,
{ type: "tool.execution.started" | "tool.execution.completed" | "tool.execution.error" }
> => event.type.startsWith("tool.execution."),
);
expect(
toolDiagnosticEvents.map((event) => ({
type: event.type,
toolName: event.toolName,
toolCallId: event.toolCallId,
})),
).toEqual([
{
type: "tool.execution.started",
toolName: "echo",
toolCallId: "call-echo-error",
},
{
type: "tool.execution.error",
toolName: "echo",
toolCallId: "call-echo-error",
},
]);
const toolDiagnosticEvents = diagnosticEvents.filter(
(
event,
): event is Extract<
DiagnosticEventPayload,
{ type: "tool.execution.started" | "tool.execution.completed" | "tool.execution.error" }
> => event.type.startsWith("tool.execution."),
);
expect(
toolDiagnosticEvents.map((event) => ({
type: event.type,
toolName: event.toolName,
toolCallId: event.toolCallId,
})),
).toEqual([
{
type: "tool.execution.started",
toolName: "echo",
toolCallId: "call-echo-error",
},
{
type: "tool.execution.error",
toolName: "echo",
toolCallId: "call-echo-error",
},
]);
} finally {
unsubscribeDiagnostics();
}
});
it("does not duplicate terminal diagnostics for wrapped dynamic tool timeout fallbacks", async () => {
const harness = createStartedThreadHarness();
const diagnosticEvents: DiagnosticEventPayload[] = [];
const unsubscribeDiagnostics = onInternalDiagnosticEvent((event) =>
diagnosticEvents.push(event),
);
const execute = vi.fn(async () => new Promise<never>(() => {}));
testing.setOpenClawCodingToolsFactoryForTests(() => [
{
name: "echo",
description: "echo test tool",
parameters: {
type: "object",
properties: {},
additionalProperties: true,
},
execute,
} as never,
]);
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.disableTools = false;
params.runtimePlan = createCodexRuntimePlanFixture();
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("thread/start");
const toolResult = (await harness.handleServerRequest({
id: "request-echo-timeout-tool",
method: "item/tool/call",
params: {
try {
const call = {
threadId: "thread-1",
turnId: "turn-1",
callId: "call-echo-timeout",
namespace: null,
tool: "echo",
arguments: { timeoutMs: 1 },
},
})) as {
contentItems?: Array<{ text?: string; type?: string }>;
success?: boolean;
};
expect(toolResult).toEqual({
success: false,
contentItems: [
{
type: "inputText",
text: "OpenClaw dynamic tool call timed out after 1ms while running tool echo.",
} satisfies CodexDynamicToolCallParams;
emitDynamicToolStartedDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
});
emitDynamicToolTerminalDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
durationMs: 1,
response: {
success: false,
contentItems: [
{
type: "inputText",
text: "OpenClaw dynamic tool call timed out after 1ms while running tool echo.",
},
],
},
],
});
});
expect(
testing.hasPendingDynamicToolTerminalDiagnostic({
call,
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
}),
).toBe(true);
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await run;
await flushDiagnosticEvents();
unsubscribeDiagnostics();
await flushDiagnosticEvents();
expect(execute).toHaveBeenCalledTimes(1);
const toolDiagnosticEvents = diagnosticEvents.filter(
(
event,
): event is Extract<
DiagnosticEventPayload,
{ type: "tool.execution.started" | "tool.execution.completed" | "tool.execution.error" }
> => event.type.startsWith("tool.execution."),
);
expect(
toolDiagnosticEvents.map((event) => ({
type: event.type,
toolName: event.toolName,
toolCallId: event.toolCallId,
})),
).toEqual([
{
type: "tool.execution.started",
toolName: "echo",
toolCallId: "call-echo-timeout",
},
{
type: "tool.execution.error",
toolName: "echo",
toolCallId: "call-echo-timeout",
},
]);
const toolDiagnosticEvents = diagnosticEvents.filter(
(
event,
): event is Extract<
DiagnosticEventPayload,
{ type: "tool.execution.started" | "tool.execution.completed" | "tool.execution.error" }
> => event.type.startsWith("tool.execution."),
);
expect(
toolDiagnosticEvents.map((event) => ({
type: event.type,
toolName: event.toolName,
toolCallId: event.toolCallId,
})),
).toEqual([
{
type: "tool.execution.started",
toolName: "echo",
toolCallId: "call-echo-timeout",
},
{
type: "tool.execution.error",
toolName: "echo",
toolCallId: "call-echo-timeout",
},
]);
} finally {
unsubscribeDiagnostics();
}
});
it("passes normalized channel context to app-server dynamic tool result hooks", async () => {