matrix-js: improve thread context and auto-threading

This commit is contained in:
Gustavo Madeira Santana
2026-03-08 16:36:14 -04:00
parent fcba58cff2
commit 882828941f
9 changed files with 624 additions and 2 deletions

View File

@@ -370,6 +370,12 @@ export type ChannelThreadingToolContext = {
currentChannelProvider?: ChannelId;
currentThreadTs?: string;
currentMessageId?: string | number;
/**
* Optional direct-chat participant identifier for channels whose outbound
* tool targets can address either the backing conversation id or the direct
* participant id.
*/
currentDirectUserId?: string;
replyToMode?: "off" | "first" | "all";
hasRepliedRef?: { value: boolean };
/**

View File

@@ -1,4 +1,15 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { matrixPlugin } from "../../../extensions/matrix-js/src/channel.js";
import { setMatrixRuntime } from "../../../extensions/matrix-js/src/runtime.js";
import { slackPlugin } from "../../../extensions/slack/src/channel.js";
import { setSlackRuntime } from "../../../extensions/slack/src/runtime.js";
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
import { setTelegramRuntime } from "../../../extensions/telegram/src/runtime.js";
import type { OpenClawConfig } from "../../config/config.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { createPluginRuntime } from "../../plugins/runtime/index.js";
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
const mocks = vi.hoisted(() => ({
executeSendAction: vi.fn(),
recordSessionMetaFromInbound: vi.fn(async () => ({ ok: true })),
@@ -34,6 +45,15 @@ let resetMessageActionRunnerTestRegistry: MessageActionRunnerTestHelpersModule["
let slackConfig: MessageActionRunnerTestHelpersModule["slackConfig"];
let telegramConfig: MessageActionRunnerTestHelpersModule["telegramConfig"];
const matrixConfig = {
channels: {
"matrix-js": {
homeserver: "https://matrix.example.org",
accessToken: "matrix-test",
},
},
} as OpenClawConfig;
async function runThreadingAction(params: {
cfg: MessageActionRunnerTestHelpersModule["slackConfig"];
actionParams: Record<string, unknown>;
@@ -65,7 +85,25 @@ const defaultTelegramToolContext = {
currentThreadTs: "42",
} as const;
const defaultMatrixToolContext = {
currentChannelId: "room:!room:example.org",
currentThreadTs: "$thread",
} as const;
const defaultMatrixDmToolContext = {
currentChannelId: "room:!dm:example.org",
currentThreadTs: "$thread",
currentDirectUserId: "@alice:example.org",
} as const;
describe("runMessageAction threading auto-injection", () => {
beforeAll(() => {
const runtime = createPluginRuntime();
setMatrixRuntime(runtime);
setSlackRuntime(runtime);
setTelegramRuntime(runtime);
});
beforeEach(async () => {
vi.resetModules();
({ runMessageAction } = await import("./message-action-runner.js"));
@@ -76,6 +114,25 @@ describe("runMessageAction threading auto-injection", () => {
telegramConfig,
} = await import("./message-action-runner.test-helpers.js"));
installMessageActionRunnerTestRegistry();
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "matrix-js",
source: "test",
plugin: matrixPlugin,
},
{
pluginId: "slack",
source: "test",
plugin: slackPlugin,
},
{
pluginId: "telegram",
source: "test",
plugin: telegramPlugin,
},
]),
);
});
afterEach(() => {
@@ -188,4 +245,96 @@ describe("runMessageAction threading auto-injection", () => {
expect(call?.replyToId).toBe("777");
expect(call?.ctx?.params?.replyTo).toBe("777");
});
it.each([
{
name: "injects threadId for bare room id",
target: "!room:example.org",
expectedThreadId: "$thread",
},
{
name: "injects threadId for room target prefix",
target: "room:!room:example.org",
expectedThreadId: "$thread",
},
{
name: "injects threadId for matrix room target",
target: "matrix:room:!room:example.org",
expectedThreadId: "$thread",
},
{
name: "skips threadId when target room differs",
target: "!other:example.org",
expectedThreadId: undefined,
},
] as const)("matrix auto-threading: $name", async (testCase) => {
mockHandledSendAction();
const call = await runThreadingAction({
cfg: matrixConfig,
actionParams: {
channel: "matrix-js",
target: testCase.target,
message: "hi",
},
toolContext: defaultMatrixToolContext,
});
expect(call?.ctx?.params?.threadId).toBe(testCase.expectedThreadId);
if (testCase.expectedThreadId !== undefined) {
expect(call?.threadId).toBe(testCase.expectedThreadId);
}
});
it("uses explicit matrix threadId when provided", async () => {
mockHandledSendAction();
const call = await runThreadingAction({
cfg: matrixConfig,
actionParams: {
channel: "matrix-js",
target: "room:!room:example.org",
message: "hi",
threadId: "$explicit",
},
toolContext: defaultMatrixToolContext,
});
expect(call?.threadId).toBe("$explicit");
expect(call?.ctx?.params?.threadId).toBe("$explicit");
});
it("injects threadId for matching Matrix dm user target", async () => {
mockHandledSendAction();
const call = await runThreadingAction({
cfg: matrixConfig,
actionParams: {
channel: "matrix-js",
target: "user:@alice:example.org",
message: "hi",
},
toolContext: defaultMatrixDmToolContext,
});
expect(call?.threadId).toBe("$thread");
expect(call?.ctx?.params?.threadId).toBe("$thread");
});
it("skips threadId for different Matrix dm user target", async () => {
mockHandledSendAction();
const call = await runThreadingAction({
cfg: matrixConfig,
actionParams: {
channel: "matrix-js",
target: "user:@bob:example.org",
message: "hi",
},
toolContext: defaultMatrixDmToolContext,
});
expect(call?.threadId).toBeUndefined();
expect(call?.ctx?.params?.threadId).toBeUndefined();
});
});