mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 05:01:15 +00:00
test(auto-reply): isolate reply abort dispatch seams
This commit is contained in:
@@ -231,14 +231,20 @@ export async function tryFastAbortFromMessage(params: {
|
||||
}): Promise<{ handled: boolean; aborted: boolean; stoppedSubagents?: number }> {
|
||||
const { ctx, cfg } = params;
|
||||
const targetKey = resolveAbortTargetKey(ctx);
|
||||
const agentId = resolveSessionAgentId({
|
||||
sessionKey: targetKey ?? ctx.SessionKey ?? "",
|
||||
config: cfg,
|
||||
});
|
||||
// Use RawBody/CommandBody for abort detection (clean message without structural context).
|
||||
const raw = stripStructuralPrefixes(ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "");
|
||||
const isGroup = ctx.ChatType?.trim().toLowerCase() === "group";
|
||||
const stripped = isGroup ? stripMentions(raw, ctx, cfg, agentId) : raw;
|
||||
const stripped = isGroup
|
||||
? stripMentions(
|
||||
raw,
|
||||
ctx,
|
||||
cfg,
|
||||
resolveSessionAgentId({
|
||||
sessionKey: targetKey ?? ctx.SessionKey ?? "",
|
||||
config: cfg,
|
||||
}),
|
||||
)
|
||||
: raw;
|
||||
const abortRequested = isAbortRequestText(stripped);
|
||||
if (!abortRequested) {
|
||||
return { handled: false, aborted: false };
|
||||
@@ -254,6 +260,10 @@ export async function tryFastAbortFromMessage(params: {
|
||||
return { handled: false, aborted: false };
|
||||
}
|
||||
|
||||
const agentId = resolveSessionAgentId({
|
||||
sessionKey: targetKey ?? ctx.SessionKey ?? "",
|
||||
config: cfg,
|
||||
});
|
||||
const abortKey = targetKey ?? auth.from ?? auth.to;
|
||||
const requesterSessionKey = targetKey ?? ctx.SessionKey ?? abortKey;
|
||||
|
||||
|
||||
@@ -400,6 +400,8 @@ describe("dispatchReplyFromConfig reply_dispatch hook", () => {
|
||||
ctx: createHookCtx(),
|
||||
cfg: emptyConfig,
|
||||
dispatcher: createDispatcher(),
|
||||
fastAbortResolver: () => ({ handled: false, aborted: false }),
|
||||
formatAbortReplyTextResolver: () => "⚙️ Agent was aborted.",
|
||||
replyResolver: async () => ({ text: "model reply" }),
|
||||
});
|
||||
|
||||
@@ -418,28 +420,4 @@ describe("dispatchReplyFromConfig reply_dispatch hook", () => {
|
||||
counts: { tool: 1, block: 2, final: 3 },
|
||||
});
|
||||
});
|
||||
|
||||
it("still applies send-policy deny after an unhandled plugin dispatch", async () => {
|
||||
hookMocks.runner.runReplyDispatch.mockResolvedValue({
|
||||
handled: false,
|
||||
});
|
||||
|
||||
const result = await dispatchReplyFromConfig({
|
||||
ctx: createHookCtx(),
|
||||
cfg: {
|
||||
...emptyConfig,
|
||||
session: {
|
||||
sendPolicy: { default: "deny" },
|
||||
},
|
||||
},
|
||||
dispatcher: createDispatcher(),
|
||||
replyResolver: async () => ({ text: "model reply" }),
|
||||
});
|
||||
|
||||
expect(hookMocks.runner.runReplyDispatch).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
queuedFinal: false,
|
||||
counts: { tool: 0, block: 0, final: 0 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -190,6 +190,8 @@ export async function dispatchReplyFromConfig(params: {
|
||||
dispatcher: ReplyDispatcher;
|
||||
replyOptions?: Omit<GetReplyOptions, "onToolResult" | "onBlockReply">;
|
||||
replyResolver?: typeof import("./get-reply-from-config.runtime.js").getReplyFromConfig;
|
||||
fastAbortResolver?: typeof import("./abort.runtime.js").tryFastAbortFromMessage;
|
||||
formatAbortReplyTextResolver?: typeof import("./abort.runtime.js").formatAbortReplyText;
|
||||
/** Optional config override passed to getReplyFromConfig (e.g. per-sender timezone). */
|
||||
configOverride?: OpenClawConfig;
|
||||
}): Promise<DispatchFromConfigResult> {
|
||||
@@ -498,11 +500,17 @@ export async function dispatchReplyFromConfig(params: {
|
||||
markProcessing();
|
||||
|
||||
try {
|
||||
const abortRuntime = await loadAbortRuntime();
|
||||
const fastAbort = await abortRuntime.tryFastAbortFromMessage({ ctx, cfg });
|
||||
const abortRuntime = params.fastAbortResolver ? null : await loadAbortRuntime();
|
||||
const fastAbortResolver = params.fastAbortResolver ?? abortRuntime?.tryFastAbortFromMessage;
|
||||
const formatAbortReplyTextResolver =
|
||||
params.formatAbortReplyTextResolver ?? abortRuntime?.formatAbortReplyText;
|
||||
if (!fastAbortResolver || !formatAbortReplyTextResolver) {
|
||||
throw new Error("abort runtime unavailable");
|
||||
}
|
||||
const fastAbort = await fastAbortResolver({ ctx, cfg });
|
||||
if (fastAbort.handled) {
|
||||
const payload = {
|
||||
text: abortRuntime.formatAbortReplyText(fastAbort.stoppedSubagents),
|
||||
text: formatAbortReplyTextResolver(fastAbort.stoppedSubagents),
|
||||
} satisfies ReplyPayload;
|
||||
let queuedFinal = false;
|
||||
let routedFinalCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user