mirror of https://github.com/openclaw/openclaw.git
fix(matrix): avoid touching dropped room bindings
This commit is contained in:
parent
ee749b520e
commit
0d161069f2
|
|
@ -288,6 +288,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Plugins/message discovery: require `ChannelMessageActionAdapter.describeMessageTool(...)` for shared `message` tool discovery. The legacy `listActions`, `getCapabilities`, and `getToolSchema` adapter methods are removed. Plugin authors should migrate message discovery to `describeMessageTool(...)` and keep channel-specific action runtime code inside the owning plugin package. Thanks @gumadeiras.
|
||||
- Exec/env sandbox: block build-tool JVM injection (`MAVEN_OPTS`, `SBT_OPTS`, `GRADLE_OPTS`, `ANT_OPTS`), glibc tunable exploitation (`GLIBC_TUNABLES`), and .NET dependency resolution hijack (`DOTNET_ADDITIONAL_DEPS`) from the host exec environment, and restrict Gradle init script redirect (`GRADLE_USER_HOME`) as an override-only block so user-configured Gradle homes still propagate. (#49702)
|
||||
- Plugins/Matrix: add a new Matrix plugin backed by the official `matrix-js-sdk`. If you are upgrading from the previous public Matrix plugin, follow the migration guide: https://docs.openclaw.ai/install/migrating-matrix Thanks @gumadeiras.
|
||||
- Plugins/Matrix: stop mention-gated or otherwise dropped room chatter from refreshing focused thread bindings before the message is actually routed, so idle ACP and session bindings can still expire normally in mention-required rooms. Thanks @vincentkoc, @dinakars777 and @mvanhorn.
|
||||
- Discord/commands: switch native command deployment to Carbon reconcile by default so Discord restarts stop churning slash commands through OpenClaw’s local deploy path. (#46597) Thanks @huntharo and @thewilloftheshadow.
|
||||
- Plugins/Matrix: durably dedupe inbound room events across gateway restarts so previously handled Matrix messages are not replayed as new, while preserving clean-restart backlog delivery for unseen events. (#50922) thanks @gumadeiras
|
||||
- Agents/media replies: migrate the remaining browser, canvas, and nodes snapshot outputs onto `details.media` so generated media keeps attaching to assistant replies after the collect-then-attach refactor. (#51731) Thanks @christianklotz.
|
||||
|
|
|
|||
|
|
@ -591,6 +591,7 @@ describe("matrix monitor handler pairing account scope", () => {
|
|||
});
|
||||
|
||||
it("routes bound Matrix threads to the target session key", async () => {
|
||||
const touch = vi.fn();
|
||||
registerSessionBindingAdapter({
|
||||
channel: "matrix",
|
||||
accountId: "ops",
|
||||
|
|
@ -614,7 +615,7 @@ describe("matrix monitor handler pairing account scope", () => {
|
|||
},
|
||||
}
|
||||
: null,
|
||||
touch: vi.fn(),
|
||||
touch,
|
||||
});
|
||||
const { handler, recordInboundSession } = createMatrixHandlerTestHarness({
|
||||
client: {
|
||||
|
|
@ -649,6 +650,64 @@ describe("matrix monitor handler pairing account scope", () => {
|
|||
sessionKey: "agent:bound:session-1",
|
||||
}),
|
||||
);
|
||||
expect(touch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not refresh bound Matrix thread bindings for room messages dropped before routing", async () => {
|
||||
const touch = vi.fn();
|
||||
registerSessionBindingAdapter({
|
||||
channel: "matrix",
|
||||
accountId: "ops",
|
||||
listBySession: () => [],
|
||||
resolveByConversation: (ref) =>
|
||||
ref.conversationId === "$root"
|
||||
? {
|
||||
bindingId: "ops:!room:example:$root",
|
||||
targetSessionKey: "agent:bound:session-1",
|
||||
targetKind: "session",
|
||||
conversation: {
|
||||
channel: "matrix",
|
||||
accountId: "ops",
|
||||
conversationId: "$root",
|
||||
parentConversationId: "!room:example",
|
||||
},
|
||||
status: "active",
|
||||
boundAt: Date.now(),
|
||||
metadata: {
|
||||
boundBy: "user-1",
|
||||
},
|
||||
}
|
||||
: null,
|
||||
touch,
|
||||
});
|
||||
const { handler, recordInboundSession } = createMatrixHandlerTestHarness({
|
||||
client: {
|
||||
getEvent: async () =>
|
||||
createMatrixTextMessageEvent({
|
||||
eventId: "$root",
|
||||
sender: "@alice:example.org",
|
||||
body: "Root topic",
|
||||
}),
|
||||
},
|
||||
isDirectMessage: false,
|
||||
getMemberDisplayName: async () => "sender",
|
||||
});
|
||||
|
||||
await handler(
|
||||
"!room:example",
|
||||
createMatrixTextMessageEvent({
|
||||
eventId: "$reply-no-mention",
|
||||
body: "follow up without mention",
|
||||
relatesTo: {
|
||||
rel_type: "m.thread",
|
||||
event_id: "$root",
|
||||
"m.in_reply_to": { event_id: "$root" },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(recordInboundSession).not.toHaveBeenCalled();
|
||||
expect(touch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not enqueue system events for delivered text replies", async () => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
ensureConfiguredAcpBindingReady,
|
||||
formatAllowlistMatchMeta,
|
||||
getAgentScopedMediaLocalRoots,
|
||||
getSessionBindingService,
|
||||
logInboundDrop,
|
||||
logTypingFailure,
|
||||
resolveControlCommandGate,
|
||||
|
|
@ -529,7 +530,11 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|||
|
||||
const _messageId = event.event_id ?? "";
|
||||
const _threadRootId = resolveMatrixThreadRootId({ event, content });
|
||||
const { route: _route, configuredBinding: _configuredBinding } = resolveMatrixInboundRoute({
|
||||
const {
|
||||
route: _route,
|
||||
configuredBinding: _configuredBinding,
|
||||
runtimeBindingId: _runtimeBindingId,
|
||||
} = resolveMatrixInboundRoute({
|
||||
cfg,
|
||||
accountId,
|
||||
roomId,
|
||||
|
|
@ -714,6 +719,9 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (_runtimeBindingId) {
|
||||
getSessionBindingService().touch(_runtimeBindingId, eventTs ?? undefined);
|
||||
}
|
||||
const envelopeFrom = isDirectMessage ? senderName : (roomName ?? roomId);
|
||||
const textWithId = `${bodyText}\n[matrix event id: ${_messageId} room: ${roomId}]`;
|
||||
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getSessionBindingService } from "../../runtime-api.js";
|
||||
import type { PluginRuntime } from "../../runtime-api.js";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import { resolveMatrixAccountConfig } from "../accounts.js";
|
||||
|
|
@ -72,7 +73,7 @@ export async function handleInboundMatrixReaction(params: {
|
|||
content: targetContent,
|
||||
})
|
||||
: undefined;
|
||||
const { route } = resolveMatrixInboundRoute({
|
||||
const { route, runtimeBindingId } = resolveMatrixInboundRoute({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
roomId: params.roomId,
|
||||
|
|
@ -83,6 +84,9 @@ export async function handleInboundMatrixReaction(params: {
|
|||
eventTs: params.event.origin_server_ts,
|
||||
resolveAgentRoute: params.core.channel.routing.resolveAgentRoute,
|
||||
});
|
||||
if (runtimeBindingId) {
|
||||
getSessionBindingService().touch(runtimeBindingId, params.event.origin_server_ts);
|
||||
}
|
||||
const text = `Matrix reaction added: ${reaction.key} by ${params.senderLabel} on msg ${reaction.eventId}`;
|
||||
params.core.system.enqueueSystemEvent(text, {
|
||||
sessionKey: route.sessionKey,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ describe("resolveMatrixInboundRoute", () => {
|
|||
});
|
||||
|
||||
it("lets runtime conversation bindings override both sender and room route matches", () => {
|
||||
const touch = vi.fn();
|
||||
registerSessionBindingAdapter({
|
||||
channel: "matrix",
|
||||
accountId: "ops",
|
||||
|
|
@ -151,7 +152,7 @@ describe("resolveMatrixInboundRoute", () => {
|
|||
metadata: { boundBy: "user-1" },
|
||||
}
|
||||
: null,
|
||||
touch: vi.fn(),
|
||||
touch,
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
|
|
@ -176,11 +177,13 @@ describe("resolveMatrixInboundRoute", () => {
|
|||
],
|
||||
} satisfies OpenClawConfig;
|
||||
|
||||
const { route, configuredBinding } = resolveDmRoute(cfg);
|
||||
const { route, configuredBinding, runtimeBindingId } = resolveDmRoute(cfg);
|
||||
|
||||
expect(configuredBinding).toBeNull();
|
||||
expect(runtimeBindingId).toBe("ops:!dm:example.org");
|
||||
expect(route.agentId).toBe("bound");
|
||||
expect(route.matchedBy).toBe("binding.channel");
|
||||
expect(route.sessionKey).toBe("agent:bound:session-1");
|
||||
expect(touch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export function resolveMatrixInboundRoute(params: {
|
|||
}): {
|
||||
route: MatrixResolvedRoute;
|
||||
configuredBinding: ReturnType<typeof resolveConfiguredAcpBindingRecord>;
|
||||
runtimeBindingId: string | null;
|
||||
} {
|
||||
const baseRoute = params.resolveAgentRoute({
|
||||
cfg: params.cfg,
|
||||
|
|
@ -54,9 +55,6 @@ export function resolveMatrixInboundRoute(params: {
|
|||
});
|
||||
const boundSessionKey = runtimeBinding?.targetSessionKey?.trim();
|
||||
|
||||
if (runtimeBinding) {
|
||||
sessionBindingService.touch(runtimeBinding.bindingId, params.eventTs);
|
||||
}
|
||||
if (runtimeBinding && boundSessionKey) {
|
||||
return {
|
||||
route: {
|
||||
|
|
@ -66,6 +64,7 @@ export function resolveMatrixInboundRoute(params: {
|
|||
matchedBy: "binding.channel",
|
||||
},
|
||||
configuredBinding: null,
|
||||
runtimeBindingId: runtimeBinding.bindingId,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -95,5 +94,6 @@ export function resolveMatrixInboundRoute(params: {
|
|||
}
|
||||
: baseRoute,
|
||||
configuredBinding,
|
||||
runtimeBindingId: null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue