mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 17:34:45 +00:00
fix: restore matrix per-room dm discovery
This commit is contained in:
committed by
Peter Steinberger
parent
b90f28e895
commit
166b42a40f
@@ -406,6 +406,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Codex app-server: keep native hook relays alive for long-running turns so shell and file approvals stay reachable until the configured run window finishes. (#77533) Thanks @rubencu.
|
||||
- Gateway/macOS: clear ignored SIGUSR1 restart state, skip redundant package-update restarts when the refreshed LaunchAgent already serves the expected version, and give launchd a 10s throttle plus 20s shutdown window so update restarts do not leave old gateways alive or fight supervisor recovery. Fixes #79577; refs #78699 and #60885. Thanks @BunsDev.
|
||||
- Status/Codex: route Codex-harness `openai/*` usage through the OpenAI Codex quota provider and scope CLI status usage to the default agent auth store so `/status` and `openclaw status --usage` show Codex quota windows again. Fixes #79312. Thanks @keshavbotagent.
|
||||
- Matrix: keep joined strict DM rooms discoverable when stale `m.direct` mappings already point at an older strict room, and let `dm.sessionScope: "per-room"` promote safe unmapped strict rooms through the existing unnamed/unaliased room gate. Fixes #79514. Thanks @stainlu.
|
||||
- Gateway/agent: pass the session-key agent id into inline image attachment validation so the first image in a fresh per-agent session uses the agent's vision-capable model override instead of the text-only system default. Fixes #79407. Thanks @pandadev66.
|
||||
- Gateway/maintenance: prune dedupe overflow against a stable excess count and keep active agent retries from starting duplicate runs after cache eviction. (#73841) Thanks @thesomewhatyou.
|
||||
- Control UI/subagents: suppress internal `subagent_announce` handoff prompts from requester transcripts and hide legacy inter-session wrapper rows so completed subagent results no longer surface runtime context in WebChat history. (#79618) Thanks @joshavant.
|
||||
|
||||
@@ -47,6 +47,24 @@ describe("inspectMatrixDirectRooms", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("still surfaces joined strict rooms when an older mapped room is strict", async () => {
|
||||
const client = createClient({
|
||||
getAccountData: vi.fn(async () => ({
|
||||
"@alice:example.org": ["!older:example.org"],
|
||||
})),
|
||||
getJoinedRooms: vi.fn(async () => ["!older:example.org", "!fresh:example.org"]),
|
||||
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
||||
});
|
||||
|
||||
const result = await inspectMatrixDirectRooms({
|
||||
client,
|
||||
remoteUserId: "@alice:example.org",
|
||||
});
|
||||
|
||||
expect(result.activeRoomId).toBe("!older:example.org");
|
||||
expect(result.discoveredStrictRoomIds).toEqual(["!fresh:example.org"]);
|
||||
});
|
||||
|
||||
it("falls back to discovered strict joined rooms when m.direct is stale", async () => {
|
||||
const client = createClient({
|
||||
getAccountData: vi.fn(async () => ({
|
||||
|
||||
@@ -277,7 +277,7 @@ export async function inspectMatrixDirectRooms(params: {
|
||||
const mappedStrict = mappedRooms.find((room) => room.strict);
|
||||
|
||||
let joinedRooms: string[] = [];
|
||||
if (!mappedStrict && typeof params.client.getJoinedRooms === "function") {
|
||||
if (typeof params.client.getJoinedRooms === "function") {
|
||||
try {
|
||||
const resolved = await params.client.getJoinedRooms();
|
||||
joinedRooms = Array.isArray(resolved) ? resolved : [];
|
||||
|
||||
@@ -128,6 +128,43 @@ describe("createDirectRoomTracker", () => {
|
||||
expect(client.getJoinedRoomMembers).toHaveBeenCalledWith("!room:example.org");
|
||||
});
|
||||
|
||||
it("promotes strict unmapped rooms when the per-room fallback gate allows it", async () => {
|
||||
const client = createMockClient({ isDm: false, dmCacheAvailable: true });
|
||||
const tracker = createDirectRoomTracker(client, {
|
||||
canPromoteUnmappedStrictRoom: () => true,
|
||||
});
|
||||
|
||||
await expect(
|
||||
tracker.isDirectMessage({
|
||||
roomId: "!room:example.org",
|
||||
senderId: "@alice:example.org",
|
||||
}),
|
||||
).resolves.toBe(true);
|
||||
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(
|
||||
EventType.Direct,
|
||||
expect.objectContaining({
|
||||
"@alice:example.org": ["!room:example.org"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not promote strict unmapped rooms when the per-room fallback gate vetoes it", async () => {
|
||||
const client = createMockClient({ isDm: false, dmCacheAvailable: true });
|
||||
const tracker = createDirectRoomTracker(client, {
|
||||
canPromoteUnmappedStrictRoom: () => false,
|
||||
});
|
||||
|
||||
await expect(
|
||||
tracker.isDirectMessage({
|
||||
roomId: "!room:example.org",
|
||||
senderId: "@alice:example.org",
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
|
||||
expect(client.setAccountData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to strict 2-member membership before m.direct account data is available", async () => {
|
||||
const client = createMockClient({ isDm: false, dmCacheAvailable: false });
|
||||
const tracker = createDirectRoomTracker(client);
|
||||
|
||||
@@ -15,6 +15,7 @@ type DirectMessageCheck = {
|
||||
type DirectRoomTrackerOptions = {
|
||||
log?: (message: string) => void;
|
||||
canPromoteRecentInvite?: (roomId: string) => boolean | Promise<boolean>;
|
||||
canPromoteUnmappedStrictRoom?: (roomId: string) => boolean | Promise<boolean>;
|
||||
shouldKeepLocallyPromotedDirectRoom?:
|
||||
| ((roomId: string) => boolean | undefined | Promise<boolean | undefined>)
|
||||
| undefined;
|
||||
@@ -141,6 +142,15 @@ export function createDirectRoomTracker(client: MatrixClient, opts: DirectRoomTr
|
||||
}
|
||||
};
|
||||
|
||||
const canPromoteUnmappedStrictRoom = async (roomId: string): Promise<boolean> => {
|
||||
try {
|
||||
return (await opts.canPromoteUnmappedStrictRoom?.(roomId)) ?? false;
|
||||
} catch (err) {
|
||||
log(`matrix: unmapped strict room promotion veto failed room=${roomId} (${String(err)})`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const shouldKeepLocallyPromotedDirectRoom = async (
|
||||
roomId: string,
|
||||
): Promise<boolean | undefined> => {
|
||||
@@ -259,6 +269,22 @@ export function createDirectRoomTracker(client: MatrixClient, opts: DirectRoomTr
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (await canPromoteUnmappedStrictRoom(roomId)) {
|
||||
const promotion = await promoteMatrixDirectRoomCandidate({
|
||||
client,
|
||||
remoteUserId: senderId ?? "",
|
||||
roomId,
|
||||
selfUserId,
|
||||
});
|
||||
if (promotion.classifyAsDirect) {
|
||||
rememberLocallyPromotedDirectRoom(roomId, senderId ?? "");
|
||||
log(
|
||||
`matrix: dm detected via per-room strict fallback room=${roomId} reason=${promotion.reason} repaired=${String(promotion.repaired)}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { MatrixRoomInfo } from "./room-info.js";
|
||||
|
||||
type DirectRoomTrackerOptions = {
|
||||
canPromoteRecentInvite?: (roomId: string) => boolean | Promise<boolean>;
|
||||
canPromoteUnmappedStrictRoom?: (roomId: string) => boolean | Promise<boolean>;
|
||||
shouldKeepLocallyPromotedDirectRoom?:
|
||||
| ((roomId: string) => boolean | undefined | Promise<boolean | undefined>)
|
||||
| undefined;
|
||||
@@ -981,6 +982,40 @@ describe("monitorMatrixProvider", () => {
|
||||
await expect(trackerOpts.canPromoteRecentInvite("!room:example.org")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("does not wire unmapped strict room promotion for per-user DM scope", async () => {
|
||||
await startMonitorAndAbortAfterStartup();
|
||||
|
||||
const trackerOpts = hoisted.createDirectRoomTracker.mock.calls[0]?.[1];
|
||||
|
||||
expect(trackerOpts?.canPromoteUnmappedStrictRoom).toBeUndefined();
|
||||
});
|
||||
|
||||
it("wires per-room unmapped strict room promotion through the room metadata gate", async () => {
|
||||
hoisted.accountConfig.dm = { sessionScope: "per-room" };
|
||||
|
||||
await startMonitorAndAbortAfterStartup();
|
||||
|
||||
const trackerOpts = hoisted.createDirectRoomTracker.mock.calls[0]?.[1];
|
||||
if (!trackerOpts?.canPromoteUnmappedStrictRoom) {
|
||||
throw new Error("per-room strict fallback callback was not wired");
|
||||
}
|
||||
|
||||
hoisted.getRoomInfo.mockResolvedValueOnce({
|
||||
altAliases: [],
|
||||
nameResolved: true,
|
||||
aliasesResolved: true,
|
||||
});
|
||||
await expect(trackerOpts.canPromoteUnmappedStrictRoom("!dm:example.org")).resolves.toBe(true);
|
||||
|
||||
hoisted.getRoomInfo.mockResolvedValueOnce({
|
||||
name: "Ops Room",
|
||||
altAliases: [],
|
||||
nameResolved: true,
|
||||
aliasesResolved: true,
|
||||
});
|
||||
await expect(trackerOpts.canPromoteUnmappedStrictRoom("!ops:example.org")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("treats unresolved room metadata as indeterminate for local promotion revalidation", async () => {
|
||||
await startMonitorAndAbortAfterStartup();
|
||||
|
||||
|
||||
@@ -353,6 +353,16 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
roomInfo: await getRoomInfo(roomId, { includeAliases: true }),
|
||||
rooms: roomsConfig,
|
||||
}),
|
||||
...(dmSessionScope === "per-room"
|
||||
? {
|
||||
canPromoteUnmappedStrictRoom: async (roomId) =>
|
||||
shouldPromoteRecentInviteRoom({
|
||||
roomId,
|
||||
roomInfo: await getRoomInfo(roomId, { includeAliases: true }),
|
||||
rooms: roomsConfig,
|
||||
}),
|
||||
}
|
||||
: {}),
|
||||
shouldKeepLocallyPromotedDirectRoom: async (roomId) => {
|
||||
try {
|
||||
const roomInfo = await getRoomInfo(roomId, { includeAliases: true });
|
||||
|
||||
Reference in New Issue
Block a user