mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 19:20:43 +00:00
fix(channels): thread runtime config through sends
This commit is contained in:
@@ -30,7 +30,7 @@ type ProbeMatrix = (params: {
|
||||
type SendMessageMatrix = (
|
||||
to: string,
|
||||
message: string,
|
||||
options?: { accountId?: string },
|
||||
options: { cfg: CoreConfig; accountId?: string },
|
||||
) => Promise<unknown>;
|
||||
|
||||
export function createMatrixProbeAccount(params: {
|
||||
@@ -80,13 +80,18 @@ export function createMatrixPairingText(sendMessageMatrix: SendMessageMatrix) {
|
||||
notify: async ({
|
||||
id,
|
||||
message,
|
||||
cfg,
|
||||
accountId,
|
||||
}: {
|
||||
id: string;
|
||||
message: string;
|
||||
cfg: CoreConfig;
|
||||
accountId?: string;
|
||||
}) => {
|
||||
await sendMessageMatrix(`user:${id}`, message, accountId ? { accountId } : {});
|
||||
await sendMessageMatrix(`user:${id}`, message, {
|
||||
cfg,
|
||||
...(accountId ? { accountId } : {}),
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ describe("matrix account path propagation", () => {
|
||||
);
|
||||
|
||||
await pairingText.notify({
|
||||
cfg: {} as never,
|
||||
id: "@user:example.org",
|
||||
message: pairingText.message,
|
||||
accountId: "poe",
|
||||
@@ -68,7 +69,7 @@ describe("matrix account path propagation", () => {
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledWith(
|
||||
"user:@user:example.org",
|
||||
expect.any(String),
|
||||
{ accountId: "poe" },
|
||||
{ cfg: {}, accountId: "poe" },
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ const {
|
||||
resolveMatrixAuthContextMock,
|
||||
} = matrixClientResolverMocks;
|
||||
|
||||
const TEST_CFG = {};
|
||||
|
||||
vi.mock("../../runtime.js", () => ({
|
||||
getMatrixRuntime: () => getMatrixRuntimeMock(),
|
||||
}));
|
||||
@@ -65,14 +67,20 @@ describe("action client helpers", () => {
|
||||
it("stops one-off shared clients when no active monitor client is registered", async () => {
|
||||
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "18799");
|
||||
|
||||
const result = await withResolvedActionClient({ accountId: "default" }, async () => "ok");
|
||||
const result = await withResolvedActionClient(
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async () => "ok",
|
||||
);
|
||||
|
||||
await expectOneOffSharedMatrixClient();
|
||||
expect(result).toBe("ok");
|
||||
});
|
||||
|
||||
it("skips one-off room preparation when readiness is disabled", async () => {
|
||||
await withResolvedActionClient({ accountId: "default", readiness: "none" }, async () => {});
|
||||
await withResolvedActionClient(
|
||||
{ cfg: TEST_CFG, accountId: "default", readiness: "none" },
|
||||
async () => {},
|
||||
);
|
||||
|
||||
const sharedClient = await acquireSharedMatrixClientMock.mock.results[0]?.value;
|
||||
expect(sharedClient.prepareForOneOff).not.toHaveBeenCalled();
|
||||
@@ -81,7 +89,7 @@ describe("action client helpers", () => {
|
||||
});
|
||||
|
||||
it("starts one-off clients when started readiness is required", async () => {
|
||||
await withStartedActionClient({ accountId: "default" }, async () => {});
|
||||
await withStartedActionClient({ cfg: TEST_CFG, accountId: "default" }, async () => {});
|
||||
|
||||
const sharedClient = await acquireSharedMatrixClientMock.mock.results[0]?.value;
|
||||
expect(sharedClient.start).toHaveBeenCalledTimes(1);
|
||||
@@ -93,10 +101,13 @@ describe("action client helpers", () => {
|
||||
const activeClient = createMockMatrixClient();
|
||||
getActiveMatrixClientMock.mockReturnValue(activeClient);
|
||||
|
||||
const result = await withResolvedActionClient({ accountId: "default" }, async (client) => {
|
||||
expect(client).toBe(activeClient);
|
||||
return "ok";
|
||||
});
|
||||
const result = await withResolvedActionClient(
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async (client) => {
|
||||
expect(client).toBe(activeClient);
|
||||
return "ok";
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toBe("ok");
|
||||
expect(acquireSharedMatrixClientMock).not.toHaveBeenCalled();
|
||||
@@ -107,7 +118,7 @@ describe("action client helpers", () => {
|
||||
const activeClient = createMockMatrixClient();
|
||||
getActiveMatrixClientMock.mockReturnValue(activeClient);
|
||||
|
||||
await withStartedActionClient({ accountId: "default" }, async (client) => {
|
||||
await withStartedActionClient({ cfg: TEST_CFG, accountId: "default" }, async (client) => {
|
||||
expect(client).toBe(activeClient);
|
||||
});
|
||||
|
||||
@@ -143,7 +154,7 @@ describe("action client helpers", () => {
|
||||
encryption: true,
|
||||
},
|
||||
});
|
||||
await withResolvedActionClient({}, async () => {});
|
||||
await withResolvedActionClient({ cfg: loadConfigMock() as never }, async () => {});
|
||||
|
||||
await expectOneOffSharedMatrixClient({
|
||||
cfg: loadConfigMock(),
|
||||
@@ -172,10 +183,13 @@ describe("action client helpers", () => {
|
||||
const sharedClient = createMockMatrixClient();
|
||||
acquireSharedMatrixClientMock.mockResolvedValue(sharedClient);
|
||||
|
||||
const result = await withResolvedActionClient({ accountId: "default" }, async (client) => {
|
||||
expect(client).toBe(sharedClient);
|
||||
return "ok";
|
||||
});
|
||||
const result = await withResolvedActionClient(
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async (client) => {
|
||||
expect(client).toBe(sharedClient);
|
||||
return "ok";
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toBe("ok");
|
||||
expect(releaseSharedClientInstanceMock).toHaveBeenCalledWith(sharedClient, "stop");
|
||||
@@ -186,7 +200,7 @@ describe("action client helpers", () => {
|
||||
acquireSharedMatrixClientMock.mockResolvedValue(sharedClient);
|
||||
|
||||
await expect(
|
||||
withResolvedActionClient({ accountId: "default" }, async () => {
|
||||
withResolvedActionClient({ cfg: TEST_CFG, accountId: "default" }, async () => {
|
||||
throw new Error("boom");
|
||||
}),
|
||||
).rejects.toThrow("boom");
|
||||
@@ -201,7 +215,7 @@ describe("action client helpers", () => {
|
||||
|
||||
const result = await withResolvedRoomAction(
|
||||
"room:#ops:example.org",
|
||||
{ accountId: "default" },
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async (client, resolvedRoom) => {
|
||||
expect(client).toBe(sharedClient);
|
||||
return resolvedRoom;
|
||||
|
||||
@@ -4,6 +4,12 @@ import type { MatrixClient } from "../sdk.js";
|
||||
import * as sendModule from "../send.js";
|
||||
import { editMatrixMessage, readMatrixMessages } from "./messages.js";
|
||||
|
||||
const MATRIX_ACTION_TEST_CFG = {
|
||||
channels: {
|
||||
matrix: {},
|
||||
},
|
||||
};
|
||||
|
||||
function installMatrixActionTestRuntime(): void {
|
||||
setMatrixRuntime({
|
||||
config: {
|
||||
@@ -110,13 +116,15 @@ describe("matrix message actions", () => {
|
||||
const editSpy = vi.spyOn(sendModule, "editMessageMatrix").mockResolvedValue("evt-edit");
|
||||
|
||||
try {
|
||||
const cfg = {} as never;
|
||||
const result = await editMatrixMessage("!room:example.org", "$original", "hello", {
|
||||
cfg,
|
||||
timeoutMs: 12_345,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ eventId: "evt-edit" });
|
||||
expect(editSpy).toHaveBeenCalledWith("!room:example.org", "$original", "hello", {
|
||||
cfg: undefined,
|
||||
cfg,
|
||||
accountId: undefined,
|
||||
client: undefined,
|
||||
timeoutMs: 12_345,
|
||||
@@ -137,7 +145,7 @@ describe("matrix message actions", () => {
|
||||
"!room:example.org",
|
||||
"$original",
|
||||
"hello @alice:example.org and @bob:example.org",
|
||||
{ client },
|
||||
{ cfg: MATRIX_ACTION_TEST_CFG, client },
|
||||
);
|
||||
|
||||
expect(result).toEqual({ eventId: "evt-edit" });
|
||||
@@ -162,7 +170,7 @@ describe("matrix message actions", () => {
|
||||
"!room:example.org",
|
||||
"$original",
|
||||
"hello again @alice:example.org",
|
||||
{ client },
|
||||
{ cfg: MATRIX_ACTION_TEST_CFG, client },
|
||||
);
|
||||
|
||||
expect(result).toEqual({ eventId: "evt-edit" });
|
||||
|
||||
@@ -22,6 +22,9 @@ export async function sendMatrixMessage(
|
||||
audioAsVoice?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
if (!opts.cfg) {
|
||||
throw new Error("Matrix message actions require a resolved runtime config.");
|
||||
}
|
||||
return await sendMessageMatrix(to, content, {
|
||||
cfg: opts.cfg,
|
||||
mediaUrl: opts.mediaUrl,
|
||||
@@ -41,6 +44,9 @@ export async function editMatrixMessage(
|
||||
content: string,
|
||||
opts: MatrixActionClientOpts = {},
|
||||
) {
|
||||
if (!opts.cfg) {
|
||||
throw new Error("Matrix message actions require a resolved runtime config.");
|
||||
}
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error("Matrix edit requires content");
|
||||
|
||||
@@ -16,6 +16,16 @@ vi.mock("../../runtime.js", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
||||
"openclaw/plugin-sdk/config-runtime",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
requireRuntimeConfig: vi.fn((cfg: unknown) => cfg ?? loadConfigMock()),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
withResolvedActionClient: (...args: unknown[]) => withResolvedActionClientMock(...args),
|
||||
withStartedActionClient: (...args: unknown[]) => withStartedActionClientMock(...args),
|
||||
@@ -61,7 +71,9 @@ describe("matrix verification actions", () => {
|
||||
return await run({ crypto: null });
|
||||
});
|
||||
|
||||
await expect(listMatrixVerifications({ accountId: "ops" })).rejects.toThrow(
|
||||
await expect(
|
||||
listMatrixVerifications({ cfg: loadConfigMock(), accountId: "ops" }),
|
||||
).rejects.toThrow(
|
||||
"Matrix encryption is not available (enable channels.matrix.accounts.ops.encryption=true)",
|
||||
);
|
||||
});
|
||||
@@ -83,7 +95,7 @@ describe("matrix verification actions", () => {
|
||||
return await run({ crypto: null });
|
||||
});
|
||||
|
||||
await expect(listMatrixVerifications()).rejects.toThrow(
|
||||
await expect(listMatrixVerifications({ cfg: loadConfigMock() })).rejects.toThrow(
|
||||
"Matrix encryption is not available (enable channels.matrix.accounts.ops.encryption=true)",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import { formatMatrixEncryptionUnavailableError } from "../encryption-guidance.js";
|
||||
import { withResolvedActionClient, withStartedActionClient } from "./client.js";
|
||||
@@ -10,7 +10,12 @@ function requireCrypto(
|
||||
opts: MatrixActionClientOpts,
|
||||
): NonNullable<import("../sdk.js").MatrixClient["crypto"]> {
|
||||
if (!client.crypto) {
|
||||
const cfg = opts.cfg ?? (getMatrixRuntime().config.loadConfig() as CoreConfig);
|
||||
if (!opts.cfg) {
|
||||
throw new Error(
|
||||
"Matrix verification actions requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
|
||||
);
|
||||
}
|
||||
const cfg = requireRuntimeConfig(opts.cfg, "Matrix verification actions") as CoreConfig;
|
||||
throw new Error(formatMatrixEncryptionUnavailableError(cfg, opts.accountId));
|
||||
}
|
||||
return client.crypto;
|
||||
|
||||
@@ -14,6 +14,8 @@ const {
|
||||
resolveMatrixAuthContextMock,
|
||||
} = matrixClientResolverMocks;
|
||||
|
||||
const TEST_CFG = {};
|
||||
|
||||
vi.mock("../runtime.js", () => ({
|
||||
getMatrixRuntime: () => getMatrixRuntimeMock(),
|
||||
}));
|
||||
@@ -56,6 +58,7 @@ describe("client bootstrap", () => {
|
||||
|
||||
await expect(
|
||||
resolveRuntimeMatrixClientWithReadiness({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "default",
|
||||
readiness: "prepared",
|
||||
}),
|
||||
@@ -72,6 +75,7 @@ describe("client bootstrap", () => {
|
||||
await expect(
|
||||
withResolvedRuntimeMatrixClient(
|
||||
{
|
||||
cfg: TEST_CFG,
|
||||
accountId: "default",
|
||||
readiness: "started",
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getMatrixRuntime } from "../runtime.js";
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { CoreConfig } from "../types.js";
|
||||
import { getActiveMatrixClient } from "./active-client.js";
|
||||
import { isBunRuntime } from "./client/runtime.js";
|
||||
@@ -71,7 +71,12 @@ async function resolveRuntimeMatrixClient(opts: {
|
||||
return { client: opts.client, stopOnDone: false };
|
||||
}
|
||||
|
||||
const cfg = opts.cfg ?? (getMatrixRuntime().config.loadConfig() as CoreConfig);
|
||||
if (!opts.cfg) {
|
||||
throw new Error(
|
||||
"Matrix runtime client requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
|
||||
);
|
||||
}
|
||||
const cfg = requireRuntimeConfig(opts.cfg, "Matrix runtime client") as CoreConfig;
|
||||
const { acquireSharedMatrixClient, releaseSharedClientInstance, resolveMatrixAuthContext } =
|
||||
await loadMatrixSharedClientRuntimeDeps();
|
||||
const authContext = resolveMatrixAuthContext({
|
||||
|
||||
@@ -23,6 +23,21 @@ export const matrixClientResolverMocks: MatrixClientResolverMocks = {
|
||||
resolveMatrixAuthContextMock: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
||||
"openclaw/plugin-sdk/config-runtime",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
requireRuntimeConfig: vi.fn((cfg: unknown) => {
|
||||
if (cfg) {
|
||||
return cfg;
|
||||
}
|
||||
return matrixClientResolverMocks.loadConfigMock();
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
export function createMockMatrixClient(): MatrixClient {
|
||||
return {
|
||||
prepareForOneOff: vi.fn(async () => undefined),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { retryAsync } from "openclaw/plugin-sdk/retry-runtime";
|
||||
import {
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
} from "../../account-selection.js";
|
||||
import { resolveMatrixAccountStringValues } from "../../auth-precedence.js";
|
||||
import { getMatrixScopedEnvVarNames } from "../../env-vars.js";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import {
|
||||
findMatrixAccountConfig,
|
||||
@@ -556,8 +556,8 @@ function resolveImplicitMatrixAccountId(
|
||||
return normalizeAccountId(resolveMatrixDefaultOrOnlyAccountId(cfg, env));
|
||||
}
|
||||
|
||||
export function resolveMatrixAuthContext(params?: {
|
||||
cfg?: CoreConfig;
|
||||
export function resolveMatrixAuthContext(params: {
|
||||
cfg: CoreConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
accountId?: string | null;
|
||||
}): {
|
||||
@@ -566,7 +566,7 @@ export function resolveMatrixAuthContext(params?: {
|
||||
accountId: string;
|
||||
resolved: MatrixResolvedConfig;
|
||||
} {
|
||||
const cfg = params?.cfg ?? (getMatrixRuntime().config.loadConfig() as CoreConfig);
|
||||
const cfg = requireRuntimeConfig(params.cfg, "Matrix auth context") as CoreConfig;
|
||||
const env = params?.env ?? process.env;
|
||||
const explicitAccountId = normalizeOptionalAccountId(params?.accountId);
|
||||
const effectiveAccountId = explicitAccountId ?? resolveImplicitMatrixAccountId(cfg, env);
|
||||
@@ -600,7 +600,16 @@ export async function resolveMatrixAuth(params?: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
accountId?: string | null;
|
||||
}): Promise<MatrixAuth> {
|
||||
const { cfg, env, accountId, resolved } = resolveMatrixAuthContext(params);
|
||||
if (!params?.cfg) {
|
||||
throw new Error(
|
||||
"Matrix auth requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
|
||||
);
|
||||
}
|
||||
const { cfg, env, accountId, resolved } = resolveMatrixAuthContext({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const accessToken =
|
||||
(await resolveConfiguredMatrixAuthSecretInput({
|
||||
cfg,
|
||||
|
||||
@@ -5,6 +5,8 @@ const resolveMatrixAuthMock = vi.hoisted(() => vi.fn());
|
||||
const resolveMatrixAuthContextMock = vi.hoisted(() => vi.fn());
|
||||
const createMatrixClientMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
const TEST_CFG = {};
|
||||
|
||||
vi.mock("./config.js", () => ({
|
||||
resolveMatrixAuth: resolveMatrixAuthMock,
|
||||
resolveMatrixAuthContext: resolveMatrixAuthContextMock,
|
||||
@@ -106,7 +108,7 @@ describe("resolveSharedMatrixClient", () => {
|
||||
createMatrixClientMock.mockReset();
|
||||
resolveMatrixAuthContextMock.mockImplementation(
|
||||
({ accountId }: { accountId?: string | null } = {}) => ({
|
||||
cfg: undefined,
|
||||
cfg: TEST_CFG,
|
||||
env: undefined,
|
||||
accountId: accountId ?? "default",
|
||||
resolved: {},
|
||||
@@ -122,9 +124,17 @@ describe("resolveSharedMatrixClient", () => {
|
||||
it("keeps account clients isolated when resolves are interleaved", async () => {
|
||||
const { mainClient, opsClient } = primeAccountClientMocks();
|
||||
|
||||
const firstMain = await resolveSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const firstPoe = await resolveSharedMatrixClient({ accountId: "ops", startClient: false });
|
||||
const secondMain = await resolveSharedMatrixClient({ accountId: "main" });
|
||||
const firstMain = await resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
const firstPoe = await resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "ops",
|
||||
startClient: false,
|
||||
});
|
||||
const secondMain = await resolveSharedMatrixClient({ cfg: TEST_CFG, accountId: "main" });
|
||||
|
||||
expect(firstMain).toBe(mainClient);
|
||||
expect(firstPoe).toBe(opsClient);
|
||||
@@ -137,8 +147,8 @@ describe("resolveSharedMatrixClient", () => {
|
||||
it("stops only the targeted account client", async () => {
|
||||
const { mainAuth, mainClient, opsClient } = primeAccountClientMocks();
|
||||
|
||||
await resolveSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
await resolveSharedMatrixClient({ accountId: "ops", startClient: false });
|
||||
await resolveSharedMatrixClient({ cfg: TEST_CFG, accountId: "main", startClient: false });
|
||||
await resolveSharedMatrixClient({ cfg: TEST_CFG, accountId: "ops", startClient: false });
|
||||
|
||||
stopSharedClientForAccount(mainAuth);
|
||||
|
||||
@@ -160,9 +170,17 @@ describe("resolveSharedMatrixClient", () => {
|
||||
.mockResolvedValueOnce(firstMainClient)
|
||||
.mockResolvedValueOnce(secondMainClient);
|
||||
|
||||
const first = await resolveSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const first = await resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
stopSharedClientInstance(first as unknown as import("../sdk.js").MatrixClient);
|
||||
const second = await resolveSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const second = await resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
|
||||
expect(first).toBe(firstMainClient);
|
||||
expect(second).toBe(secondMainClient);
|
||||
@@ -175,7 +193,7 @@ describe("resolveSharedMatrixClient", () => {
|
||||
const poeClient = createMockClient("ops");
|
||||
|
||||
resolveMatrixAuthContextMock.mockReturnValue({
|
||||
cfg: undefined,
|
||||
cfg: TEST_CFG,
|
||||
env: undefined,
|
||||
accountId: "ops",
|
||||
resolved: {},
|
||||
@@ -183,13 +201,13 @@ describe("resolveSharedMatrixClient", () => {
|
||||
resolveMatrixAuthMock.mockResolvedValue(poeAuth);
|
||||
createMatrixClientMock.mockResolvedValue(poeClient);
|
||||
|
||||
const first = await resolveSharedMatrixClient({ startClient: false });
|
||||
const second = await resolveSharedMatrixClient({ startClient: false });
|
||||
const first = await resolveSharedMatrixClient({ cfg: TEST_CFG, startClient: false });
|
||||
const second = await resolveSharedMatrixClient({ cfg: TEST_CFG, startClient: false });
|
||||
|
||||
expect(first).toBe(poeClient);
|
||||
expect(second).toBe(poeClient);
|
||||
expect(resolveMatrixAuthMock).toHaveBeenCalledWith({
|
||||
cfg: undefined,
|
||||
cfg: TEST_CFG,
|
||||
env: undefined,
|
||||
accountId: "ops",
|
||||
});
|
||||
@@ -208,7 +226,11 @@ describe("resolveSharedMatrixClient", () => {
|
||||
resolveMatrixAuthMock.mockResolvedValue(mainAuth);
|
||||
createMatrixClientMock.mockResolvedValue(mainClient);
|
||||
|
||||
const client = await acquireSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const client = await acquireSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
|
||||
expect(client).toBe(mainClient);
|
||||
expect(mainClient.start).not.toHaveBeenCalled();
|
||||
@@ -224,8 +246,16 @@ describe("resolveSharedMatrixClient", () => {
|
||||
resolveMatrixAuthMock.mockResolvedValue(mainAuth);
|
||||
createMatrixClientMock.mockResolvedValue(mainClient);
|
||||
|
||||
const first = await acquireSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const second = await acquireSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const first = await acquireSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
const second = await acquireSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
|
||||
expect(first).toBe(mainClient);
|
||||
expect(second).toBe(mainClient);
|
||||
@@ -254,13 +284,14 @@ describe("resolveSharedMatrixClient", () => {
|
||||
it("lets a later waiter abort while shared startup continues for the owner", async () => {
|
||||
const { mainClient, resolveStartup } = createPendingSharedStartup();
|
||||
|
||||
const ownerPromise = resolveSharedMatrixClient({ accountId: "main" });
|
||||
const ownerPromise = resolveSharedMatrixClient({ cfg: TEST_CFG, accountId: "main" });
|
||||
await vi.waitFor(() => {
|
||||
expect(mainClient.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const abortController = new AbortController();
|
||||
const canceledWaiter = resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
abortSignal: abortController.signal,
|
||||
});
|
||||
@@ -278,13 +309,14 @@ describe("resolveSharedMatrixClient", () => {
|
||||
it("keeps the shared startup lock while an aborted waiter exits early", async () => {
|
||||
const { mainClient, resolveStartup } = createPendingSharedStartup();
|
||||
|
||||
const ownerPromise = resolveSharedMatrixClient({ accountId: "main" });
|
||||
const ownerPromise = resolveSharedMatrixClient({ cfg: TEST_CFG, accountId: "main" });
|
||||
await vi.waitFor(() => {
|
||||
expect(mainClient.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const abortController = new AbortController();
|
||||
const abortedWaiter = resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
abortSignal: abortController.signal,
|
||||
});
|
||||
@@ -294,7 +326,7 @@ describe("resolveSharedMatrixClient", () => {
|
||||
name: "AbortError",
|
||||
});
|
||||
|
||||
const followerPromise = resolveSharedMatrixClient({ accountId: "main" });
|
||||
const followerPromise = resolveSharedMatrixClient({ cfg: TEST_CFG, accountId: "main" });
|
||||
expect(mainClient.start).toHaveBeenCalledTimes(1);
|
||||
|
||||
resolveStartup();
|
||||
@@ -324,8 +356,16 @@ describe("resolveSharedMatrixClient", () => {
|
||||
resolveMatrixAuthMock.mockResolvedValueOnce(firstAuth).mockResolvedValueOnce(secondAuth);
|
||||
createMatrixClientMock.mockResolvedValueOnce(firstClient).mockResolvedValueOnce(secondClient);
|
||||
|
||||
const first = await resolveSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const second = await resolveSharedMatrixClient({ accountId: "main", startClient: false });
|
||||
const first = await resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
const second = await resolveSharedMatrixClient({
|
||||
cfg: TEST_CFG,
|
||||
accountId: "main",
|
||||
startClient: false,
|
||||
});
|
||||
|
||||
expect(first).toBe(firstClient);
|
||||
expect(second).toBe(secondClient);
|
||||
|
||||
@@ -155,13 +155,21 @@ async function resolveSharedMatrixClientState(
|
||||
`Matrix shared client account mismatch: requested ${requestedAccountId}, auth resolved ${params.auth.accountId}`,
|
||||
);
|
||||
}
|
||||
const authContext = params.auth
|
||||
? null
|
||||
: resolveMatrixAuthContext({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const authContext = (() => {
|
||||
if (params.auth) {
|
||||
return null;
|
||||
}
|
||||
if (!params.cfg) {
|
||||
throw new Error(
|
||||
"Matrix shared client requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
|
||||
);
|
||||
}
|
||||
return resolveMatrixAuthContext({
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
})();
|
||||
const auth =
|
||||
params.auth ??
|
||||
(await resolveMatrixAuth({
|
||||
|
||||
@@ -356,6 +356,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
needsRoomAliasesForConfig,
|
||||
});
|
||||
threadBindingManager = await createMatrixThreadBindingManager({
|
||||
cfg,
|
||||
accountId: effectiveAccountId,
|
||||
auth,
|
||||
client,
|
||||
|
||||
@@ -33,6 +33,16 @@ const resolveMarkdownTableModeMock = vi.fn(() => "code");
|
||||
const convertMarkdownTablesMock = vi.fn((text: string) => text);
|
||||
const chunkMarkdownTextWithModeMock = vi.fn((text: string) => (text ? [text] : []));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
||||
"openclaw/plugin-sdk/config-runtime",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
requireRuntimeConfig: vi.fn((cfg: unknown) => cfg ?? loadConfigMock()),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./outbound-media-runtime.js", () => ({
|
||||
loadOutboundMediaFromUrl: loadOutboundMediaFromUrlMock,
|
||||
}));
|
||||
@@ -179,6 +189,7 @@ describe("sendMessageMatrix media", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "caption", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/photo.png",
|
||||
});
|
||||
|
||||
@@ -202,6 +213,7 @@ describe("sendMessageMatrix media", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "caption", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/photo.png",
|
||||
});
|
||||
|
||||
@@ -246,6 +258,7 @@ describe("sendMessageMatrix media", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "caption", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/photo.png",
|
||||
});
|
||||
|
||||
@@ -279,6 +292,7 @@ describe("sendMessageMatrix media", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "voice caption", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/clip.mp3",
|
||||
audioAsVoice: true,
|
||||
replyToId: "$reply",
|
||||
@@ -310,6 +324,7 @@ describe("sendMessageMatrix media", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "voice caption", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/clip.wav",
|
||||
audioAsVoice: true,
|
||||
});
|
||||
@@ -334,6 +349,7 @@ describe("sendMessageMatrix media", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "caption", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/photo.png",
|
||||
});
|
||||
|
||||
@@ -401,6 +417,7 @@ describe("sendMessageMatrix media", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "caption", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/photo.png",
|
||||
mediaLocalRoots: ["/tmp/openclaw-matrix-test"],
|
||||
});
|
||||
@@ -426,6 +443,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "hello", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -439,6 +457,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "hello @alice:example.org", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -455,6 +474,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "hello @alice", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -470,6 +490,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "\\@alice:example.org", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -485,6 +506,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "\\@room please review", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -497,6 +519,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "@room please review", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -509,6 +532,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "caption @alice:example.org", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/photo.png",
|
||||
});
|
||||
|
||||
@@ -528,6 +552,7 @@ describe("sendMessageMatrix mentions", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
mediaUrl: "file:///tmp/room.png",
|
||||
});
|
||||
|
||||
@@ -552,6 +577,7 @@ describe("sendMessageMatrix threads", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "hello thread", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
threadId: "$thread",
|
||||
});
|
||||
|
||||
@@ -575,6 +601,7 @@ describe("sendMessageMatrix threads", () => {
|
||||
|
||||
await sendMessageMatrix("room:!room:example", "hello", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
accountId: "ops",
|
||||
});
|
||||
|
||||
@@ -593,6 +620,7 @@ describe("sendMessageMatrix threads", () => {
|
||||
|
||||
const result = await sendMessageMatrix("room:!room:example", "ignored", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
@@ -618,6 +646,7 @@ describe("sendSingleTextMessageMatrix", () => {
|
||||
await expect(
|
||||
sendSingleTextMessageMatrix("room:!room:example", "1234", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
}),
|
||||
).rejects.toThrow("Matrix single-message text exceeds limit");
|
||||
|
||||
@@ -629,6 +658,7 @@ describe("sendSingleTextMessageMatrix", () => {
|
||||
|
||||
await sendSingleTextMessageMatrix("room:!room:example", "@room hi @alice:example.org", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
msgtype: "m.notice",
|
||||
includeMentions: false,
|
||||
});
|
||||
@@ -648,6 +678,7 @@ describe("sendSingleTextMessageMatrix", () => {
|
||||
|
||||
await sendSingleTextMessageMatrix("room:!room:example", "done", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
extraContent: { [MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY]: true },
|
||||
});
|
||||
|
||||
@@ -679,6 +710,7 @@ describe("editMessageMatrix mentions", () => {
|
||||
"hello @alice:example.org and @bob:example.org",
|
||||
{
|
||||
client,
|
||||
cfg: {} as never,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -700,6 +732,7 @@ describe("editMessageMatrix mentions", () => {
|
||||
|
||||
await editMessageMatrix("room:!room:example", "$original", "hello again @alice:example.org", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -722,6 +755,7 @@ describe("editMessageMatrix mentions", () => {
|
||||
|
||||
await editMessageMatrix("room:!room:example", "$original", "@alice:example.org", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
});
|
||||
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toMatchObject({
|
||||
@@ -743,6 +777,7 @@ describe("editMessageMatrix mentions", () => {
|
||||
|
||||
await editMessageMatrix("room:!room:example", "$original", "@room hi @alice:example.org", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
msgtype: "m.notice",
|
||||
includeMentions: false,
|
||||
});
|
||||
@@ -772,6 +807,7 @@ describe("editMessageMatrix mentions", () => {
|
||||
|
||||
await editMessageMatrix("room:!room:example", "$original", "done", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
extraContent: { [MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY]: true },
|
||||
});
|
||||
|
||||
@@ -801,6 +837,7 @@ describe("sendPollMatrix mentions", () => {
|
||||
},
|
||||
{
|
||||
client,
|
||||
cfg: {} as never,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -841,6 +878,7 @@ describe("voteMatrixPoll", () => {
|
||||
|
||||
const result = await voteMatrixPoll("room:!room:example", "$poll", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
optionIndex: 2,
|
||||
});
|
||||
|
||||
@@ -877,6 +915,7 @@ describe("voteMatrixPoll", () => {
|
||||
await expect(
|
||||
voteMatrixPoll("room:!room:example", "$poll", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
optionIndex: 2,
|
||||
}),
|
||||
).rejects.toThrow("out of range");
|
||||
@@ -901,6 +940,7 @@ describe("voteMatrixPoll", () => {
|
||||
await expect(
|
||||
voteMatrixPoll("room:!room:example", "$poll", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
optionIndexes: [1, 2],
|
||||
}),
|
||||
).rejects.toThrow("at most 1 selection");
|
||||
@@ -916,6 +956,7 @@ describe("voteMatrixPoll", () => {
|
||||
await expect(
|
||||
voteMatrixPoll("room:!room:example", "$poll", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
optionIndex: 1,
|
||||
}),
|
||||
).rejects.toThrow("is not a Matrix poll start event");
|
||||
@@ -938,6 +979,7 @@ describe("voteMatrixPoll", () => {
|
||||
await expect(
|
||||
voteMatrixPoll("room:!room:example", "$poll", {
|
||||
client,
|
||||
cfg: {} as never,
|
||||
optionIndex: 1,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { MarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
||||
import type { PollInput } from "../runtime-api.js";
|
||||
import { getMatrixRuntime } from "../runtime.js";
|
||||
@@ -135,13 +136,13 @@ async function resolvePreviousEditMentions(params: {
|
||||
export function prepareMatrixSingleText(
|
||||
text: string,
|
||||
opts: {
|
||||
cfg?: CoreConfig;
|
||||
cfg: CoreConfig;
|
||||
accountId?: string;
|
||||
tableMode?: MarkdownTableMode;
|
||||
} = {},
|
||||
},
|
||||
): MatrixPreparedSingleText {
|
||||
const trimmedText = text.trim();
|
||||
const cfg = opts.cfg ?? getCore().config.loadConfig();
|
||||
const cfg = requireRuntimeConfig(opts.cfg, "Matrix text preparation") as CoreConfig;
|
||||
const tableMode =
|
||||
opts.tableMode ??
|
||||
getCore().channel.text.resolveMarkdownTableMode({
|
||||
@@ -165,13 +166,13 @@ export function prepareMatrixSingleText(
|
||||
export function chunkMatrixText(
|
||||
text: string,
|
||||
opts: {
|
||||
cfg?: CoreConfig;
|
||||
cfg: CoreConfig;
|
||||
accountId?: string;
|
||||
tableMode?: MarkdownTableMode;
|
||||
} = {},
|
||||
},
|
||||
): MatrixPreparedChunkedText {
|
||||
const preparedText = prepareMatrixSingleText(text, opts);
|
||||
const cfg = opts.cfg ?? getCore().config.loadConfig();
|
||||
const cfg = requireRuntimeConfig(opts.cfg, "Matrix text chunking") as CoreConfig;
|
||||
const chunkMode = getCore().channel.text.resolveChunkMode(cfg, "matrix", opts.accountId);
|
||||
return {
|
||||
...preparedText,
|
||||
@@ -186,7 +187,7 @@ export function chunkMatrixText(
|
||||
export async function sendMessageMatrix(
|
||||
to: string,
|
||||
message: string | undefined,
|
||||
opts: MatrixSendOpts = {},
|
||||
opts: MatrixSendOpts,
|
||||
): Promise<MatrixSendResult> {
|
||||
const trimmedMessage = message?.trim() ?? "";
|
||||
if (!trimmedMessage && !opts.mediaUrl) {
|
||||
@@ -201,7 +202,7 @@ export async function sendMessageMatrix(
|
||||
},
|
||||
async (client) => {
|
||||
const roomId = await resolveMatrixRoomId(client, to);
|
||||
const cfg = opts.cfg ?? getCore().config.loadConfig();
|
||||
const cfg = requireRuntimeConfig(opts.cfg, "Matrix send") as CoreConfig;
|
||||
const { chunks } = chunkMatrixText(trimmedMessage, {
|
||||
cfg,
|
||||
accountId: opts.accountId,
|
||||
@@ -330,7 +331,7 @@ export async function sendMessageMatrix(
|
||||
export async function sendPollMatrix(
|
||||
to: string,
|
||||
poll: PollInput,
|
||||
opts: MatrixSendOpts = {},
|
||||
opts: MatrixSendOpts,
|
||||
): Promise<{ eventId: string; roomId: string }> {
|
||||
if (!poll.question?.trim()) {
|
||||
throw new Error("Matrix poll requires a question");
|
||||
@@ -416,7 +417,7 @@ export async function sendSingleTextMessageMatrix(
|
||||
text: string,
|
||||
opts: {
|
||||
client?: MatrixClient;
|
||||
cfg?: CoreConfig;
|
||||
cfg: CoreConfig;
|
||||
replyToId?: string;
|
||||
threadId?: string;
|
||||
accountId?: string;
|
||||
@@ -425,7 +426,7 @@ export async function sendSingleTextMessageMatrix(
|
||||
extraContent?: MatrixExtraContentFields;
|
||||
/** When true, marks the message as a live/streaming update (MSC4357). */
|
||||
live?: boolean;
|
||||
} = {},
|
||||
},
|
||||
): Promise<MatrixSendResult> {
|
||||
const { trimmedText, convertedText, singleEventLimit, fitsInSingleEvent } =
|
||||
prepareMatrixSingleText(text, {
|
||||
@@ -502,7 +503,7 @@ export async function editMessageMatrix(
|
||||
newText: string,
|
||||
opts: {
|
||||
client?: MatrixClient;
|
||||
cfg?: CoreConfig;
|
||||
cfg: CoreConfig;
|
||||
threadId?: string;
|
||||
accountId?: string;
|
||||
timeoutMs?: number;
|
||||
@@ -511,7 +512,7 @@ export async function editMessageMatrix(
|
||||
extraContent?: MatrixExtraContentFields;
|
||||
/** When true, marks the edit as a live/streaming update (MSC4357). */
|
||||
live?: boolean;
|
||||
} = {},
|
||||
},
|
||||
): Promise<string> {
|
||||
return await withResolvedMatrixSendClient(
|
||||
{
|
||||
@@ -522,7 +523,7 @@ export async function editMessageMatrix(
|
||||
},
|
||||
async (client) => {
|
||||
const resolvedRoom = await resolveMatrixRoomId(client, roomId);
|
||||
const cfg = opts.cfg ?? getCore().config.loadConfig();
|
||||
const cfg = requireRuntimeConfig(opts.cfg, "Matrix message edit") as CoreConfig;
|
||||
const tableMode = getCore().channel.text.resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "matrix",
|
||||
|
||||
@@ -16,6 +16,8 @@ const {
|
||||
resolveMatrixAuthContextMock,
|
||||
} = matrixClientResolverMocks;
|
||||
|
||||
const TEST_CFG = {};
|
||||
|
||||
vi.mock("../active-client.js", () => ({
|
||||
getActiveMatrixClient: (...args: unknown[]) => getActiveMatrixClientMock(...args),
|
||||
}));
|
||||
@@ -56,7 +58,10 @@ describe("matrix send client helpers", () => {
|
||||
it("stops one-off shared clients when no active monitor client is registered", async () => {
|
||||
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "18799");
|
||||
|
||||
const result = await withResolvedMatrixSendClient({ accountId: "default" }, async () => "ok");
|
||||
const result = await withResolvedMatrixSendClient(
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async () => "ok",
|
||||
);
|
||||
|
||||
await expectOneOffSharedMatrixClient({
|
||||
prepareForOneOffCalls: 0,
|
||||
@@ -70,10 +75,13 @@ describe("matrix send client helpers", () => {
|
||||
const activeClient = createMockMatrixClient();
|
||||
getActiveMatrixClientMock.mockReturnValue(activeClient);
|
||||
|
||||
const result = await withResolvedMatrixSendClient({ accountId: "default" }, async (client) => {
|
||||
expect(client).toBe(activeClient);
|
||||
return "ok";
|
||||
});
|
||||
const result = await withResolvedMatrixSendClient(
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async (client) => {
|
||||
expect(client).toBe(activeClient);
|
||||
return "ok";
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toBe("ok");
|
||||
expect(acquireSharedMatrixClientMock).not.toHaveBeenCalled();
|
||||
@@ -89,7 +97,7 @@ describe("matrix send client helpers", () => {
|
||||
accountId: "ops",
|
||||
resolved: {},
|
||||
});
|
||||
await withResolvedMatrixSendClient({}, async () => {});
|
||||
await withResolvedMatrixSendClient({ cfg: TEST_CFG }, async () => {});
|
||||
|
||||
await expectOneOffSharedMatrixClient({
|
||||
accountId: "ops",
|
||||
@@ -121,7 +129,7 @@ describe("matrix send client helpers", () => {
|
||||
acquireSharedMatrixClientMock.mockResolvedValue(sharedClient);
|
||||
|
||||
await expect(
|
||||
withResolvedMatrixSendClient({ accountId: "default" }, async () => {
|
||||
withResolvedMatrixSendClient({ cfg: TEST_CFG, accountId: "default" }, async () => {
|
||||
throw new Error("boom");
|
||||
}),
|
||||
).rejects.toThrow("boom");
|
||||
@@ -133,7 +141,7 @@ describe("matrix send client helpers", () => {
|
||||
const sharedClient = createMockMatrixClient();
|
||||
acquireSharedMatrixClientMock.mockResolvedValue(sharedClient);
|
||||
|
||||
await withResolvedMatrixSendClient({ accountId: "default" }, async () => "ok");
|
||||
await withResolvedMatrixSendClient({ cfg: TEST_CFG, accountId: "default" }, async () => "ok");
|
||||
|
||||
expect(sharedClient.start).toHaveBeenCalledTimes(1);
|
||||
expect(sharedClient.prepareForOneOff).not.toHaveBeenCalled();
|
||||
@@ -141,7 +149,7 @@ describe("matrix send client helpers", () => {
|
||||
|
||||
it("keeps one-off control clients lightweight when no active monitor client is registered", async () => {
|
||||
const result = await withResolvedMatrixControlClient(
|
||||
{ accountId: "default" },
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async () => "ok",
|
||||
);
|
||||
|
||||
@@ -158,7 +166,7 @@ describe("matrix send client helpers", () => {
|
||||
getActiveMatrixClientMock.mockReturnValue(activeClient);
|
||||
|
||||
const result = await withResolvedMatrixControlClient(
|
||||
{ accountId: "default" },
|
||||
{ cfg: TEST_CFG, accountId: "default" },
|
||||
async (client) => {
|
||||
expect(client).toBe(activeClient);
|
||||
return "ok";
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import { resolveMatrixAccountConfig } from "../account-config.js";
|
||||
import type { MatrixClient } from "../sdk.js";
|
||||
|
||||
const getCore = () => getMatrixRuntime();
|
||||
|
||||
type MatrixSendClientRuntime = Pick<
|
||||
typeof import("../client-bootstrap.js"),
|
||||
"withResolvedRuntimeMatrixClient"
|
||||
@@ -21,7 +19,12 @@ export function resolveMediaMaxBytes(
|
||||
accountId?: string | null,
|
||||
cfg?: CoreConfig,
|
||||
): number | undefined {
|
||||
const resolvedCfg = cfg ?? (getCore().config.loadConfig() as CoreConfig);
|
||||
if (!cfg) {
|
||||
throw new Error(
|
||||
"Matrix media limits requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
|
||||
);
|
||||
}
|
||||
const resolvedCfg = requireRuntimeConfig(cfg, "Matrix media limits") as CoreConfig;
|
||||
const matrixCfg = resolveMatrixAccountConfig({ cfg: resolvedCfg, accountId });
|
||||
const mediaMaxMb = typeof matrixCfg.mediaMaxMb === "number" ? matrixCfg.mediaMaxMb : undefined;
|
||||
if (typeof mediaMaxMb === "number") {
|
||||
|
||||
@@ -89,8 +89,8 @@ export type MatrixSendResult = {
|
||||
};
|
||||
|
||||
export type MatrixSendOpts = {
|
||||
cfg: CoreConfig;
|
||||
client?: import("../sdk.js").MatrixClient;
|
||||
cfg?: CoreConfig;
|
||||
mediaUrl?: string;
|
||||
mediaAccess?: {
|
||||
localRoots?: readonly string[];
|
||||
|
||||
@@ -74,6 +74,7 @@ describe("matrix thread bindings", () => {
|
||||
} = {},
|
||||
) {
|
||||
return createMatrixThreadBindingManager({
|
||||
cfg: {},
|
||||
accountId,
|
||||
auth: params.auth ?? auth,
|
||||
client: matrixClient,
|
||||
@@ -170,6 +171,7 @@ describe("matrix thread bindings", () => {
|
||||
|
||||
it("creates child Matrix thread bindings from a top-level room context", async () => {
|
||||
await createMatrixThreadBindingManager({
|
||||
cfg: {},
|
||||
accountId,
|
||||
auth,
|
||||
client: matrixClient,
|
||||
@@ -193,6 +195,7 @@ describe("matrix thread bindings", () => {
|
||||
});
|
||||
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledWith("room:!room:example", "intro root", {
|
||||
cfg: {},
|
||||
client: {},
|
||||
accountId: "ops",
|
||||
});
|
||||
@@ -214,6 +217,7 @@ describe("matrix thread bindings", () => {
|
||||
});
|
||||
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledWith("room:!room:example", "intro thread", {
|
||||
cfg: {},
|
||||
client: {},
|
||||
accountId: "ops",
|
||||
threadId: "$thread",
|
||||
@@ -236,6 +240,7 @@ describe("matrix thread bindings", () => {
|
||||
vi.setSystemTime(new Date("2026-03-08T12:00:00.000Z"));
|
||||
try {
|
||||
await createMatrixThreadBindingManager({
|
||||
cfg: {},
|
||||
accountId: "ops",
|
||||
auth,
|
||||
client: {} as never,
|
||||
@@ -280,6 +285,7 @@ describe("matrix thread bindings", () => {
|
||||
vi.setSystemTime(new Date("2026-03-08T12:00:00.000Z"));
|
||||
try {
|
||||
await createMatrixThreadBindingManager({
|
||||
cfg: {},
|
||||
accountId: "ops",
|
||||
auth,
|
||||
client: {} as never,
|
||||
@@ -333,6 +339,7 @@ describe("matrix thread bindings", () => {
|
||||
const logVerboseMessage = vi.fn();
|
||||
try {
|
||||
await createMatrixThreadBindingManager({
|
||||
cfg: {},
|
||||
accountId: "ops",
|
||||
auth,
|
||||
client: {} as never,
|
||||
@@ -387,6 +394,7 @@ describe("matrix thread bindings", () => {
|
||||
|
||||
it("sends threaded farewell messages when bindings are unbound", async () => {
|
||||
await createMatrixThreadBindingManager({
|
||||
cfg: {},
|
||||
accountId: "ops",
|
||||
auth,
|
||||
client: {} as never,
|
||||
@@ -420,6 +428,7 @@ describe("matrix thread bindings", () => {
|
||||
"room:!room:example",
|
||||
expect.stringContaining("Session ended automatically"),
|
||||
expect.objectContaining({
|
||||
cfg: {},
|
||||
accountId: "ops",
|
||||
threadId: "$thread",
|
||||
}),
|
||||
@@ -569,6 +578,7 @@ describe("matrix thread bindings", () => {
|
||||
vi.setSystemTime(new Date("2026-03-06T10:00:00.000Z"));
|
||||
try {
|
||||
const manager = await createMatrixThreadBindingManager({
|
||||
cfg: {},
|
||||
accountId: "ops",
|
||||
auth,
|
||||
client: {} as never,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
||||
import { resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/session-key-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -146,6 +147,7 @@ function buildMatrixBindingIntroText(params: {
|
||||
}
|
||||
|
||||
async function sendBindingMessage(params: {
|
||||
cfg: OpenClawConfig;
|
||||
client: MatrixClient;
|
||||
accountId: string;
|
||||
roomId: string;
|
||||
@@ -157,6 +159,7 @@ async function sendBindingMessage(params: {
|
||||
return null;
|
||||
}
|
||||
const result = await sendMessageMatrix(`room:${params.roomId}`, trimmed, {
|
||||
cfg: params.cfg,
|
||||
client: params.client,
|
||||
accountId: params.accountId,
|
||||
...(params.threadId ? { threadId: params.threadId } : {}),
|
||||
@@ -165,6 +168,7 @@ async function sendBindingMessage(params: {
|
||||
}
|
||||
|
||||
async function sendFarewellMessage(params: {
|
||||
cfg: OpenClawConfig;
|
||||
client: MatrixClient;
|
||||
accountId: string;
|
||||
record: MatrixThreadBindingRecord;
|
||||
@@ -185,6 +189,7 @@ async function sendFarewellMessage(params: {
|
||||
maxAgeMs,
|
||||
});
|
||||
await sendBindingMessage({
|
||||
cfg: params.cfg,
|
||||
client: params.client,
|
||||
accountId: params.accountId,
|
||||
roomId,
|
||||
@@ -198,6 +203,7 @@ async function sendFarewellMessage(params: {
|
||||
}
|
||||
|
||||
export async function createMatrixThreadBindingManager(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
auth: MatrixAuth;
|
||||
client: MatrixClient;
|
||||
@@ -387,6 +393,7 @@ export async function createMatrixThreadBindingManager(params: {
|
||||
await Promise.all(
|
||||
removed.map(async (record) => {
|
||||
await sendFarewellMessage({
|
||||
cfg: params.cfg,
|
||||
client: params.client,
|
||||
accountId: params.accountId,
|
||||
record,
|
||||
@@ -429,6 +436,7 @@ export async function createMatrixThreadBindingManager(params: {
|
||||
if (input.placement === "child") {
|
||||
const roomId = parentConversationId || conversationId;
|
||||
const rootEventId = await sendBindingMessage({
|
||||
cfg: params.cfg,
|
||||
client: params.client,
|
||||
accountId: params.accountId,
|
||||
roomId,
|
||||
@@ -468,6 +476,7 @@ export async function createMatrixThreadBindingManager(params: {
|
||||
? boundConversationId
|
||||
: undefined;
|
||||
await sendBindingMessage({
|
||||
cfg: params.cfg,
|
||||
client: params.client,
|
||||
accountId: params.accountId,
|
||||
roomId,
|
||||
|
||||
Reference in New Issue
Block a user