mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:20:43 +00:00
Agents: address compaction notice review feedback
- Regenerate config baseline sha256 (pnpm config:docs:gen) - Add incomplete compaction notice when compaction ends without completing - Add test for onCompactionEnd callback precedence over notifyUser notice - Add test for incomplete compaction notice delivery
This commit is contained in:
committed by
Josh Lehman
parent
176adc30c7
commit
f938a02a38
@@ -1,4 +1,4 @@
|
||||
747a24d0acf12f95ec75feabb47dad8f03ff0e3a7173b4d277c648f75d956ce5 config-baseline.json
|
||||
cbb9a6ee1cb69068d5eb63f00f95512ba19778415ea5b2eabe056aaea38978b5 config-baseline.core.json
|
||||
e239cc20f20f8d0172812bc0ad3ee6df52da88e2e2702e3d03a47e01561132ae config-baseline.channel.json
|
||||
b695cb31b4c0cf1d31f842f2892e99cc3ff8d84263ae72b72977cae844b81d6e config-baseline.plugin.json
|
||||
12639f02b5283d35fb01136164f68301c22b8ac6786e69662142ec484924bdd8 config-baseline.json
|
||||
ada810ee23e23ad739f1b652891adffb34988d455638d5e4ea3cb8ac15197304 config-baseline.core.json
|
||||
99bb34fcf83ba6bb50a3fc11f170bd379bee5728b0938707fc39ebd7638e12eb config-baseline.channel.json
|
||||
5f5d4e850df6e9854a85b5d008236854ce185c707fdbb566efcf00f8c08b36e3 config-baseline.plugin.json
|
||||
|
||||
@@ -931,6 +931,131 @@ describe("runAgentTurnWithFallback", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers onCompactionEnd callback over default notice when notifyUser is enabled", async () => {
|
||||
const onBlockReply = vi.fn();
|
||||
const onCompactionEnd = vi.fn();
|
||||
state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => {
|
||||
await params.onAgentEvent?.({ stream: "compaction", data: { phase: "start" } });
|
||||
await params.onAgentEvent?.({
|
||||
stream: "compaction",
|
||||
data: { phase: "end", completed: true },
|
||||
});
|
||||
return { payloads: [{ text: "final" }], meta: {} };
|
||||
});
|
||||
|
||||
const followupRun = createFollowupRun();
|
||||
followupRun.run.config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
compaction: {
|
||||
notifyUser: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();
|
||||
const result = await runAgentTurnWithFallback({
|
||||
commandBody: "hello",
|
||||
followupRun,
|
||||
sessionCtx: {
|
||||
Provider: "whatsapp",
|
||||
MessageSid: "msg",
|
||||
} as unknown as TemplateContext,
|
||||
opts: { onBlockReply, onCompactionEnd },
|
||||
typingSignals: createMockTypingSignaler(),
|
||||
blockReplyPipeline: null,
|
||||
blockStreamingEnabled: false,
|
||||
resolvedBlockStreamingBreak: "message_end",
|
||||
applyReplyToMode: (payload) => payload,
|
||||
shouldEmitToolResult: () => true,
|
||||
shouldEmitToolOutput: () => false,
|
||||
pendingToolTasks: new Set(),
|
||||
resetSessionAfterCompactionFailure: async () => false,
|
||||
resetSessionAfterRoleOrderingConflict: async () => false,
|
||||
isHeartbeat: false,
|
||||
sessionKey: "main",
|
||||
getActiveSessionEntry: () => undefined,
|
||||
resolvedVerboseLevel: "off",
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("success");
|
||||
expect(onCompactionEnd).toHaveBeenCalledTimes(1);
|
||||
// The start notice still fires (no onCompactionStart callback provided),
|
||||
// but the completion notice is suppressed in favor of the callback.
|
||||
expect(onBlockReply).toHaveBeenCalledTimes(1);
|
||||
expect(onBlockReply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "🧹 Compacting context...",
|
||||
isCompactionNotice: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("emits an incomplete compaction notice when compaction ends without completing", async () => {
|
||||
const onBlockReply = vi.fn();
|
||||
state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => {
|
||||
await params.onAgentEvent?.({ stream: "compaction", data: { phase: "start" } });
|
||||
await params.onAgentEvent?.({
|
||||
stream: "compaction",
|
||||
data: { phase: "end", completed: false },
|
||||
});
|
||||
return { payloads: [{ text: "final" }], meta: {} };
|
||||
});
|
||||
|
||||
const followupRun = createFollowupRun();
|
||||
followupRun.run.config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
compaction: {
|
||||
notifyUser: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();
|
||||
const result = await runAgentTurnWithFallback({
|
||||
commandBody: "hello",
|
||||
followupRun,
|
||||
sessionCtx: {
|
||||
Provider: "whatsapp",
|
||||
MessageSid: "msg",
|
||||
} as unknown as TemplateContext,
|
||||
opts: { onBlockReply },
|
||||
typingSignals: createMockTypingSignaler(),
|
||||
blockReplyPipeline: null,
|
||||
blockStreamingEnabled: false,
|
||||
resolvedBlockStreamingBreak: "message_end",
|
||||
applyReplyToMode: (payload) => payload,
|
||||
shouldEmitToolResult: () => true,
|
||||
shouldEmitToolOutput: () => false,
|
||||
pendingToolTasks: new Set(),
|
||||
resetSessionAfterCompactionFailure: async () => false,
|
||||
resetSessionAfterRoleOrderingConflict: async () => false,
|
||||
isHeartbeat: false,
|
||||
sessionKey: "main",
|
||||
getActiveSessionEntry: () => undefined,
|
||||
resolvedVerboseLevel: "off",
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("success");
|
||||
expect(onBlockReply).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
text: "🧹 Compacting context...",
|
||||
isCompactionNotice: true,
|
||||
}),
|
||||
);
|
||||
expect(onBlockReply).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
text: "🧹 Compaction incomplete",
|
||||
isCompactionNotice: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not show a rate-limit countdown for mixed-cause fallback exhaustion", async () => {
|
||||
state.runWithModelFallbackMock.mockRejectedValueOnce(
|
||||
Object.assign(
|
||||
|
||||
@@ -626,12 +626,18 @@ export async function runAgentTurnWithFallback(params: {
|
||||
const currentMessageId = params.sessionCtx.MessageSidFull ?? params.sessionCtx.MessageSid;
|
||||
const shouldNotifyUserAboutCompaction =
|
||||
runtimeConfig?.agents?.defaults?.compaction?.notifyUser === true;
|
||||
const sendCompactionNotice = async (phase: "start" | "end") => {
|
||||
const sendCompactionNotice = async (phase: "start" | "end" | "incomplete") => {
|
||||
if (!params.opts?.onBlockReply) {
|
||||
return;
|
||||
}
|
||||
const text =
|
||||
phase === "start"
|
||||
? "🧹 Compacting context..."
|
||||
: phase === "end"
|
||||
? "🧹 Compaction complete"
|
||||
: "🧹 Compaction incomplete";
|
||||
const noticePayload = params.applyReplyToMode({
|
||||
text: phase === "start" ? "🧹 Compacting context..." : "🧹 Compaction complete",
|
||||
text,
|
||||
replyToId: currentMessageId,
|
||||
replyToCurrent: true,
|
||||
isCompactionNotice: true,
|
||||
@@ -1172,13 +1178,17 @@ export async function runAgentTurnWithFallback(params: {
|
||||
await sendCompactionNotice("start");
|
||||
}
|
||||
}
|
||||
const completed = evt.data?.completed === true;
|
||||
if (phase === "end" && completed) {
|
||||
attemptCompactionCount += 1;
|
||||
if (params.opts?.onCompactionEnd) {
|
||||
await params.opts.onCompactionEnd();
|
||||
if (phase === "end") {
|
||||
const completed = evt.data?.completed === true;
|
||||
if (completed) {
|
||||
attemptCompactionCount += 1;
|
||||
if (params.opts?.onCompactionEnd) {
|
||||
await params.opts.onCompactionEnd();
|
||||
} else if (shouldNotifyUserAboutCompaction) {
|
||||
await sendCompactionNotice("end");
|
||||
}
|
||||
} else if (shouldNotifyUserAboutCompaction) {
|
||||
await sendCompactionNotice("end");
|
||||
await sendCompactionNotice("incomplete");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user