openclaw/test/conversation-tools.codex.integration.test.ts
Peter Steinberger 8e7443e653
fix(codex): preserve delivery facts and native approval semantics (#151863)
* fix(codex): preserve delivery facts and native approval semantics

Keep failed sends from suppressing replies or recording false delivery. Share the host delivery facts before presentation middleware, preserve per-artifact media and finalized native text, and honor native approval lifetimes and explicit form answers.

Replace duplicate delivery and quota decisions with their existing owners. Document the seven shared SDK helpers and apply the approved seven-export and seven-callable surface budget increase.

* test(codex): align native fixtures with canonical outcomes

* test(codex): align mirrored transcript assertions

* fix(codex): retain core conversation delivery receipts
2026-09-19 16:45:20 -07:00

129 lines
4.4 KiB
TypeScript

import { Value } from "typebox/value";
import { describe, expect, it, vi } from "vitest";
import { createCodexDynamicToolBridge } from "../extensions/codex/test-api.js";
import type {
ConversationSendResult,
ConversationTurnResult,
} from "../packages/gateway-protocol/src/schema/agent.js";
import {
createConversationsSendTool,
createConversationsTurnTool,
} from "../src/agents/tools/conversation-tools.js";
import { callAgentToolGatewayRequest } from "../src/agents/tools/in-process-gateway.js";
const conversationRef = "conv_0123456789abcdef0123456789abcdef";
const destination = { conversationRef, channel: "qa-channel" };
describe("Codex core conversation delivery", () => {
it.each([
{
name: "sent without a platform ID",
toolName: "conversations_send",
receipt: { ...destination, status: "sent" },
delivered: true,
success: true,
},
...(["sent", "queued", "suppressed", "unknown"] as const).map((status) => ({
name: `send ${status} with a platform ID`,
toolName: "conversations_send" as const,
receipt: { ...destination, status, messageId: "outbound-1" },
delivered: status === "sent",
success: true,
})),
{
name: "replied",
toolName: "conversations_turn",
receipt: {
...destination,
status: "replied",
messageId: "outbound-1",
correlationPersisted: true,
reply: { conversationRef, messageId: "inbound-1", text: "Received", timestamp: 1 },
},
delivered: true,
success: true,
},
{
name: "peer reply timeout after send",
toolName: "conversations_turn",
receipt: {
...destination,
status: "timeout",
messageId: "outbound-1",
correlationPersisted: true,
},
delivered: true,
success: false,
},
...(["sent", "queued", "suppressed", "unknown"] as const).map((status) => ({
name: `turn ${status} with a correlation error`,
toolName: "conversations_turn" as const,
receipt: {
...destination,
status,
messageId: "outbound-1",
correlationPersisted: status === "sent" || status === "queued",
error: "No process-local reply waiter remains.",
},
delivered: status === "sent",
success: false,
})),
] satisfies Array<{
name: string;
toolName: "conversations_send" | "conversations_turn";
receipt: ConversationSendResult | ConversationTurnResult;
delivered: boolean;
success: boolean;
}>)("records $name without changing the Gateway result", async (testCase) => {
const deps = { callGateway: callAgentToolGatewayRequest };
const callGateway = vi.spyOn(deps, "callGateway").mockResolvedValue(testCase.receipt);
const createTool =
testCase.toolName === "conversations_send"
? createConversationsSendTool
: createConversationsTurnTool;
const tool = createTool({ agentId: "main" }, deps);
const bridge = createCodexDynamicToolBridge({
tools: [tool],
signal: new AbortController().signal,
});
const onAgentToolResult = vi.fn();
const response = await bridge.handleToolCall(
{
threadId: "thread-1",
turnId: "turn-1",
callId: "conversation-call",
namespace: null,
tool: tool.name,
arguments: { conversationRef, message: "Synthetic external message" },
},
{ onAgentToolResult },
);
expect(callGateway).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
method: testCase.toolName.replace("_", "."),
params: expect.objectContaining({ conversationRef, message: "Synthetic external message" }),
}),
);
const observed = onAgentToolResult.mock.calls[0]?.[0];
expect(observed?.result.details).toEqual(testCase.receipt);
expect(Value.Check(tool.outputSchema!, observed?.result.details)).toBe(true);
expect(response.success).toBe(testCase.success);
expect(response.terminate).toBeUndefined();
expect(bridge.telemetry.didSendViaMessagingTool).toBe(testCase.delivered);
expect(bridge.telemetry.messagingToolSentTexts).toEqual(
testCase.delivered ? ["Synthetic external message"] : [],
);
expect(bridge.telemetry.messagingToolSentTargets).toEqual(
testCase.delivered
? [
expect.objectContaining({
tool: tool.name,
provider: "conversation",
to: conversationRef,
}),
]
: [],
);
});
});