mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 04:51:04 +00:00
feat: show status reaction during context compaction
When context auto-compaction runs, the bot appears frozen for 10-30s with no feedback. This adds a ✍ (writing) status reaction during compaction so users know the bot is still working. - Add `compacting` state to StatusReactionController with ✍ emoji - Add `onCompactionStart`/`onCompactionEnd` callbacks to GetReplyOptions - Wire existing compaction events to the status reaction system - Apply to both Telegram and Discord channels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -393,11 +393,15 @@ export async function runAgentTurnWithFallback(params: {
|
||||
await params.opts?.onToolStart?.({ name, phase });
|
||||
}
|
||||
}
|
||||
// Track auto-compaction completion
|
||||
// Track auto-compaction completion and notify UI layer
|
||||
if (evt.stream === "compaction") {
|
||||
const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
|
||||
if (phase === "start") {
|
||||
await params.opts?.onCompactionStart?.();
|
||||
}
|
||||
if (phase === "end") {
|
||||
autoCompactionCompleted = true;
|
||||
await params.opts?.onCompactionEnd?.();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -54,6 +54,10 @@ export type GetReplyOptions = {
|
||||
onToolResult?: (payload: ReplyPayload) => Promise<void> | void;
|
||||
/** Called when a tool phase starts/updates, before summary payloads are emitted. */
|
||||
onToolStart?: (payload: { name?: string; phase?: string }) => Promise<void> | void;
|
||||
/** Called when context auto-compaction starts (allows UX feedback during the pause). */
|
||||
onCompactionStart?: () => Promise<void> | void;
|
||||
/** Called when context auto-compaction completes. */
|
||||
onCompactionEnd?: () => Promise<void> | void;
|
||||
/** Called when the actual model is selected (including after fallback).
|
||||
* Use this to get model/provider/thinkLevel for responsePrefix template interpolation. */
|
||||
onModelSelected?: (ctx: ModelSelectedContext) => void;
|
||||
|
||||
@@ -24,6 +24,7 @@ export type StatusReactionEmojis = {
|
||||
error?: string; // Default: "❌"
|
||||
stallSoft?: string; // Default: "⏳"
|
||||
stallHard?: string; // Default: "⚠️"
|
||||
compacting?: string; // Default: "✍"
|
||||
};
|
||||
|
||||
export type StatusReactionTiming = {
|
||||
@@ -38,6 +39,7 @@ export type StatusReactionController = {
|
||||
setQueued: () => Promise<void> | void;
|
||||
setThinking: () => Promise<void> | void;
|
||||
setTool: (toolName?: string) => Promise<void> | void;
|
||||
setCompacting: () => Promise<void> | void;
|
||||
setDone: () => Promise<void>;
|
||||
setError: () => Promise<void>;
|
||||
clear: () => Promise<void>;
|
||||
@@ -58,6 +60,7 @@ export const DEFAULT_EMOJIS: Required<StatusReactionEmojis> = {
|
||||
error: "😱",
|
||||
stallSoft: "🥱",
|
||||
stallHard: "😨",
|
||||
compacting: "✍",
|
||||
};
|
||||
|
||||
export const DEFAULT_TIMING: Required<StatusReactionTiming> = {
|
||||
@@ -162,6 +165,7 @@ export function createStatusReactionController(params: {
|
||||
emojis.error,
|
||||
emojis.stallSoft,
|
||||
emojis.stallHard,
|
||||
emojis.compacting,
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -306,6 +310,10 @@ export function createStatusReactionController(params: {
|
||||
scheduleEmoji(emoji);
|
||||
}
|
||||
|
||||
function setCompacting(): void {
|
||||
scheduleEmoji(emojis.compacting);
|
||||
}
|
||||
|
||||
function finishWithEmoji(emoji: string): Promise<void> {
|
||||
if (!enabled) {
|
||||
return Promise.resolve();
|
||||
@@ -375,6 +383,7 @@ export function createStatusReactionController(params: {
|
||||
setQueued,
|
||||
setThinking,
|
||||
setTool,
|
||||
setCompacting,
|
||||
setDone,
|
||||
setError,
|
||||
clear,
|
||||
|
||||
@@ -769,6 +769,18 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
}
|
||||
await statusReactions.setTool(payload.name);
|
||||
},
|
||||
onCompactionStart: async () => {
|
||||
if (isProcessAborted(abortSignal)) {
|
||||
return;
|
||||
}
|
||||
await statusReactions.setCompacting();
|
||||
},
|
||||
onCompactionEnd: async () => {
|
||||
if (isProcessAborted(abortSignal)) {
|
||||
return;
|
||||
}
|
||||
await statusReactions.setThinking();
|
||||
},
|
||||
},
|
||||
});
|
||||
if (isProcessAborted(abortSignal)) {
|
||||
|
||||
@@ -713,6 +713,12 @@ export const dispatchTelegramMessage = async ({
|
||||
await statusReactionController.setTool(payload.name);
|
||||
}
|
||||
: undefined,
|
||||
onCompactionStart: statusReactionController
|
||||
? () => statusReactionController.setCompacting()
|
||||
: undefined,
|
||||
onCompactionEnd: statusReactionController
|
||||
? () => statusReactionController.setThinking()
|
||||
: undefined,
|
||||
onModelSelected,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -90,6 +90,7 @@ export const TELEGRAM_STATUS_REACTION_VARIANTS: Record<StatusReactionEmojiKey, s
|
||||
error: ["😱", "😨", "🤯"],
|
||||
stallSoft: ["🥱", "😴", "🤔"],
|
||||
stallHard: ["😨", "😱", "⚡"],
|
||||
compacting: ["✍", "🤔", "🤯"],
|
||||
};
|
||||
|
||||
const STATUS_REACTION_EMOJI_KEYS: StatusReactionEmojiKey[] = [
|
||||
@@ -102,6 +103,7 @@ const STATUS_REACTION_EMOJI_KEYS: StatusReactionEmojiKey[] = [
|
||||
"error",
|
||||
"stallSoft",
|
||||
"stallHard",
|
||||
"compacting",
|
||||
];
|
||||
|
||||
function normalizeEmoji(value: string | undefined): string | undefined {
|
||||
@@ -129,6 +131,7 @@ export function resolveTelegramStatusReactionEmojis(params: {
|
||||
error: normalizeEmoji(overrides?.error) ?? DEFAULT_EMOJIS.error,
|
||||
stallSoft: normalizeEmoji(overrides?.stallSoft) ?? DEFAULT_EMOJIS.stallSoft,
|
||||
stallHard: normalizeEmoji(overrides?.stallHard) ?? DEFAULT_EMOJIS.stallHard,
|
||||
compacting: normalizeEmoji(overrides?.compacting) ?? DEFAULT_EMOJIS.compacting,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user