Matrix: honor account-scoped status and chunk limits

This commit is contained in:
Gustavo Madeira Santana
2026-03-11 23:05:39 +00:00
parent b9cdd48978
commit 457bb22c13
7 changed files with 222 additions and 764 deletions

View File

@@ -3,6 +3,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const hoisted = vi.hoisted(() => { const hoisted = vi.hoisted(() => {
const callOrder: string[] = []; const callOrder: string[] = [];
const client = { id: "matrix-client" }; const client = { id: "matrix-client" };
const resolveTextChunkLimit = vi.fn<
(cfg: unknown, channel: unknown, accountId?: unknown) => number
>(() => 4000);
const logger = { const logger = {
info: vi.fn(), info: vi.fn(),
warn: vi.fn(), warn: vi.fn(),
@@ -14,6 +17,7 @@ const hoisted = vi.hoisted(() => {
callOrder, callOrder,
client, client,
logger, logger,
resolveTextChunkLimit,
stopThreadBindingManager, stopThreadBindingManager,
}; };
}); });
@@ -60,7 +64,8 @@ vi.mock("../../runtime.js", () => ({
buildMentionRegexes: () => [], buildMentionRegexes: () => [],
}, },
text: { text: {
resolveTextChunkLimit: () => 4000, resolveTextChunkLimit: (cfg: unknown, channel: unknown, accountId?: unknown) =>
hoisted.resolveTextChunkLimit(cfg, channel, accountId),
}, },
}, },
system: { system: {
@@ -186,6 +191,7 @@ describe("monitorMatrixProvider", () => {
beforeEach(() => { beforeEach(() => {
vi.resetModules(); vi.resetModules();
hoisted.callOrder.length = 0; hoisted.callOrder.length = 0;
hoisted.resolveTextChunkLimit.mockReset().mockReturnValue(4000);
hoisted.stopThreadBindingManager.mockReset(); hoisted.stopThreadBindingManager.mockReset();
Object.values(hoisted.logger).forEach((mock) => mock.mockReset()); Object.values(hoisted.logger).forEach((mock) => mock.mockReset());
}); });
@@ -205,4 +211,18 @@ describe("monitorMatrixProvider", () => {
]); ]);
expect(hoisted.stopThreadBindingManager).toHaveBeenCalledTimes(1); expect(hoisted.stopThreadBindingManager).toHaveBeenCalledTimes(1);
}); });
it("resolves text chunk limit for the effective Matrix account", async () => {
const { monitorMatrixProvider } = await import("./index.js");
const abortController = new AbortController();
abortController.abort();
await monitorMatrixProvider({ abortSignal: abortController.signal });
expect(hoisted.resolveTextChunkLimit).toHaveBeenCalledWith(
expect.anything(),
"matrix",
"default",
);
});
}); });

View File

@@ -158,7 +158,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
const dmEnabled = dmConfig?.enabled ?? true; const dmEnabled = dmConfig?.enabled ?? true;
const dmPolicyRaw = dmConfig?.policy ?? "pairing"; const dmPolicyRaw = dmConfig?.policy ?? "pairing";
const dmPolicy = allowlistOnly && dmPolicyRaw !== "disabled" ? "allowlist" : dmPolicyRaw; const dmPolicy = allowlistOnly && dmPolicyRaw !== "disabled" ? "allowlist" : dmPolicyRaw;
const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "matrix"); const textLimit = core.channel.text.resolveTextChunkLimit(cfg, "matrix", account.accountId);
const mediaMaxMb = opts.mediaMaxMb ?? accountConfig.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB; const mediaMaxMb = opts.mediaMaxMb ?? accountConfig.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;
const mediaMaxBytes = Math.max(1, mediaMaxMb) * 1024 * 1024; const mediaMaxBytes = Math.max(1, mediaMaxMb) * 1024 * 1024;
const startupMs = Date.now(); const startupMs = Date.now();

View File

