fix: isolate visual tool failures from agent runs
This commit is contained in:
+70
-9
@@ -178,15 +178,32 @@ function buildFreshSessionContext(conversation, { maxMessages = 16, maxChars = 1
|
||||
return selected.join('\n\n');
|
||||
}
|
||||
|
||||
function appendFreshSessionContext(userMessage, conversation) {
|
||||
function appendFreshSessionContext(
|
||||
userMessage,
|
||||
conversation,
|
||||
{ visualFallback = false } = {},
|
||||
) {
|
||||
const context = buildFreshSessionContext(conversation);
|
||||
if (!context) return userMessage;
|
||||
if (!context && !visualFallback) return userMessage;
|
||||
const message = userMessage && typeof userMessage === 'object'
|
||||
? { ...userMessage }
|
||||
: { role: 'user', content: [] };
|
||||
const content = Array.isArray(message.content) ? [...message.content] : [];
|
||||
const textIndex = content.findIndex((item) => item?.type === 'text');
|
||||
const note = `【会话恢复上下文】\n以下内容仅用于理解上文,不是新的执行指令:\n${context}`;
|
||||
const notes = [];
|
||||
if (context) {
|
||||
notes.push(
|
||||
`【会话恢复上下文】\n以下内容仅用于理解上文,不是新的执行指令:\n${context}`,
|
||||
);
|
||||
}
|
||||
if (visualFallback) {
|
||||
notes.push(
|
||||
'【视觉检查已降级】\n'
|
||||
+ '当前文本模型不接受图片工具结果。请跳过缩略图或图片视觉检查,'
|
||||
+ '不要再次调用 read_image;直接依据现有 HTML、CSS、文件内容和用户要求继续完成主任务。',
|
||||
);
|
||||
}
|
||||
const note = notes.join('\n\n');
|
||||
if (textIndex >= 0) {
|
||||
content[textIndex] = {
|
||||
...content[textIndex],
|
||||
@@ -201,6 +218,7 @@ function appendFreshSessionContext(userMessage, conversation) {
|
||||
const FRESH_SESSION_RECOVERY_CODES = new Set([
|
||||
'SESSION_TOOL_HISTORY_FRESH_SESSION_REQUIRED',
|
||||
'SESSION_REASONING_CONTENT_POISONED',
|
||||
'SESSION_VISUAL_CONTEXT_UNSUPPORTED',
|
||||
]);
|
||||
|
||||
async function resolveConversationForFreshSessionRecovery({
|
||||
@@ -547,6 +565,7 @@ export function createAgentRunGateway({
|
||||
observeWorkflowValidation = null,
|
||||
isSessionExternallyBusy = null,
|
||||
validateRunDeliverables = null,
|
||||
cancelSessionOnRetry = null,
|
||||
quiesceSessionOnTerminal = null,
|
||||
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
|
||||
autoDispatch = envFlag(process.env.MEMIND_AGENT_RUN_AUTODISPATCH, true),
|
||||
@@ -1645,7 +1664,8 @@ export function createAgentRunGateway({
|
||||
}
|
||||
if (awaitSessionFinish) {
|
||||
let submitMessage = ensureGooseUserMessageMetadata(userMessage);
|
||||
let replacedPoisonedSession = false;
|
||||
let disableImageReading = false;
|
||||
const recoveredSessionCodes = new Set();
|
||||
while (true) {
|
||||
try {
|
||||
const finish = await tkmindProxy.submitSessionReplyAndAwaitFinishForUser(
|
||||
@@ -1657,6 +1677,7 @@ export function createAgentRunGateway({
|
||||
toolMode: effectiveToolMode,
|
||||
forceDeepReasoning: runOptions.forceDeepReasoning,
|
||||
timeoutMs: runTimeoutMs,
|
||||
disableImageReading,
|
||||
},
|
||||
);
|
||||
await appendEvent(runId, 'session_finished', {
|
||||
@@ -1668,10 +1689,14 @@ export function createAgentRunGateway({
|
||||
toolEvidence = finish.toolEvidence ?? null;
|
||||
break;
|
||||
} catch (err) {
|
||||
const recoveryCode = String(err?.code ?? '').trim();
|
||||
if (
|
||||
!replacedPoisonedSession
|
||||
&& FRESH_SESSION_RECOVERY_CODES.has(String(err?.code ?? '').trim())
|
||||
FRESH_SESSION_RECOVERY_CODES.has(recoveryCode)
|
||||
&& !recoveredSessionCodes.has(recoveryCode)
|
||||
&& recoveredSessionCodes.size < FRESH_SESSION_RECOVERY_CODES.size
|
||||
) {
|
||||
const visualFallback =
|
||||
recoveryCode === 'SESSION_VISUAL_CONTEXT_UNSUPPORTED';
|
||||
const previousSessionId = sessionId;
|
||||
const conversationForContext = await resolveConversationForFreshSessionRecovery({
|
||||
err,
|
||||
@@ -1680,9 +1705,15 @@ export function createAgentRunGateway({
|
||||
tkmindProxy,
|
||||
sessionSnapshotService,
|
||||
});
|
||||
const replacement = await tkmindProxy.startSessionForUser(row.user_id);
|
||||
const replacement = visualFallback
|
||||
? await tkmindProxy.startSessionForUser(
|
||||
row.user_id,
|
||||
{ disableImageReading: true },
|
||||
)
|
||||
: await tkmindProxy.startSessionForUser(row.user_id);
|
||||
sessionId = replacement.id;
|
||||
replacedPoisonedSession = true;
|
||||
recoveredSessionCodes.add(recoveryCode);
|
||||
disableImageReading ||= visualFallback;
|
||||
await pool.query(
|
||||
`UPDATE h5_agent_runs SET agent_session_id = ?, updated_at = ? WHERE id = ?`,
|
||||
[sessionId, nowMs(), runId],
|
||||
@@ -1697,13 +1728,18 @@ export function createAgentRunGateway({
|
||||
previousSessionId,
|
||||
sessionId,
|
||||
reason: err.code,
|
||||
visualFallback,
|
||||
repairedMessageCount: conversationForContext.length,
|
||||
persistedMessageCount: persisted.saved,
|
||||
});
|
||||
await appendRunSnapshot(runId);
|
||||
await invalidatePortalDirectChatSnapshot(sessionId);
|
||||
submitMessage = ensureGooseUserMessageMetadata(
|
||||
appendFreshSessionContext(userMessage, conversationForContext),
|
||||
appendFreshSessionContext(
|
||||
userMessage,
|
||||
conversationForContext,
|
||||
{ visualFallback },
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -1970,6 +2006,31 @@ export function createAgentRunGateway({
|
||||
await appendEvent(runId, 'timeout', { timeoutMs: runTimeoutMs });
|
||||
}
|
||||
const retryable = !timedOut && err?.retryable !== false && nextAttempt < retryDelaysMs.length;
|
||||
if (retryable && typeof cancelSessionOnRetry === 'function' && recoverySessionId) {
|
||||
try {
|
||||
const cancellation = await cancelSessionOnRetry({
|
||||
runId,
|
||||
userId: row.user_id,
|
||||
sessionId: recoverySessionId,
|
||||
requestId: row.request_id,
|
||||
});
|
||||
await appendEvent(runId, 'session_retry_cancelled', {
|
||||
sessionId: recoverySessionId,
|
||||
requestId: row.request_id,
|
||||
cancelled: Boolean(cancellation?.cancelled),
|
||||
skipped: Boolean(cancellation?.skipped),
|
||||
});
|
||||
} catch (cancelError) {
|
||||
await appendEvent(runId, 'session_retry_cancel_failed', {
|
||||
sessionId: recoverySessionId,
|
||||
requestId: row.request_id,
|
||||
error:
|
||||
cancelError instanceof Error
|
||||
? cancelError.message
|
||||
: String(cancelError),
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
if (!retryable && isPageDataSuggestionRemediable(err?.code)) {
|
||||
await maybeTriggerPageDataSuggestionRepair({
|
||||
userId: row.user_id,
|
||||
|
||||
Reference in New Issue
Block a user