Skip to content

Commit cefa677

Browse files
committed
fix(qa): keep fallback delivery on latest targets
1 parent 31ecbbd commit cefa677

4 files changed

Lines changed: 91 additions & 11 deletions

File tree

extensions/qa-lab/src/providers/mock-openai/server.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3176,6 +3176,44 @@ describe("qa mock openai server", () => {
31763176
expect(payload.output?.[0]?.content?.[0]?.text).toBe("fresh-marker-ok");
31773177
});
31783178

3179+
it("keeps stale consecutive image prompts from overriding later marker turns", async () => {
3180+
const server = await startMockServer();
3181+
3182+
const response = await fetch(`${server.baseUrl}/v1/responses`, {
3183+
method: "POST",
3184+
headers: { "content-type": "application/json" },
3185+
body: JSON.stringify({
3186+
stream: false,
3187+
model: "mock-openai/gpt-5.5",
3188+
input: [
3189+
{
3190+
role: "user",
3191+
content: [
3192+
{
3193+
type: "input_text",
3194+
text: "Image understanding check: describe the top and bottom colors.",
3195+
},
3196+
{
3197+
type: "input_image",
3198+
source: {
3199+
type: "base64",
3200+
mime_type: "image/png",
3201+
data: QA_IMAGE_PNG_BASE64,
3202+
},
3203+
},
3204+
],
3205+
},
3206+
makeUserInput("Marker exact marker: `fresh-consecutive-marker-ok`"),
3207+
],
3208+
}),
3209+
});
3210+
expect(response.status).toBe(200);
3211+
const payload = (await response.json()) as {
3212+
output?: Array<{ content?: Array<{ text?: string }> }>;
3213+
};
3214+
expect(payload.output?.[0]?.content?.[0]?.text).toBe("fresh-consecutive-marker-ok");
3215+
});
3216+
31793217
it("handles deeply nested image input shapes without recursive traversal failure", async () => {
31803218
const server = await startQaMockOpenAiServer({
31813219
host: "127.0.0.1",

extensions/qa-lab/src/providers/mock-openai/server.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -561,16 +561,12 @@ function extractLatestImageUserTurn(input: ResponsesInputItem[]) {
561561
return { text: "", imageInputCount: 0 };
562562
}
563563

564-
let startIndex = latestUserIndex;
565-
while (
566-
startIndex > 0 &&
567-
input[startIndex - 1]?.role === "user" &&
568-
Array.isArray(input[startIndex - 1]?.content)
569-
) {
570-
startIndex -= 1;
564+
const latestUserItem = input[latestUserIndex];
565+
if (!latestUserItem) {
566+
return { text: "", imageInputCount: 0 };
571567
}
572568

573-
const imageTurnItems = input.slice(startIndex, latestUserIndex + 1);
569+
const imageTurnItems = [latestUserItem];
574570
const imageInputCount = countImageInputs(imageTurnItems.map((item) => item.content));
575571
if (imageInputCount === 0) {
576572
return { text: "", imageInputCount: 0 };

src/agents/subagent-announce-delivery.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,51 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
10911091
);
10921092
});
10931093

1094+
it("does not raw-send channel completions just because the requester key is direct", async () => {
1095+
const callGateway = createGatewayMock({
1096+
result: {
1097+
payloads: [],
1098+
},
1099+
});
1100+
const sendMessage = createSendMessageMock();
1101+
1102+
const result = await deliverSlackChannelAnnouncement({
1103+
callGateway,
1104+
sendMessage,
1105+
sessionId: "requester-session-channel",
1106+
isActive: false,
1107+
expectsCompletionMessage: true,
1108+
directIdempotencyKey: "announce-channel-direct-key-empty",
1109+
requesterSessionKey: "agent:main:discord:dm:U123",
1110+
internalEvents: [
1111+
{
1112+
type: "task_completion",
1113+
source: "subagent",
1114+
childSessionKey: "agent:worker:subagent:child",
1115+
childSessionId: "child-session-id",
1116+
announceType: "subagent task",
1117+
taskLabel: "channel completion smoke",
1118+
status: "ok",
1119+
statusLabel: "completed successfully",
1120+
result: "child completion output",
1121+
replyInstruction: "Summarize the result.",
1122+
},
1123+
],
1124+
});
1125+
1126+
expectRecordFields(result, {
1127+
delivered: true,
1128+
path: "direct",
1129+
});
1130+
expectGatewayAgentParams(callGateway, {
1131+
deliver: true,
1132+
channel: "slack",
1133+
accountId: "acct-1",
1134+
to: "channel:C123",
1135+
});
1136+
expect(sendMessage).not.toHaveBeenCalled();
1137+
});
1138+
10941139
it("directly delivers direct-message subagent text when the announce agent returns incomplete", async () => {
10951140
const callGateway = vi.fn(async () => {
10961141
throw new Error(

src/agents/subagent-announce-delivery.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -755,10 +755,11 @@ function isDirectMessageDeliveryTarget(
755755
if (target.threadId) {
756756
return false;
757757
}
758-
if (deriveSessionChatTypeFromKey(requesterSessionKey) === "direct") {
759-
return true;
758+
const targetChatType = inferDeliveryTargetChatType(target);
759+
if (targetChatType) {
760+
return targetChatType === "direct";
760761
}
761-
return inferDeliveryTargetChatType(target) === "direct";
762+
return deriveSessionChatTypeFromKey(requesterSessionKey) === "direct";
762763
}
763764

764765
function resolveTextCompletionDirectFallback(events: readonly AgentInternalEvent[] | undefined) {

0 commit comments

Comments
 (0)