feat: use SDK recoverPendingApprovals for approval conflict recovery (#585)
Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
@@ -1557,33 +1557,33 @@ export class LettaBot implements AgentSession {
|
||||
|
||||
// Approval conflict recovery
|
||||
if (retryDecision.isApprovalConflict && !retried && this.store.agentId) {
|
||||
if (retryConvId) {
|
||||
log.info('Approval conflict detected -- attempting targeted recovery...');
|
||||
this.sessionManager.invalidateSession(retryConvKey);
|
||||
session = null;
|
||||
clearInterval(typingInterval);
|
||||
const convResult = await recoverOrphanedConversationApproval(
|
||||
this.store.agentId, retryConvId, true,
|
||||
);
|
||||
if (convResult.recovered) {
|
||||
log.info(`Approval recovery succeeded (${convResult.details}), retrying message...`);
|
||||
log.info('Approval conflict detected -- attempting SDK recovery...');
|
||||
clearInterval(typingInterval);
|
||||
|
||||
// Try SDK-level recovery first (through CLI control protocol)
|
||||
if (session) {
|
||||
const sdkResult = await session.recoverPendingApprovals({ timeoutMs: 10_000 });
|
||||
if (sdkResult.recovered) {
|
||||
log.info('SDK approval recovery succeeded, retrying message...');
|
||||
this.sessionManager.invalidateSession(retryConvKey);
|
||||
session = null;
|
||||
return this.processMessage(msg, adapter, true);
|
||||
}
|
||||
log.warn(`Approval recovery failed: ${convResult.details}`);
|
||||
return this.processMessage(msg, adapter, true);
|
||||
} else {
|
||||
log.info('Approval conflict in default conversation -- attempting agent-level recovery...');
|
||||
this.sessionManager.invalidateSession(retryConvKey);
|
||||
session = null;
|
||||
clearInterval(typingInterval);
|
||||
const agentResult = await recoverPendingApprovalsForAgent(this.store.agentId);
|
||||
if (agentResult.recovered) {
|
||||
log.info(`Agent-level recovery succeeded (${agentResult.details}), retrying message...`);
|
||||
return this.processMessage(msg, adapter, true);
|
||||
}
|
||||
log.warn(`Agent-level recovery failed: ${agentResult.details}`);
|
||||
return this.processMessage(msg, adapter, true);
|
||||
log.warn(`SDK recovery did not resolve (${sdkResult.detail ?? 'unknown'}), trying API-level recovery...`);
|
||||
}
|
||||
|
||||
// Fall back to API-level recovery
|
||||
this.sessionManager.invalidateSession(retryConvKey);
|
||||
session = null;
|
||||
const result = (retryConvId && isRecoverableConversationId(retryConvId))
|
||||
? await recoverOrphanedConversationApproval(this.store.agentId, retryConvId, true)
|
||||
: await recoverPendingApprovalsForAgent(this.store.agentId);
|
||||
if (result.recovered) {
|
||||
log.info(`API-level recovery succeeded (${result.details}), retrying message...`);
|
||||
} else {
|
||||
log.warn(`API-level recovery failed: ${result.details}`);
|
||||
}
|
||||
return this.processMessage(msg, adapter, true);
|
||||
}
|
||||
|
||||
// Empty/error result retry
|
||||
@@ -1830,7 +1830,7 @@ export class LettaBot implements AgentSession {
|
||||
let retried = false;
|
||||
|
||||
while (true) {
|
||||
const { stream } = await this.sessionManager.runSession(text, { convKey, retried });
|
||||
const { session, stream } = await this.sessionManager.runSession(text, { convKey, retried });
|
||||
|
||||
try {
|
||||
let response = '';
|
||||
@@ -1885,15 +1885,21 @@ export class LettaBot implements AgentSession {
|
||||
|| ((lastErrorDetail?.message?.toLowerCase().includes('conflict') || false)
|
||||
&& (lastErrorDetail?.message?.toLowerCase().includes('waiting for approval') || false));
|
||||
if (isApprovalIssue && !retried) {
|
||||
if (this.store.agentId) {
|
||||
const recovery = await recoverPendingApprovalsForAgent(this.store.agentId);
|
||||
if (recovery.recovered) {
|
||||
log.info(`sendToAgent: agent-level approval recovery succeeded (${recovery.details})`);
|
||||
} else {
|
||||
log.warn(`sendToAgent: agent-level approval recovery did not resolve approvals (${recovery.details})`);
|
||||
log.info('sendToAgent: approval conflict detected -- attempting SDK recovery...');
|
||||
const sdkResult = await session.recoverPendingApprovals({ timeoutMs: 10_000 });
|
||||
if (sdkResult.recovered) {
|
||||
log.info('sendToAgent: SDK approval recovery succeeded');
|
||||
} else {
|
||||
log.warn(`sendToAgent: SDK recovery did not resolve (${sdkResult.detail ?? 'unknown'}), trying API-level recovery...`);
|
||||
if (this.store.agentId) {
|
||||
const recovery = await recoverPendingApprovalsForAgent(this.store.agentId);
|
||||
if (recovery.recovered) {
|
||||
log.info(`sendToAgent: API-level recovery succeeded (${recovery.details})`);
|
||||
} else {
|
||||
log.warn(`sendToAgent: API-level recovery failed (${recovery.details})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info('sendToAgent: approval issue detected -- retrying once with fresh session...');
|
||||
this.sessionManager.invalidateSession(convKey);
|
||||
retried = true;
|
||||
approvalRetryPending = true;
|
||||
|
||||
@@ -110,6 +110,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -147,6 +148,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -176,6 +178,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -211,6 +214,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -241,6 +245,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-stale',
|
||||
};
|
||||
@@ -255,6 +260,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-recreated',
|
||||
conversationId: 'conv-recreated',
|
||||
};
|
||||
@@ -293,6 +299,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-keep',
|
||||
};
|
||||
@@ -331,6 +338,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test-1',
|
||||
};
|
||||
@@ -346,6 +354,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test-2',
|
||||
};
|
||||
@@ -378,6 +387,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-old',
|
||||
};
|
||||
@@ -392,6 +402,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-new',
|
||||
};
|
||||
@@ -453,6 +464,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'default',
|
||||
};
|
||||
@@ -468,6 +480,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'default',
|
||||
};
|
||||
@@ -506,6 +519,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-stuck',
|
||||
};
|
||||
@@ -521,6 +535,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-fresh',
|
||||
};
|
||||
@@ -567,6 +582,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -596,6 +612,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -625,6 +642,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -654,6 +672,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -692,6 +711,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -754,6 +774,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: `${sessionName}-conversation`,
|
||||
} as never;
|
||||
@@ -872,6 +893,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-new',
|
||||
};
|
||||
@@ -879,9 +901,11 @@ describe('SDK session contract', () => {
|
||||
|
||||
const activeSession = {
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
};
|
||||
const idleSession = {
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
};
|
||||
|
||||
const bot = new LettaBot({
|
||||
@@ -920,6 +944,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -946,6 +971,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-123',
|
||||
};
|
||||
@@ -978,6 +1004,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conversation-contract-test',
|
||||
};
|
||||
@@ -1012,6 +1039,7 @@ describe('SDK session contract', () => {
|
||||
})();
|
||||
}),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-approval',
|
||||
};
|
||||
@@ -1042,7 +1070,7 @@ describe('SDK session contract', () => {
|
||||
|
||||
let runCall = 0;
|
||||
(bot as any).sessionManager.runSession = vi.fn(async () => ({
|
||||
session: { abort: vi.fn(async () => undefined) },
|
||||
session: { abort: vi.fn(async () => undefined), recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })) },
|
||||
stream: async function* () {
|
||||
if (runCall++ === 0) {
|
||||
yield { type: 'result', success: false, error: 'error', conversationId: 'conv-approval' };
|
||||
@@ -1108,7 +1136,7 @@ describe('SDK session contract', () => {
|
||||
|
||||
let runCall = 0;
|
||||
(bot as any).sessionManager.runSession = vi.fn(async () => ({
|
||||
session: { abort: vi.fn(async () => undefined) },
|
||||
session: { abort: vi.fn(async () => undefined), recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })) },
|
||||
stream: async function* () {
|
||||
if (runCall++ === 0) {
|
||||
// Pre-foreground error is filtered by the pipeline -- it never
|
||||
@@ -1183,7 +1211,7 @@ describe('SDK session contract', () => {
|
||||
|
||||
let runCall = 0;
|
||||
(bot as any).sessionManager.runSession = vi.fn(async () => ({
|
||||
session: { abort: vi.fn(async () => undefined) },
|
||||
session: { abort: vi.fn(async () => undefined), recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })) },
|
||||
stream: async function* () {
|
||||
if (runCall++ === 0) {
|
||||
yield { type: 'result', success: false, error: 'error', conversationId: 'default' };
|
||||
@@ -1255,6 +1283,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-stuck',
|
||||
};
|
||||
@@ -1270,6 +1299,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-contract-test',
|
||||
conversationId: 'conv-fresh',
|
||||
};
|
||||
@@ -1316,6 +1346,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-new-tagged',
|
||||
conversationId: 'conversation-new-tagged',
|
||||
};
|
||||
@@ -1371,6 +1402,7 @@ describe('SDK session contract', () => {
|
||||
})();
|
||||
}),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-runid-test',
|
||||
conversationId: 'conversation-runid-test',
|
||||
};
|
||||
@@ -1409,6 +1441,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-reuse-false',
|
||||
conversationId: 'conversation-reuse-false',
|
||||
};
|
||||
@@ -1442,6 +1475,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-background-directives',
|
||||
conversationId: 'conversation-background-directives',
|
||||
};
|
||||
@@ -1528,6 +1562,7 @@ describe('SDK session contract', () => {
|
||||
})()
|
||||
),
|
||||
close: vi.fn(() => undefined),
|
||||
recoverPendingApprovals: vi.fn(async () => ({ recovered: false, unsupported: true, detail: 'mock' })),
|
||||
agentId: 'agent-queue-leak-test',
|
||||
conversationId: 'conversation-queue-leak-test',
|
||||
};
|
||||
|
||||
@@ -373,34 +373,27 @@ export class SessionManager {
|
||||
);
|
||||
if (bootstrap.hasPendingApproval) {
|
||||
const convId = bootstrap.conversationId || session.conversationId;
|
||||
if (!isRecoverableConversationId(convId)) {
|
||||
log.warn(
|
||||
`Pending approval detected at session startup (key=${key}, conv=${convId}) ` +
|
||||
'using agent-level recovery fallback.'
|
||||
);
|
||||
session.close();
|
||||
const result = await recoverPendingApprovalsForAgent(this.store.agentId);
|
||||
if (result.recovered) {
|
||||
log.info(`Proactive agent-level recovery succeeded: ${result.details}`);
|
||||
} else {
|
||||
log.warn(`Proactive agent-level recovery did not resolve approvals: ${result.details}`);
|
||||
}
|
||||
return this._createSessionForKey(key, true, generation);
|
||||
} else {
|
||||
log.warn(`Pending approval detected at session startup (key=${key}, conv=${convId}), recovering...`);
|
||||
session.close();
|
||||
const result = await recoverOrphanedConversationApproval(
|
||||
this.store.agentId,
|
||||
convId,
|
||||
true, /* deepScan */
|
||||
);
|
||||
if (result.recovered) {
|
||||
log.info(`Proactive approval recovery succeeded: ${result.details}`);
|
||||
} else {
|
||||
log.warn(`Proactive approval recovery did not find resolvable approvals: ${result.details}`);
|
||||
}
|
||||
log.warn(`Pending approval detected at session startup (key=${key}, conv=${convId}), recovering...`);
|
||||
|
||||
// Try SDK-level recovery first (goes through CLI control protocol)
|
||||
const sdkResult = await session.recoverPendingApprovals({ timeoutMs: 10_000 });
|
||||
if (sdkResult.recovered) {
|
||||
log.info('Proactive SDK approval recovery succeeded');
|
||||
return this._createSessionForKey(key, true, generation);
|
||||
}
|
||||
|
||||
// SDK recovery failed -- fall back to API-level recovery
|
||||
log.warn(`SDK recovery did not resolve (${sdkResult.detail ?? 'unknown'}), trying API-level recovery...`);
|
||||
session.close();
|
||||
const result = isRecoverableConversationId(convId)
|
||||
? await recoverOrphanedConversationApproval(this.store.agentId, convId, true)
|
||||
: await recoverPendingApprovalsForAgent(this.store.agentId);
|
||||
if (result.recovered) {
|
||||
log.info(`Proactive API-level recovery succeeded: ${result.details}`);
|
||||
} else {
|
||||
log.warn(`Proactive approval recovery did not find resolvable approvals: ${result.details}`);
|
||||
}
|
||||
return this._createSessionForKey(key, true, generation);
|
||||
}
|
||||
} catch (err) {
|
||||
// bootstrapState failure is non-fatal -- the reactive 409 handler in
|
||||
@@ -578,18 +571,25 @@ export class SessionManager {
|
||||
try {
|
||||
await this.withSessionTimeout(session.send(message), `Session send (key=${convKey})`);
|
||||
} catch (error) {
|
||||
// 409 CONFLICT from orphaned approval
|
||||
// 409 CONFLICT from orphaned approval -- use SDK recovery first, fall back to API
|
||||
if (!retried && isApprovalConflictError(error) && this.store.agentId) {
|
||||
log.info('CONFLICT detected - attempting orphaned approval recovery...');
|
||||
log.info('CONFLICT detected - attempting SDK approval recovery...');
|
||||
const sdkResult = await session.recoverPendingApprovals({ timeoutMs: 10_000 });
|
||||
if (sdkResult.recovered) {
|
||||
log.info('SDK approval recovery succeeded, retrying...');
|
||||
return this.runSession(message, { retried: true, canUseTool, convKey });
|
||||
}
|
||||
// SDK recovery failed or unsupported -- fall back to API-level recovery
|
||||
log.warn(`SDK recovery did not resolve (${sdkResult.detail ?? 'unknown'}), trying API-level recovery...`);
|
||||
this.invalidateSession(convKey);
|
||||
const result = isRecoverableConversationId(convId)
|
||||
? await recoverOrphanedConversationApproval(this.store.agentId, convId)
|
||||
: await recoverPendingApprovalsForAgent(this.store.agentId);
|
||||
if (result.recovered) {
|
||||
log.info(`Recovery succeeded (${result.details}), retrying...`);
|
||||
log.info(`API-level recovery succeeded (${result.details}), retrying...`);
|
||||
return this.runSession(message, { retried: true, canUseTool, convKey });
|
||||
}
|
||||
log.error(`Orphaned approval recovery failed: ${result.details}`);
|
||||
log.error(`Approval recovery failed: ${result.details}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user