mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-06 23:55:12 +00:00
feat: add user input blocking lifecycle gates
This commit is contained in:
@@ -4581,6 +4581,38 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () =>
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves hook-blocked metadata when source delivery is message-tool-only", async () => {
|
||||
setNoAbort();
|
||||
sessionStoreMocks.currentEntry = {
|
||||
sessionId: "s1",
|
||||
updatedAt: 0,
|
||||
sendPolicy: "allow",
|
||||
};
|
||||
const dispatcher = createDispatcher();
|
||||
const blockedReply = setReplyPayloadMetadata(
|
||||
{ text: "Your message could not be sent: blocked by policy-plugin", isError: true },
|
||||
{ beforeAgentRunBlocked: true },
|
||||
);
|
||||
const replyResolver = vi.fn(async () => blockedReply satisfies ReplyPayload);
|
||||
|
||||
const result = await dispatchReplyFromConfig({
|
||||
ctx: buildTestCtx({ SessionKey: "test:session" }),
|
||||
cfg: emptyConfig,
|
||||
dispatcher,
|
||||
replyResolver,
|
||||
replyOptions: {
|
||||
sourceReplyDeliveryMode: "message_tool_only",
|
||||
},
|
||||
});
|
||||
|
||||
expect(replyResolver).toHaveBeenCalledTimes(1);
|
||||
expect(result.queuedFinal).toBe(false);
|
||||
expect(result.beforeAgentRunBlocked).toBe(true);
|
||||
expect(result.sourceReplyDeliveryMode).toBe("message_tool_only");
|
||||
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
|
||||
expect(dispatcher.sendBlockReply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("delivers marked runtime failure notices in message-tool-only mode", async () => {
|
||||
setNoAbort();
|
||||
sessionStoreMocks.currentEntry = {
|
||||
|
||||
@@ -95,7 +95,10 @@ import { withFullRuntimeReplyConfig } from "./get-reply-fast-path.js";
|
||||
import { claimInboundDedupe, commitInboundDedupe, releaseInboundDedupe } from "./inbound-dedupe.js";
|
||||
import { resolveOriginMessageProvider } from "./origin-routing.js";
|
||||
import { resolveReplyRoutingDecision } from "./routing-policy.js";
|
||||
import { resolveSourceReplyVisibilityPolicy } from "./source-reply-delivery-mode.js";
|
||||
import {
|
||||
isExplicitSourceReplyCommand,
|
||||
resolveSourceReplyVisibilityPolicy,
|
||||
} from "./source-reply-delivery-mode.js";
|
||||
import { resolveRunTypingPolicy } from "./typing-policy.js";
|
||||
|
||||
const routeReplyRuntimeLoader = createLazyImportLoader(() => import("./route-reply.runtime.js"));
|
||||
@@ -711,7 +714,7 @@ export async function dispatchReplyFromConfig(
|
||||
const prefersMessageToolDelivery =
|
||||
params.replyOptions?.sourceReplyDeliveryMode === "message_tool_only" ||
|
||||
(params.replyOptions?.sourceReplyDeliveryMode === undefined &&
|
||||
ctx.CommandSource !== "native" &&
|
||||
!isExplicitSourceReplyCommand(ctx) &&
|
||||
(chatType === "group" || chatType === "channel"
|
||||
? effectiveVisibleReplies !== "automatic"
|
||||
: effectiveVisibleReplies === "message_tool"));
|
||||
|
||||
@@ -97,13 +97,23 @@ describe("resolveSourceReplyDeliveryMode", () => {
|
||||
expect(
|
||||
resolveSourceReplyDeliveryMode({
|
||||
cfg: emptyConfig,
|
||||
ctx: { ChatType: "group", CommandSource: "text", CommandAuthorized: true },
|
||||
ctx: {
|
||||
ChatType: "group",
|
||||
CommandSource: "text",
|
||||
CommandAuthorized: true,
|
||||
CommandBody: "/status",
|
||||
},
|
||||
}),
|
||||
).toBe("automatic");
|
||||
expect(
|
||||
resolveSourceReplyDeliveryMode({
|
||||
cfg: emptyConfig,
|
||||
ctx: { ChatType: "group", CommandSource: "text" },
|
||||
ctx: {
|
||||
ChatType: "group",
|
||||
CommandSource: "text",
|
||||
CommandAuthorized: false,
|
||||
CommandBody: "/status",
|
||||
},
|
||||
}),
|
||||
).toBe("message_tool_only");
|
||||
});
|
||||
@@ -192,7 +202,12 @@ describe("resolveSourceReplyVisibilityPolicy", () => {
|
||||
it("keeps native and authorized text command replies visible in groups", () => {
|
||||
for (const ctx of [
|
||||
{ ChatType: "group", CommandSource: "native" },
|
||||
{ ChatType: "group", CommandSource: "text", CommandAuthorized: true },
|
||||
{
|
||||
ChatType: "group",
|
||||
CommandSource: "text",
|
||||
CommandAuthorized: true,
|
||||
CommandBody: "/status",
|
||||
},
|
||||
] as const) {
|
||||
expect(
|
||||
resolveSourceReplyVisibilityPolicy({
|
||||
|
||||
@@ -6,9 +6,17 @@ import type { SourceReplyDeliveryMode } from "../get-reply-options.types.js";
|
||||
export type SourceReplyDeliveryModeContext = {
|
||||
ChatType?: string;
|
||||
CommandAuthorized?: boolean;
|
||||
CommandBody?: string;
|
||||
CommandSource?: "text" | "native";
|
||||
};
|
||||
|
||||
export function isExplicitSourceReplyCommand(ctx: SourceReplyDeliveryModeContext): boolean {
|
||||
if (ctx.CommandSource === "native") {
|
||||
return true;
|
||||
}
|
||||
return ctx.CommandSource === "text" && ctx.CommandAuthorized === true;
|
||||
}
|
||||
|
||||
export function resolveSourceReplyDeliveryMode(params: {
|
||||
cfg: OpenClawConfig;
|
||||
ctx: SourceReplyDeliveryModeContext;
|
||||
@@ -21,10 +29,7 @@ export function resolveSourceReplyDeliveryMode(params: {
|
||||
? "automatic"
|
||||
: params.requested;
|
||||
}
|
||||
if (
|
||||
params.ctx.CommandSource === "native" ||
|
||||
(params.ctx.CommandSource === "text" && params.ctx.CommandAuthorized === true)
|
||||
) {
|
||||
if (isExplicitSourceReplyCommand(params.ctx)) {
|
||||
return "automatic";
|
||||
}
|
||||
const chatType = normalizeChatType(params.ctx.ChatType);
|
||||
|
||||
Reference in New Issue
Block a user