mirror of https://github.com/openclaw/openclaw.git
fix: accumulate compaction counts across retries
This commit is contained in:
parent
37313844c1
commit
df8f292039
|
|
@ -319,59 +319,79 @@ export async function runAgentTurnWithFallback(params: {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return (async () => {
|
return (async () => {
|
||||||
const result = await runEmbeddedPiAgent({
|
let attemptCompactionCount = 0;
|
||||||
...embeddedContext,
|
try {
|
||||||
trigger: params.isHeartbeat ? "heartbeat" : "user",
|
const result = await runEmbeddedPiAgent({
|
||||||
groupId: resolveGroupSessionKey(params.sessionCtx)?.id,
|
...embeddedContext,
|
||||||
groupChannel:
|
trigger: params.isHeartbeat ? "heartbeat" : "user",
|
||||||
params.sessionCtx.GroupChannel?.trim() ?? params.sessionCtx.GroupSubject?.trim(),
|
groupId: resolveGroupSessionKey(params.sessionCtx)?.id,
|
||||||
groupSpace: params.sessionCtx.GroupSpace?.trim() ?? undefined,
|
groupChannel:
|
||||||
...senderContext,
|
params.sessionCtx.GroupChannel?.trim() ?? params.sessionCtx.GroupSubject?.trim(),
|
||||||
...runBaseParams,
|
groupSpace: params.sessionCtx.GroupSpace?.trim() ?? undefined,
|
||||||
prompt: params.commandBody,
|
...senderContext,
|
||||||
extraSystemPrompt: params.followupRun.run.extraSystemPrompt,
|
...runBaseParams,
|
||||||
toolResultFormat: (() => {
|
prompt: params.commandBody,
|
||||||
const channel = resolveMessageChannel(
|
extraSystemPrompt: params.followupRun.run.extraSystemPrompt,
|
||||||
params.sessionCtx.Surface,
|
toolResultFormat: (() => {
|
||||||
params.sessionCtx.Provider,
|
const channel = resolveMessageChannel(
|
||||||
);
|
params.sessionCtx.Surface,
|
||||||
if (!channel) {
|
params.sessionCtx.Provider,
|
||||||
return "markdown";
|
);
|
||||||
}
|
if (!channel) {
|
||||||
return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
|
return "markdown";
|
||||||
})(),
|
}
|
||||||
suppressToolErrorWarnings: params.opts?.suppressToolErrorWarnings,
|
return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
|
||||||
bootstrapContextMode: params.opts?.bootstrapContextMode,
|
})(),
|
||||||
bootstrapContextRunKind: params.opts?.isHeartbeat ? "heartbeat" : "default",
|
suppressToolErrorWarnings: params.opts?.suppressToolErrorWarnings,
|
||||||
images: params.opts?.images,
|
bootstrapContextMode: params.opts?.bootstrapContextMode,
|
||||||
abortSignal: params.opts?.abortSignal,
|
bootstrapContextRunKind: params.opts?.isHeartbeat ? "heartbeat" : "default",
|
||||||
blockReplyBreak: params.resolvedBlockStreamingBreak,
|
images: params.opts?.images,
|
||||||
blockReplyChunking: params.blockReplyChunking,
|
abortSignal: params.opts?.abortSignal,
|
||||||
onPartialReply: async (payload) => {
|
blockReplyBreak: params.resolvedBlockStreamingBreak,
|
||||||
const textForTyping = await handlePartialForTyping(payload);
|
blockReplyChunking: params.blockReplyChunking,
|
||||||
if (!params.opts?.onPartialReply || textForTyping === undefined) {
|
onPartialReply: async (payload) => {
|
||||||
return;
|
const textForTyping = await handlePartialForTyping(payload);
|
||||||
}
|
if (!params.opts?.onPartialReply || textForTyping === undefined) {
|
||||||
await params.opts.onPartialReply({
|
return;
|
||||||
text: textForTyping,
|
}
|
||||||
mediaUrls: payload.mediaUrls,
|
await params.opts.onPartialReply({
|
||||||
});
|
text: textForTyping,
|
||||||
},
|
mediaUrls: payload.mediaUrls,
|
||||||
onAssistantMessageStart: async () => {
|
});
|
||||||
await params.typingSignals.signalMessageStart();
|
},
|
||||||
await params.opts?.onAssistantMessageStart?.();
|
onAssistantMessageStart: async () => {
|
||||||
},
|
await params.typingSignals.signalMessageStart();
|
||||||
onReasoningStream:
|
await params.opts?.onAssistantMessageStart?.();
|
||||||
params.typingSignals.shouldStartOnReasoning || params.opts?.onReasoningStream
|
},
|
||||||
? async (payload) => {
|
onReasoningStream:
|
||||||
await params.typingSignals.signalReasoningDelta();
|
params.typingSignals.shouldStartOnReasoning || params.opts?.onReasoningStream
|
||||||
await params.opts?.onReasoningStream?.({
|
? async (payload) => {
|
||||||
text: payload.text,
|
await params.typingSignals.signalReasoningDelta();
|
||||||
mediaUrls: payload.mediaUrls,
|
await params.opts?.onReasoningStream?.({
|
||||||
});
|
text: payload.text,
|
||||||
|
mediaUrls: payload.mediaUrls,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
onReasoningEnd: params.opts?.onReasoningEnd,
|
||||||
|
onAgentEvent: async (evt) => {
|
||||||
|
// Signal run start only after the embedded agent emits real activity.
|
||||||
|
const hasLifecyclePhase =
|
||||||
|
evt.stream === "lifecycle" && typeof evt.data.phase === "string";
|
||||||
|
if (evt.stream !== "lifecycle" || hasLifecyclePhase) {
|
||||||
|
notifyAgentRunStart();
|
||||||
|
}
|
||||||
|
// Trigger typing when tools start executing.
|
||||||
|
// Must await to ensure typing indicator starts before tool summaries are emitted.
|
||||||
|
if (evt.stream === "tool") {
|
||||||
|
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
||||||
|
const name = typeof evt.data.name === "string" ? evt.data.name : undefined;
|
||||||
|
if (phase === "start" || phase === "update") {
|
||||||
|
await params.typingSignals.signalToolStart();
|
||||||
|
await params.opts?.onToolStart?.({ name, phase });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Track auto-compaction completion and notify UI layer
|
// Track auto-compaction completion and notify UI layer.
|
||||||
if (evt.stream === "compaction") {
|
if (evt.stream === "compaction") {
|
||||||
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
||||||
if (phase === "start") {
|
if (phase === "start") {
|
||||||
|
|
@ -401,104 +421,63 @@ export async function runAgentTurnWithFallback(params: {
|
||||||
directlySentBlockKeys,
|
directlySentBlockKeys,
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
onReasoningEnd: params.opts?.onReasoningEnd,
|
onBlockReplyFlush:
|
||||||
onAgentEvent: async (evt) => {
|
params.blockStreamingEnabled && blockReplyPipeline
|
||||||
// Signal run start only after the embedded agent emits real activity.
|
? async () => {
|
||||||
const hasLifecyclePhase =
|
await blockReplyPipeline.flush({ force: true });
|
||||||
evt.stream === "lifecycle" && typeof evt.data.phase === "string";
|
}
|
||||||
if (evt.stream !== "lifecycle" || hasLifecyclePhase) {
|
: undefined,
|
||||||
notifyAgentRunStart();
|
shouldEmitToolResult: params.shouldEmitToolResult,
|
||||||
}
|
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
||||||
// Trigger typing when tools start executing.
|
bootstrapPromptWarningSignaturesSeen,
|
||||||
// Must await to ensure typing indicator starts before tool summaries are emitted.
|
bootstrapPromptWarningSignature:
|
||||||
if (evt.stream === "tool") {
|
bootstrapPromptWarningSignaturesSeen[
|
||||||
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
bootstrapPromptWarningSignaturesSeen.length - 1
|
||||||
const name = typeof evt.data.name === "string" ? evt.data.name : undefined;
|
],
|
||||||
if (phase === "start" || phase === "update") {
|
onToolResult: onToolResult
|
||||||
await params.typingSignals.signalToolStart();
|
? (() => {
|
||||||
await params.opts?.onToolStart?.({ name, phase });
|
// Serialize tool result delivery to preserve message ordering.
|
||||||
}
|
// Without this, concurrent tool callbacks race through typing signals
|
||||||
}
|
// and message sends, causing out-of-order delivery to the user.
|
||||||
// Track auto-compaction completion and notify UI layer
|
// See: https://github.com/openclaw/openclaw/issues/11044
|
||||||
if (evt.stream === "compaction") {
|
let toolResultChain: Promise<void> = Promise.resolve();
|
||||||
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
return (payload: ReplyPayload) => {
|
||||||
if (phase === "start") {
|
toolResultChain = toolResultChain
|
||||||
await params.opts?.onCompactionStart?.();
|
.then(async () => {
|
||||||
}
|
const { text, skip } = normalizeStreamingText(payload);
|
||||||
if (phase === "end") {
|
if (skip) {
|
||||||
autoCompactionCount += 1;
|
return;
|
||||||
await params.opts?.onCompactionEnd?.();
|
}
|
||||||
}
|
await params.typingSignals.signalTextDelta(text);
|
||||||
}
|
await onToolResult({
|
||||||
},
|
...payload,
|
||||||
// Always pass onBlockReply so flushBlockReplyBuffer works before tool execution,
|
text,
|
||||||
// even when regular block streaming is disabled. The handler sends directly
|
});
|
||||||
// via opts.onBlockReply when the pipeline isn't available.
|
})
|
||||||
onBlockReply: params.opts?.onBlockReply
|
.catch((err) => {
|
||||||
? createBlockReplyDeliveryHandler({
|
// Keep chain healthy after an error so later tool results still deliver.
|
||||||
onBlockReply: params.opts.onBlockReply,
|
logVerbose(`tool result delivery failed: ${String(err)}`);
|
||||||
currentMessageId:
|
|
||||||
params.sessionCtx.MessageSidFull ?? params.sessionCtx.MessageSid,
|
|
||||||
normalizeStreamingText,
|
|
||||||
applyReplyToMode: params.applyReplyToMode,
|
|
||||||
normalizeMediaPaths: normalizeReplyMediaPaths,
|
|
||||||
typingSignals: params.typingSignals,
|
|
||||||
blockStreamingEnabled: params.blockStreamingEnabled,
|
|
||||||
blockReplyPipeline,
|
|
||||||
directlySentBlockKeys,
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
onBlockReplyFlush:
|
|
||||||
params.blockStreamingEnabled && blockReplyPipeline
|
|
||||||
? async () => {
|
|
||||||
await blockReplyPipeline.flush({ force: true });
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
shouldEmitToolResult: params.shouldEmitToolResult,
|
|
||||||
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
|
||||||
bootstrapPromptWarningSignaturesSeen,
|
|
||||||
bootstrapPromptWarningSignature:
|
|
||||||
bootstrapPromptWarningSignaturesSeen[
|
|
||||||
bootstrapPromptWarningSignaturesSeen.length - 1
|
|
||||||
],
|
|
||||||
onToolResult: onToolResult
|
|
||||||
? (() => {
|
|
||||||
// Serialize tool result delivery to preserve message ordering.
|
|
||||||
// Without this, concurrent tool callbacks race through typing signals
|
|
||||||
// and message sends, causing out-of-order delivery to the user.
|
|
||||||
// See: https://github.com/openclaw/openclaw/issues/11044
|
|
||||||
let toolResultChain: Promise<void> = Promise.resolve();
|
|
||||||
return (payload: ReplyPayload) => {
|
|
||||||
toolResultChain = toolResultChain
|
|
||||||
.then(async () => {
|
|
||||||
const { text, skip } = normalizeStreamingText(payload);
|
|
||||||
if (skip) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await params.typingSignals.signalTextDelta(text);
|
|
||||||
await onToolResult({
|
|
||||||
...payload,
|
|
||||||
text,
|
|
||||||
});
|
});
|
||||||
})
|
const task = toolResultChain.finally(() => {
|
||||||
.catch((err) => {
|
params.pendingToolTasks.delete(task);
|
||||||
// Keep chain healthy after an error so later tool results still deliver.
|
|
||||||
logVerbose(`tool result delivery failed: ${String(err)}`);
|
|
||||||
});
|
});
|
||||||
const task = toolResultChain.finally(() => {
|
params.pendingToolTasks.add(task);
|
||||||
params.pendingToolTasks.delete(task);
|
};
|
||||||
});
|
})()
|
||||||
params.pendingToolTasks.add(task);
|
: undefined,
|
||||||
};
|
});
|
||||||
})()
|
bootstrapPromptWarningSignaturesSeen = resolveBootstrapWarningSignaturesSeen(
|
||||||
: undefined,
|
result.meta?.systemPromptReport,
|
||||||
});
|
);
|
||||||
bootstrapPromptWarningSignaturesSeen = resolveBootstrapWarningSignaturesSeen(
|
const resultCompactionCount = Math.max(
|
||||||
result.meta?.systemPromptReport,
|
0,
|
||||||
);
|
result.meta?.agentMeta?.compactionCount ?? 0,
|
||||||
const resultCompactionCount = Math.max(0, result.meta?.agentMeta?.compactionCount ?? 0);
|
);
|
||||||
autoCompactionCount = Math.max(autoCompactionCount, resultCompactionCount);
|
attemptCompactionCount = Math.max(attemptCompactionCount, resultCompactionCount);
|
||||||
return result;
|
return result;
|
||||||
|
} finally {
|
||||||
|
autoCompactionCount += attemptCompactionCount;
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,7 @@ export function createFollowupRunner(params: {
|
||||||
}),
|
}),
|
||||||
run: async (provider, model, runOptions) => {
|
run: async (provider, model, runOptions) => {
|
||||||
const authProfile = resolveRunAuthProfile(queued.run, provider);
|
const authProfile = resolveRunAuthProfile(queued.run, provider);
|
||||||
|
let attemptCompactionCount = 0;
|
||||||
try {
|
try {
|
||||||
const result = await runEmbeddedPiAgent({
|
const result = await runEmbeddedPiAgent({
|
||||||
sessionId: queued.run.sessionId,
|
sessionId: queued.run.sessionId,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue