fix(matrix): preserve sender labels in Matrix BodyForAgent
This commit is contained in:
@@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Queue/Drain/Cron reliability: harden lane draining with guaranteed `draining` flag reset on synchronous pump failures, reject new queue enqueues during gateway restart drain windows (instead of silently killing accepted tasks), add `/stop` queued-backlog cutoff metadata with stale-message skipping (while avoiding cross-session native-stop cutoff bleed), and raise isolated cron `agentTurn` outer safety timeout to avoid false 10-minute timeout races against longer agent session timeouts. (#27407, #27332, #27427)
|
||||
- Gateway shared-auth scopes: preserve requested operator scopes for shared-token clients when device identity is unavailable, instead of clearing scopes during auth handling. Landed from contributor PR #27498 by @kevinWangSheng. (#27494)
|
||||
- NO_REPLY suppression: suppress `NO_REPLY` before Slack API send and in sub-agent announce completion flow so sentinel text no longer leaks into user channels. Landed from contributor PRs #27529 (by @Sid-Qin) and #27535 (rewritten minimal landing by maintainers). (#27387, #27531)
|
||||
- Matrix/Group sender identity: preserve sender labels in Matrix group inbound prompt text (`BodyForAgent`) for both channel and threaded messages, and align group envelopes with shared inbound sender-prefix formatting so first-person requests resolve against the current sender. (#27401) thanks @koushikxd.
|
||||
- Auto-reply/Streaming: suppress only exact `NO_REPLY` final replies while still filtering streaming partial sentinel fragments (`NO_`, `NO_RE`, `HEARTBEAT_...`) so substantive replies ending with `NO_REPLY` are delivered and partial silent tokens do not leak during streaming. (#19576) Thanks @aldoeliacim.
|
||||
- Auto-reply/Inbound metadata: add a readable `timestamp` field to conversation info and ignore invalid/out-of-range timestamp values so prompt assembly never crashes on malformed timestamp inputs. (#17017) thanks @liuy.
|
||||
- Typing/Main reply pipeline: always mark dispatch idle in `agent-runner` finalization so typing cleanup runs even when dispatcher `onIdle` does not fire, preventing stuck typing indicators after run completion. (#27250) Thanks @Sid-Qin.
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
resolveMatrixAllowListMatch,
|
||||
resolveMatrixAllowListMatches,
|
||||
} from "./allowlist.js";
|
||||
import { resolveMatrixBodyForAgent } from "./inbound-body.js";
|
||||
import { resolveMatrixLocation, type MatrixLocationPayload } from "./location.js";
|
||||
import { downloadMatrixMedia } from "./media.js";
|
||||
import { resolveMentions } from "./mentions.js";
|
||||
@@ -215,6 +216,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
}
|
||||
|
||||
const senderName = await getMemberDisplayName(roomId, senderId);
|
||||
const senderUsername = senderId.split(":")[0]?.replace(/^@/, "");
|
||||
const storeAllowFrom = isDirectMessage
|
||||
? await readStoreAllowFromForDmPolicy({
|
||||
provider: "matrix",
|
||||
@@ -521,19 +523,26 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
storePath,
|
||||
sessionKey: route.sessionKey,
|
||||
});
|
||||
const body = core.channel.reply.formatAgentEnvelope({
|
||||
const body = core.channel.reply.formatInboundEnvelope({
|
||||
channel: "Matrix",
|
||||
from: envelopeFrom,
|
||||
timestamp: eventTs ?? undefined,
|
||||
previousTimestamp,
|
||||
envelope: envelopeOptions,
|
||||
body: textWithId,
|
||||
chatType: isDirectMessage ? "direct" : "channel",
|
||||
sender: { name: senderName, username: senderUsername },
|
||||
});
|
||||
|
||||
const groupSystemPrompt = roomConfig?.systemPrompt?.trim() || undefined;
|
||||
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
||||
Body: body,
|
||||
BodyForAgent: bodyText,
|
||||
BodyForAgent: resolveMatrixBodyForAgent({
|
||||
isDirectMessage,
|
||||
bodyText,
|
||||
senderName,
|
||||
senderId,
|
||||
}),
|
||||
RawBody: bodyText,
|
||||
CommandBody: bodyText,
|
||||
From: isDirectMessage ? `matrix:${senderId}` : `matrix:channel:${roomId}`,
|
||||
@@ -544,7 +553,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
ConversationLabel: envelopeFrom,
|
||||
SenderName: senderName,
|
||||
SenderId: senderId,
|
||||
SenderUsername: senderId.split(":")[0]?.replace(/^@/, ""),
|
||||
SenderUsername: senderUsername,
|
||||
GroupSubject: isRoom ? (roomName ?? roomId) : undefined,
|
||||
GroupChannel: isRoom ? (roomInfo.canonicalAlias ?? roomId) : undefined,
|
||||
GroupSystemPrompt: isRoom ? groupSystemPrompt : undefined,
|
||||
|
||||
55
extensions/matrix/src/matrix/monitor/inbound-body.test.ts
Normal file
55
extensions/matrix/src/matrix/monitor/inbound-body.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveMatrixBodyForAgent, resolveMatrixInboundSenderLabel } from "./inbound-body.js";
|
||||
|
||||
describe("resolveMatrixInboundSenderLabel", () => {
|
||||
it("includes sender username when it differs from display name", () => {
|
||||
expect(
|
||||
resolveMatrixInboundSenderLabel({
|
||||
senderName: "Bu",
|
||||
senderId: "@bu:matrix.example.org",
|
||||
}),
|
||||
).toBe("Bu (bu)");
|
||||
});
|
||||
|
||||
it("falls back to sender username when display name is blank", () => {
|
||||
expect(
|
||||
resolveMatrixInboundSenderLabel({
|
||||
senderName: " ",
|
||||
senderId: "@zhang:matrix.example.org",
|
||||
}),
|
||||
).toBe("zhang");
|
||||
});
|
||||
|
||||
it("falls back to sender id when username cannot be parsed", () => {
|
||||
expect(
|
||||
resolveMatrixInboundSenderLabel({
|
||||
senderName: "",
|
||||
senderId: "matrix-user-without-colon",
|
||||
}),
|
||||
).toBe("matrix-user-without-colon");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMatrixBodyForAgent", () => {
|
||||
it("keeps direct message body unchanged", () => {
|
||||
expect(
|
||||
resolveMatrixBodyForAgent({
|
||||
isDirectMessage: true,
|
||||
bodyText: "show me my commits",
|
||||
senderName: "Bu",
|
||||
senderId: "@bu:matrix.example.org",
|
||||
}),
|
||||
).toBe("show me my commits");
|
||||
});
|
||||
|
||||
it("prefixes non-direct message body with sender label", () => {
|
||||
expect(
|
||||
resolveMatrixBodyForAgent({
|
||||
isDirectMessage: false,
|
||||
bodyText: "show me my commits",
|
||||
senderName: "Bu",
|
||||
senderId: "@bu:matrix.example.org",
|
||||
}),
|
||||
).toBe("Bu (bu): show me my commits");
|
||||
});
|
||||
});
|
||||
32
extensions/matrix/src/matrix/monitor/inbound-body.ts
Normal file
32
extensions/matrix/src/matrix/monitor/inbound-body.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
function resolveMatrixSenderUsername(senderId: string): string | undefined {
|
||||
const username = senderId.split(":")[0]?.replace(/^@/, "").trim();
|
||||
return username ? username : undefined;
|
||||
}
|
||||
|
||||
export function resolveMatrixInboundSenderLabel(params: {
|
||||
senderName: string;
|
||||
senderId: string;
|
||||
}): string {
|
||||
const senderName = params.senderName.trim();
|
||||
const senderUsername = resolveMatrixSenderUsername(params.senderId);
|
||||
if (senderName && senderUsername && senderName !== senderUsername) {
|
||||
return `${senderName} (${senderUsername})`;
|
||||
}
|
||||
return senderName || senderUsername || params.senderId;
|
||||
}
|
||||
|
||||
export function resolveMatrixBodyForAgent(params: {
|
||||
isDirectMessage: boolean;
|
||||
bodyText: string;
|
||||
senderName: string;
|
||||
senderId: string;
|
||||
}): string {
|
||||
if (params.isDirectMessage) {
|
||||
return params.bodyText;
|
||||
}
|
||||
const senderLabel = resolveMatrixInboundSenderLabel({
|
||||
senderName: params.senderName,
|
||||
senderId: params.senderId,
|
||||
});
|
||||
return `${senderLabel}: ${params.bodyText}`;
|
||||
}
|
||||
Reference in New Issue
Block a user