@@ -10,6 +10,9 @@ const loadWebMediaMock = vi.fn().mockResolvedValue({
}); });
const getImageMetadataMock = vi.fn().mockResolvedValue(null); const getImageMetadataMock = vi.fn().mockResolvedValue(null);
const resizeToJpegMock = vi.fn(); const resizeToJpegMock = vi.fn();
const resolveTextChunkLimitMock = vi.fn<
(cfg: unknown, channel: unknown, accountId?: unknown) => number
>(() => 4000);
const runtimeStub = { const runtimeStub = {
config: { config: {
@@ -24,7 +27,8 @@ const runtimeStub = {
}, },
channel: { channel: {
text: { text: {
resolveTextChunkLimit: () => 4000, resolveTextChunkLimit: (cfg: unknown, channel: unknown, accountId?: unknown) =>
resolveTextChunkLimitMock(cfg, channel, accountId),
resolveChunkMode: () => "length", resolveChunkMode: () => "length",
chunkMarkdownText: (text: string) => (text ? [text] : []), chunkMarkdownText: (text: string) => (text ? [text] : []),
chunkMarkdownTextWithMode: (text: string) => (text ? [text] : []), chunkMarkdownTextWithMode: (text: string) => (text ? [text] : []),
@@ -70,6 +74,7 @@ describe("sendMessageMatrix media", () => {
}); });
getImageMetadataMock.mockReset().mockResolvedValue(null); getImageMetadataMock.mockReset().mockResolvedValue(null);
resizeToJpegMock.mockReset(); resizeToJpegMock.mockReset();
resolveTextChunkLimitMock.mockReset().mockReturnValue(4000);
setMatrixRuntime(runtimeStub); setMatrixRuntime(runtimeStub);
}); });
@@ -235,6 +240,17 @@ describe("sendMessageMatrix threads", () => {
"m.in_reply_to": { event_id: "$thread" }, "m.in_reply_to": { event_id: "$thread" },
}); });
}); });
it("resolves text chunk limit using the active Matrix account", async () => {
const { client } = makeClient();
await sendMessageMatrix("room:!room:example", "hello", {
client,
accountId: "ops",
});
expect(resolveTextChunkLimitMock).toHaveBeenCalledWith(expect.anything(), "matrix", "ops");
});
}); });
describe("voteMatrixPoll", () => { describe("voteMatrixPoll", () => {

View File

@@ -81,7 +81,7 @@ export async function sendMessageMatrix(
trimmedMessage, trimmedMessage,
tableMode, tableMode,
); );
const textLimit = getCore().channel.text.resolveTextChunkLimit(cfg, "matrix"); const textLimit = getCore().channel.text.resolveTextChunkLimit(cfg, "matrix", opts.accountId);
const chunkLimit = Math.min(textLimit, MATRIX_TEXT_LIMIT); const chunkLimit = Math.min(textLimit, MATRIX_TEXT_LIMIT);
const chunkMode = getCore().channel.text.resolveChunkMode(cfg, "matrix", opts.accountId); const chunkMode = getCore().channel.text.resolveChunkMode(cfg, "matrix", opts.accountId);
const chunks = getCore().channel.text.chunkMarkdownTextWithMode( const chunks = getCore().channel.text.chunkMarkdownTextWithMode(

View File

@@ -161,6 +161,35 @@ describe("matrix onboarding", () => {
expect(noteText).toContain("MATRIX_<ACCOUNT_ID>_DEVICE_NAME"); expect(noteText).toContain("MATRIX_<ACCOUNT_ID>_DEVICE_NAME");
}); });
it("resolves status using the overridden Matrix account", async () => {
const status = await matrixOnboardingAdapter.getStatus({
cfg: {
channels: {
matrix: {
defaultAccount: "default",
accounts: {
default: {
homeserver: "https://matrix.default.example.org",
},
ops: {
homeserver: "https://matrix.ops.example.org",
accessToken: "ops-token",
},
},
},
},
} as CoreConfig,
options: undefined,
accountOverrides: {
matrix: "ops",
},
});
expect(status.configured).toBe(true);
expect(status.selectionHint).toBe("configured");
expect(status.statusLines).toEqual(["Matrix: configured"]);
});
it("writes allowlists and room access to the selected Matrix account", async () => { it("writes allowlists and room access to the selected Matrix account", async () => {
setMatrixRuntime({ setMatrixRuntime({
state: { state: {

View File

@@ -492,10 +492,10 @@ async function runMatrixConfigure(params: {
export const matrixOnboardingAdapter: ChannelOnboardingAdapter = { export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
channel, channel,
getStatus: async ({ cfg }) => { getStatus: async ({ cfg, accountOverrides }) => {
const account = resolveMatrixAccount({ const account = resolveMatrixAccount({
cfg: cfg as CoreConfig, cfg: cfg as CoreConfig,
accountId: resolveMatrixOnboardingAccountId(cfg as CoreConfig), accountId: resolveMatrixOnboardingAccountId(cfg as CoreConfig, accountOverrides[channel]),
}); });
const configured = account.configured; const configured = account.configured;
const sdkReady = isMatrixSdkAvailable(); const sdkReady = isMatrixSdkAvailable();

909
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff