Skip to content

Commit 291dc8e

Browse files
committed
fix(cron): preserve isolated success on delivery failure
1 parent 02330f3 commit 291dc8e

2 files changed

Lines changed: 95 additions & 5 deletions

File tree

src/cron/isolated-agent/run.message-tool-policy.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,76 @@ describe("runCronIsolatedAgentTurn message tool policy", () => {
12121212
});
12131213
});
12141214

1215+
it("keeps the isolated run successful when post-run delivery fails", async () => {
1216+
mockRunCronFallbackPassthrough();
1217+
resolveCronDeliveryPlanMock.mockReturnValue(makeAnnounceDeliveryPlan());
1218+
resolveCronPayloadOutcomeMock.mockReturnValue({
1219+
summary: "Final cron report",
1220+
outputText: "Final cron report",
1221+
synthesizedText: "Final cron report",
1222+
deliveryPayload: { text: "Final cron report" },
1223+
deliveryPayloads: [{ text: "Final cron report" }],
1224+
deliveryPayloadHasStructuredContent: false,
1225+
hasFatalErrorPayload: false,
1226+
hasFatalStructuredErrorPayload: false,
1227+
embeddedRunError: undefined,
1228+
});
1229+
dispatchCronDeliveryMock.mockImplementationOnce(
1230+
(params: {
1231+
withRunSession: (result: {
1232+
status: "error";
1233+
summary: string;
1234+
outputText: string;
1235+
error: string;
1236+
deliveryAttempted: true;
1237+
}) => unknown;
1238+
}) => ({
1239+
result: params.withRunSession({
1240+
status: "error",
1241+
summary: "Final cron report",
1242+
outputText: "Final cron report",
1243+
error: "Message failed",
1244+
deliveryAttempted: true,
1245+
}),
1246+
delivered: false,
1247+
deliveryAttempted: true,
1248+
summary: "Final cron report",
1249+
outputText: "Final cron report",
1250+
synthesizedText: "Final cron report",
1251+
deliveryPayloads: [{ text: "Final cron report" }],
1252+
}),
1253+
);
1254+
1255+
const result = await runCronIsolatedAgentTurn({
1256+
...makeParams(),
1257+
job: makeAnnounceMessageToolJob({
1258+
id: "delivery-failure-after-success",
1259+
name: "Delivery Failure After Success",
1260+
}),
1261+
});
1262+
1263+
expect(result.status).toBe("ok");
1264+
expect(result.error).toBe("Message failed");
1265+
expect(result.delivered).toBe(false);
1266+
expect(result.deliveryAttempted).toBe(true);
1267+
expectDeliveryFields(result.delivery, {
1268+
intended: { channel: "messagechat", to: "123", source: "explicit" },
1269+
resolved: { ok: true, channel: "messagechat", to: "123", source: "explicit" },
1270+
fallbackUsed: true,
1271+
delivered: false,
1272+
});
1273+
expect(result.diagnostics?.summary).toBe("Message failed");
1274+
expect(result.diagnostics?.entries).toEqual(
1275+
expect.arrayContaining([
1276+
expect.objectContaining({
1277+
source: "delivery",
1278+
severity: "error",
1279+
message: "Message failed",
1280+
}),
1281+
]),
1282+
);
1283+
});
1284+
12151285
it("rewrites generic message provider to resolved channel in delivery trace", async () => {
12161286
mockRunCronFallbackPassthrough();
12171287
resolveCronDeliveryPlanMock.mockReturnValue(makeAnnounceDeliveryPlan());

src/cron/isolated-agent/run.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,12 +1178,17 @@ async function finalizeCronRun(params: {
11781178
delivered?: boolean;
11791179
deliveryAttempted?: boolean;
11801180
delivery?: CronDeliveryTrace;
1181-
}) =>
1182-
prepared.withRunSession({
1181+
deliveryError?: string;
1182+
deliveryDiagnostics?: RunCronAgentTurnResult["diagnostics"];
1183+
}) => {
1184+
const deliveryError = normalizeOptionalString(result?.deliveryError);
1185+
return prepared.withRunSession({
11831186
status: hasFatalErrorPayload ? "error" : "ok",
11841187
...(hasFatalErrorPayload
11851188
? { error: embeddedRunError ?? "cron isolated run returned an error payload" }
1186-
: {}),
1189+
: deliveryError
1190+
? { error: deliveryError }
1191+
: {}),
11871192
summary,
11881193
outputText,
11891194
delivered: result?.delivered,
@@ -1192,14 +1197,16 @@ async function finalizeCronRun(params: {
11921197
diagnostics: hasFatalErrorPayload
11931198
? mergeCronRunDiagnostics(
11941199
agentDiagnostics,
1200+
result?.deliveryDiagnostics,
11951201
createCronRunDiagnosticsFromError(
11961202
"agent-run",
11971203
embeddedRunError ?? "cron isolated run returned an error payload",
11981204
),
11991205
)
1200-
: agentDiagnostics,
1206+
: mergeCronRunDiagnostics(agentDiagnostics, result?.deliveryDiagnostics),
12011207
...telemetry,
12021208
});
1209+
};
12031210
const failPendingPresentationWarningUnlessDelivered = (delivered?: boolean) => {
12041211
if (pendingPresentationWarningError && delivered !== true) {
12051212
hasFatalErrorPayload = true;
@@ -1295,13 +1302,26 @@ async function finalizeCronRun(params: {
12951302
failPendingPresentationWarningUnlessDelivered(
12961303
resultWithDeliveryMeta.delivered ?? deliveryResult.delivered,
12971304
);
1298-
if (!hasFatalErrorPayload || deliveryResult.result.status !== "ok") {
1305+
if (!hasFatalErrorPayload) {
1306+
if (deliveryResult.result.status === "error" && !params.isAborted()) {
1307+
return resolveRunOutcome({
1308+
delivered: resultWithDeliveryMeta.delivered ?? deliveryResult.delivered,
1309+
deliveryAttempted: resultWithDeliveryMeta.deliveryAttempted,
1310+
delivery: deliveryTrace,
1311+
deliveryError: deliveryResult.result.error,
1312+
deliveryDiagnostics: resultWithDeliveryMeta.diagnostics,
1313+
});
1314+
}
1315+
return resultWithDeliveryMeta;
1316+
}
1317+
if (deliveryResult.result.status !== "ok") {
12991318
return resultWithDeliveryMeta;
13001319
}
13011320
return resolveRunOutcome({
13021321
delivered: deliveryResult.result.delivered,
13031322
deliveryAttempted: resultWithDeliveryMeta.deliveryAttempted,
13041323
delivery: deliveryTrace,
1324+
deliveryDiagnostics: resultWithDeliveryMeta.diagnostics,
13051325
});
13061326
}
13071327
summary = deliveryResult.summary;

0 commit comments

Comments
 (0)