Files
openclaw/extensions/msteams/src/monitor-handler/message-handler.test-support.ts
WhatsSkiLL 7dc2e50ac3 fix(channels): bypass debounce for bare abort triggers [AI-assisted] (#83348)
Summary:
- The PR changes shared, Feishu, Mattermost, Microsoft Teams, and WhatsApp inbound debounce predicates so bare abort text bypasses debounce, then adds focused tests and a changelog entry.
- Reproducibility: yes. source-level. Current main sends bare `stop`, `abort`, and `wait` through a `hasContro ... ()` debounce gate, while the existing abort-aware detector and trigger set already recognize those phrases.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(channels): bypass debounce for bare abort triggers [AI-assisted]
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8334…

Validation:
- ClawSweeper review passed for head c96bf84270.
- Required merge gates passed before the squash merge.

Prepared head SHA: c96bf84270
Review: https://github.com/openclaw/openclaw/pull/83348#issuecomment-4473176095

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-22 04:09:33 +00:00

113 lines
4.2 KiB
TypeScript

import { vi } from "vitest";
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "../../runtime-api.js";
import type { MSTeamsMessageHandlerDeps } from "../monitor-handler.js";
import { installMSTeamsTestRuntime } from "../monitor-handler.test-helpers.js";
export const channelConversationId = "19:general@thread.tacv2";
type MessageHandlerDepsOptions = {
enqueueSystemEvent?: ReturnType<typeof vi.fn>;
readAllowFromStore?: ReturnType<typeof vi.fn>;
upsertPairingRequest?: ReturnType<typeof vi.fn>;
recordInboundSession?: ReturnType<typeof vi.fn>;
resolveAgentRoute?: (params: { peer: { kind: string; id: string } }) => unknown;
hasControlCommand?: PluginRuntime["channel"]["text"]["hasControlCommand"];
isControlCommandMessage?: PluginRuntime["channel"]["commands"]["isControlCommandMessage"];
shouldComputeCommandAuthorized?: PluginRuntime["channel"]["commands"]["shouldComputeCommandAuthorized"];
shouldHandleTextCommands?: PluginRuntime["channel"]["commands"]["shouldHandleTextCommands"];
createInboundDebouncer?: PluginRuntime["channel"]["debounce"]["createInboundDebouncer"];
resolveInboundDebounceMs?: PluginRuntime["channel"]["debounce"]["resolveInboundDebounceMs"];
};
export function createMessageHandlerDeps(
cfg: OpenClawConfig,
options: MessageHandlerDepsOptions = {},
) {
const enqueueSystemEvent = options.enqueueSystemEvent ?? vi.fn();
const readAllowFromStore = options.readAllowFromStore ?? vi.fn(async () => []);
const upsertPairingRequest = options.upsertPairingRequest ?? vi.fn(async () => null);
const recordInboundSession =
options.recordInboundSession ?? vi.fn(async (_params: { sessionKey: string }) => undefined);
const resolveAgentRoute =
options.resolveAgentRoute ??
vi.fn(({ peer }: { peer: { kind: string; id: string } }) => ({
sessionKey: `agent:main:msteams:${peer.kind}:${peer.id}`,
agentId: "main",
accountId: "default",
mainSessionKey: "agent:main:main",
lastRoutePolicy: "session" as const,
matchedBy: "default" as const,
}));
installMSTeamsTestRuntime({
enqueueSystemEvent,
readAllowFromStore,
upsertPairingRequest,
recordInboundSession,
resolveAgentRoute,
hasControlCommand: options.hasControlCommand,
isControlCommandMessage: options.isControlCommandMessage,
shouldComputeCommandAuthorized: options.shouldComputeCommandAuthorized,
shouldHandleTextCommands: options.shouldHandleTextCommands,
createInboundDebouncer: options.createInboundDebouncer,
resolveInboundDebounceMs: options.resolveInboundDebounceMs,
resolveTextChunkLimit: () => 4000,
resolveStorePath: () => "/tmp/test-store",
});
const conversationStore = {
get: vi.fn(async () => null),
upsert: vi.fn(async () => undefined),
list: vi.fn(async () => []),
remove: vi.fn(async () => false),
findPreferredDmByUserId: vi.fn(async () => null),
findByUserId: vi.fn(async () => null),
} satisfies MSTeamsMessageHandlerDeps["conversationStore"];
const deps: MSTeamsMessageHandlerDeps = {
cfg,
runtime: { error: vi.fn() } as unknown as RuntimeEnv,
appId: "test-app",
adapter: {} as MSTeamsMessageHandlerDeps["adapter"],
tokenProvider: {
getAccessToken: vi.fn(async () => "token"),
},
textLimit: 4000,
mediaMaxBytes: 1024 * 1024,
conversationStore,
pollStore: {
recordVote: vi.fn(async () => null),
} as unknown as MSTeamsMessageHandlerDeps["pollStore"],
log: {
info: vi.fn(),
debug: vi.fn(),
error: vi.fn(),
} as unknown as MSTeamsMessageHandlerDeps["log"],
};
return {
conversationStore,
deps,
enqueueSystemEvent,
readAllowFromStore,
upsertPairingRequest,
recordInboundSession,
resolveAgentRoute,
};
}
export function buildChannelActivity(overrides: Record<string, unknown> = {}) {
return {
id: "msg-1",
type: "message",
text: "hello",
from: { id: "user-id", aadObjectId: "user-aad", name: "Test User" },
recipient: { id: "bot-id", name: "Bot" },
conversation: { id: channelConversationId, conversationType: "channel" },
channelData: { team: { id: "team-1" } },
attachments: [],
entities: [{ type: "mention", mentioned: { id: "bot-id" } }],
...overrides,
};
}