Skip to content

Commit 96a96b7

Browse files
committed
fix(auto-reply): require durable restart recovery
1 parent e362754 commit 96a96b7

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

src/auto-reply/reply/agent-runner-execution.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6031,6 +6031,7 @@ describe("runAgentTurnWithFallback", () => {
60316031
sessionKey: "main",
60326032
getActiveSessionEntry: () => undefined,
60336033
resolvedVerboseLevel: "off",
6034+
isRestartRecoveryArmed: () => true,
60346035
});
60356036

60366037
expect(result.kind).toBe("final");
@@ -6085,6 +6086,7 @@ describe("runAgentTurnWithFallback", () => {
60856086
sessionKey: "main",
60866087
getActiveSessionEntry: () => undefined,
60876088
resolvedVerboseLevel: "off",
6089+
isRestartRecoveryArmed: () => true,
60886090
});
60896091

60906092
expect(result).toEqual({

src/auto-reply/reply/agent-runner-execution.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,7 @@ export async function runAgentTurnWithFallback(params: {
15941594
toolProgressDetail?: "explain" | "raw";
15951595
replyMediaContext?: ReplyMediaContext;
15961596
onCompactionNoticePayload?: (payload: ReplyPayload) => Promise<void> | void;
1597+
isRestartRecoveryArmed?: () => boolean;
15971598
}): Promise<AgentRunLoopResult> {
15981599
const TRANSIENT_HTTP_RETRY_DELAY_MS = 2_500;
15991600
let didLogHeartbeatStrip = false;
@@ -3117,6 +3118,14 @@ export async function runAgentTurnWithFallback(params: {
31173118
// bookkeeping for drain/lane-cleared is still recorded.
31183119
if (isReplyOperationRestartAbort(params.replyOperation)) {
31193120
takePendingLifecycleTerminal()?.emit("end", err);
3121+
if (params.isRestartRecoveryArmed?.() !== true) {
3122+
return {
3123+
kind: "final",
3124+
payload: markAgentRunFailureReplyPayload({
3125+
text: buildRestartLifecycleReplyText(),
3126+
}),
3127+
};
3128+
}
31203129
return {
31213130
kind: "final",
31223131
payload: {

src/auto-reply/reply/agent-runner.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,18 @@ export async function runReplyAgent(params: {
15681568
}
15691569
}
15701570
};
1571+
const isRestartRecoveryArmed = (): boolean => {
1572+
if (!trackedRestartRecoveryDeliveryContext || !sessionKey || !storePath) {
1573+
return false;
1574+
}
1575+
const persisted = loadSessionEntry({
1576+
sessionKey,
1577+
storePath,
1578+
clone: false,
1579+
hydrateSkillPromptRefs: false,
1580+
});
1581+
return persisted?.abortedLastRun === true || activeSessionEntry?.abortedLastRun === true;
1582+
};
15711583
const prePreflightCompactionCount = activeSessionEntry?.compactionCount ?? 0;
15721584
let preflightCompactionApplied;
15731585

@@ -1742,6 +1754,7 @@ export async function runReplyAgent(params: {
17421754
resolvedVerboseLevel,
17431755
toolProgressDetail,
17441756
replyMediaContext,
1757+
isRestartRecoveryArmed,
17451758
}),
17461759
);
17471760

@@ -2596,9 +2609,25 @@ export async function runReplyAgent(params: {
25962609
// terminal: it looks like the owed work was abandoned and invites a
25972610
// duplicate manual retry. `aborted_for_restart` is an "aborted" result, so
25982611
// it falls through to the shared abort branch below.
2599-
if (replyOperation.result?.kind === "aborted") {
2612+
if (
2613+
replyOperation.result?.kind === "aborted" &&
2614+
replyOperation.result.code === "aborted_by_user"
2615+
) {
26002616
return returnWithQueuedFollowupDrain({ text: SILENT_REPLY_TOKEN });
26012617
}
2618+
if (
2619+
replyOperation.result?.kind === "aborted" &&
2620+
replyOperation.result.code === "aborted_for_restart"
2621+
) {
2622+
if (isRestartRecoveryArmed()) {
2623+
return returnWithQueuedFollowupDrain({ text: SILENT_REPLY_TOKEN });
2624+
}
2625+
return returnWithQueuedFollowupDrain(
2626+
markReplyPayloadForSourceSuppressionDelivery({
2627+
text: RESTART_LIFECYCLE_REPLY_TEXT,
2628+
}),
2629+
);
2630+
}
26022631
if (error instanceof GatewayDrainingError) {
26032632
replyOperation.fail("gateway_draining", error);
26042633
return returnWithQueuedFollowupDrain(

0 commit comments

Comments
 (0)