mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 11:20:21 +00:00
Matrix: fix typecheck and boundary drift
This commit is contained in:
@@ -59,7 +59,7 @@ describe("matrixMessageActions", () => {
|
||||
|
||||
const discovery = describeMessageTool!({
|
||||
cfg: createConfiguredMatrixConfig(),
|
||||
} as never);
|
||||
} as never) ?? { actions: [] };
|
||||
const actions = discovery.actions;
|
||||
|
||||
expect(actions).toContain("poll");
|
||||
@@ -74,7 +74,7 @@ describe("matrixMessageActions", () => {
|
||||
|
||||
const discovery = describeMessageTool!({
|
||||
cfg: createConfiguredMatrixConfig(),
|
||||
} as never);
|
||||
} as never) ?? { actions: [], schema: null };
|
||||
const actions = discovery.actions;
|
||||
const properties =
|
||||
(discovery.schema as { properties?: Record<string, unknown> } | null)?.properties ?? {};
|
||||
@@ -87,64 +87,66 @@ describe("matrixMessageActions", () => {
|
||||
});
|
||||
|
||||
it("hides gated actions when the default Matrix account disables them", () => {
|
||||
const actions = matrixMessageActions.describeMessageTool!({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
defaultAccount: "assistant",
|
||||
actions: {
|
||||
messages: true,
|
||||
reactions: true,
|
||||
pins: true,
|
||||
profile: true,
|
||||
memberInfo: true,
|
||||
channelInfo: true,
|
||||
verification: true,
|
||||
},
|
||||
accounts: {
|
||||
assistant: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "token",
|
||||
encryption: true,
|
||||
actions: {
|
||||
messages: false,
|
||||
reactions: false,
|
||||
pins: false,
|
||||
profile: false,
|
||||
memberInfo: false,
|
||||
channelInfo: false,
|
||||
verification: false,
|
||||
const actions =
|
||||
matrixMessageActions.describeMessageTool!({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
defaultAccount: "assistant",
|
||||
actions: {
|
||||
messages: true,
|
||||
reactions: true,
|
||||
pins: true,
|
||||
profile: true,
|
||||
memberInfo: true,
|
||||
channelInfo: true,
|
||||
verification: true,
|
||||
},
|
||||
accounts: {
|
||||
assistant: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "token",
|
||||
encryption: true,
|
||||
actions: {
|
||||
messages: false,
|
||||
reactions: false,
|
||||
pins: false,
|
||||
profile: false,
|
||||
memberInfo: false,
|
||||
channelInfo: false,
|
||||
verification: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig,
|
||||
} as never).actions;
|
||||
} as CoreConfig,
|
||||
} as never)?.actions ?? [];
|
||||
|
||||
expect(actions).toEqual(["poll", "poll-vote"]);
|
||||
});
|
||||
|
||||
it("hides actions until defaultAccount is set for ambiguous multi-account configs", () => {
|
||||
const actions = matrixMessageActions.describeMessageTool!({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
accounts: {
|
||||
assistant: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "assistant-token",
|
||||
},
|
||||
ops: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "ops-token",
|
||||
const actions =
|
||||
matrixMessageActions.describeMessageTool!({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
accounts: {
|
||||
assistant: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "assistant-token",
|
||||
},
|
||||
ops: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "ops-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig,
|
||||
} as never).actions;
|
||||
} as CoreConfig,
|
||||
} as never)?.actions ?? [];
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -2,11 +2,13 @@ import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./d
|
||||
import { resolveMatrixAuth } from "./matrix/client.js";
|
||||
import { probeMatrix } from "./matrix/probe.js";
|
||||
import { sendMessageMatrix } from "./matrix/send.js";
|
||||
import { matrixOutbound } from "./outbound.js";
|
||||
import { resolveMatrixTargets } from "./resolve-targets.js";
|
||||
|
||||
export const matrixChannelRuntime = {
|
||||
listMatrixDirectoryGroupsLive,
|
||||
listMatrixDirectoryPeersLive,
|
||||
matrixOutbound,
|
||||
probeMatrix,
|
||||
resolveMatrixAuth,
|
||||
resolveMatrixTargets,
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
createTextPairingAdapter,
|
||||
listResolvedDirectoryEntriesFromSources,
|
||||
} from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { buildTrafficStatusSummary } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { buildTrafficStatusSummary } from "../../shared/channel-status-summary.js";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
buildProbeChannelStatusSummary,
|
||||
@@ -47,7 +47,6 @@ import {
|
||||
import { getMatrixRuntime } from "./runtime.js";
|
||||
import { resolveMatrixOutboundSessionRoute } from "./session-route.js";
|
||||
import { matrixSetupAdapter } from "./setup-core.js";
|
||||
import { matrixSetupWizard } from "./setup-surface.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
// Mutex for serializing account startup (workaround for concurrent dynamic import race condition)
|
||||
@@ -190,7 +189,6 @@ function matchMatrixAcpConversation(params: {
|
||||
export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
|
||||
id: "matrix",
|
||||
meta,
|
||||
setupWizard: matrixSetupWizard,
|
||||
pairing: createTextPairingAdapter({
|
||||
idLabel: "matrixUserId",
|
||||
message: PAIRING_APPROVED_MESSAGE,
|
||||
|
||||
@@ -521,7 +521,9 @@ describe("matrix CLI verification commands", () => {
|
||||
|
||||
expect(matrixRuntimeWriteConfigFileMock).toHaveBeenCalled();
|
||||
expect(process.exitCode).toBeUndefined();
|
||||
const jsonOutput = console.log.mock.calls.at(-1)?.[0];
|
||||
const jsonOutput = (console.log as unknown as { mock: { calls: unknown[][] } }).mock.calls.at(
|
||||
-1,
|
||||
)?.[0];
|
||||
expect(typeof jsonOutput).toBe("string");
|
||||
expect(JSON.parse(String(jsonOutput))).toEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -12,7 +12,7 @@ function createSyncResponse(nextBatch: string): ISyncResponse {
|
||||
rooms: {
|
||||
join: {
|
||||
"!room:example.org": {
|
||||
summary: {},
|
||||
summary: { "m.heroes": [] },
|
||||
state: { events: [] },
|
||||
timeline: {
|
||||
events: [
|
||||
@@ -34,6 +34,9 @@ function createSyncResponse(nextBatch: string): ISyncResponse {
|
||||
unread_notifications: {},
|
||||
},
|
||||
},
|
||||
invite: {},
|
||||
leave: {},
|
||||
knock: {},
|
||||
},
|
||||
account_data: {
|
||||
events: [
|
||||
|
||||
@@ -52,7 +52,7 @@ function toPersistedSyncData(value: unknown): ISyncData | null {
|
||||
nextBatch: value.nextBatch,
|
||||
accountData: value.accountData,
|
||||
roomsData: value.roomsData,
|
||||
} as ISyncData;
|
||||
} as unknown as ISyncData;
|
||||
}
|
||||
|
||||
// Older Matrix state files stored the raw /sync-shaped payload directly.
|
||||
@@ -64,7 +64,7 @@ function toPersistedSyncData(value: unknown): ISyncData | null {
|
||||
? value.account_data.events
|
||||
: [],
|
||||
roomsData: isRecord(value.rooms) ? value.rooms : {},
|
||||
} as ISyncData;
|
||||
} as unknown as ISyncData;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
1
extensions/matrix/src/matrix/index.ts
Normal file
1
extensions/matrix/src/matrix/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { monitorMatrixProvider } from "./monitor/index.js";
|
||||
@@ -62,7 +62,7 @@ function createHarness(params?: {
|
||||
const ensureVerificationDmTracked = vi.fn(
|
||||
params?.ensureVerificationDmTracked ?? (async () => null),
|
||||
);
|
||||
const sendMessage = vi.fn(async () => "$notice");
|
||||
const sendMessage = vi.fn(async (_roomId: string, _payload: { body?: string }) => "$notice");
|
||||
const invalidateRoom = vi.fn();
|
||||
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
||||
const formatNativeDependencyHint = vi.fn(() => "install hint");
|
||||
|
||||
@@ -100,6 +100,7 @@ function createHandlerHarness() {
|
||||
mediaMaxBytes: 5 * 1024 * 1024,
|
||||
startupMs: Date.now() - 120_000,
|
||||
startupGraceMs: 60_000,
|
||||
dropPreStartupMessages: false,
|
||||
directTracker: {
|
||||
isDirectMessage: vi.fn().mockResolvedValue(true),
|
||||
},
|
||||
|
||||
@@ -588,11 +588,13 @@ describe("matrix monitor handler pairing account scope", () => {
|
||||
mediaMaxBytes: 10_000_000,
|
||||
startupMs: 0,
|
||||
startupGraceMs: 0,
|
||||
dropPreStartupMessages: false,
|
||||
directTracker: {
|
||||
isDirectMessage: async () => false,
|
||||
},
|
||||
getRoomInfo: async () => ({ altAliases: [] }),
|
||||
getMemberDisplayName: async () => "sender",
|
||||
needsRoomAliasesForConfig: false,
|
||||
});
|
||||
|
||||
await handler(
|
||||
|
||||
@@ -115,6 +115,7 @@ describe("createMatrixRoomMessageHandler thread root media", () => {
|
||||
mediaMaxBytes: 5 * 1024 * 1024,
|
||||
startupMs: Date.now() - 120_000,
|
||||
startupGraceMs: 60_000,
|
||||
dropPreStartupMessages: false,
|
||||
directTracker: {
|
||||
isDirectMessage: vi.fn().mockResolvedValue(true),
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ const hoisted = vi.hoisted(() => {
|
||||
hasPersistedSyncState: vi.fn(() => false),
|
||||
};
|
||||
const createMatrixRoomMessageHandler = vi.fn(() => vi.fn());
|
||||
let startClientError: Error | null = null;
|
||||
const resolveTextChunkLimit = vi.fn<
|
||||
(cfg: unknown, channel: unknown, accountId?: unknown) => number
|
||||
>(() => 4000);
|
||||
@@ -27,7 +26,7 @@ const hoisted = vi.hoisted(() => {
|
||||
logger,
|
||||
resolveTextChunkLimit,
|
||||
setActiveMatrixClient,
|
||||
startClientError,
|
||||
startClientError: null as Error | null,
|
||||
stopSharedClientInstance,
|
||||
stopThreadBindingManager,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../../../src/config/config.js";
|
||||
import {
|
||||
__testing as sessionBindingTesting,
|
||||
createTestRegistry,
|
||||
type OpenClawConfig,
|
||||
resolveAgentRoute,
|
||||
registerSessionBindingAdapter,
|
||||
} from "../../../../../src/infra/outbound/session-binding-service.js";
|
||||
import { setActivePluginRegistry } from "../../../../../src/plugins/runtime.js";
|
||||
import { resolveAgentRoute } from "../../../../../src/routing/resolve-route.js";
|
||||
import { createTestRegistry } from "../../../../../src/test-utils/channel-plugins.js";
|
||||
sessionBindingTesting,
|
||||
setActivePluginRegistry,
|
||||
} from "../../../../../test/helpers/extensions/matrix-route-test.js";
|
||||
import { matrixPlugin } from "../../channel.js";
|
||||
import { resolveMatrixInboundRoute } from "./route.js";
|
||||
|
||||
|
||||
@@ -222,7 +222,10 @@ describe("MatrixClient request hardening", () => {
|
||||
|
||||
it("prefers authenticated client media downloads", async () => {
|
||||
const payload = Buffer.from([1, 2, 3, 4]);
|
||||
const fetchMock = vi.fn(async () => new Response(payload, { status: 200 }));
|
||||
const fetchMock = vi.fn(
|
||||
async (_input: RequestInfo | URL, _init?: RequestInit) =>
|
||||
new Response(payload, { status: 200 }),
|
||||
);
|
||||
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
|
||||
|
||||
const client = new MatrixClient("https://matrix.example.org", "token");
|
||||
|
||||
@@ -4,6 +4,7 @@ import { EventEmitter } from "node:events";
|
||||
import {
|
||||
ClientEvent,
|
||||
MatrixEventEvent,
|
||||
Preset,
|
||||
createClient as createMatrixJsClient,
|
||||
type MatrixClient as MatrixJsClient,
|
||||
type MatrixEvent,
|
||||
@@ -547,7 +548,7 @@ export class MatrixClient {
|
||||
const result = await this.client.createRoom({
|
||||
invite: [remoteUserId],
|
||||
is_direct: true,
|
||||
preset: "trusted_private_chat",
|
||||
preset: Preset.TrustedPrivateChat,
|
||||
initial_state: initialState,
|
||||
});
|
||||
return result.room_id;
|
||||
|
||||
@@ -621,14 +621,6 @@ export async function createMatrixThreadBindingManager(params: {
|
||||
});
|
||||
return record ? toSessionBindingRecord(record, defaults) : null;
|
||||
},
|
||||
setIdleTimeoutBySession: ({ targetSessionKey, idleTimeoutMs }) =>
|
||||
manager
|
||||
.setIdleTimeoutBySessionKey({ targetSessionKey, idleTimeoutMs })
|
||||
.map((record) => toSessionBindingRecord(record, defaults)),
|
||||
setMaxAgeBySession: ({ targetSessionKey, maxAgeMs }) =>
|
||||
manager
|
||||
.setMaxAgeBySessionKey({ targetSessionKey, maxAgeMs })
|
||||
.map((record) => toSessionBindingRecord(record, defaults)),
|
||||
touch: (bindingId, at) => {
|
||||
manager.touchBinding(bindingId, at);
|
||||
},
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import {
|
||||
type ChannelSetupDmPolicy,
|
||||
type ChannelSetupWizardAdapter,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { type ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
|
||||
import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js";
|
||||
import { listMatrixDirectoryGroupsLive } from "./directory-live.js";
|
||||
import {
|
||||
@@ -36,6 +33,54 @@ import type { CoreConfig } from "./types.js";
|
||||
|
||||
const channel = "matrix" as const;
|
||||
|
||||
type MatrixOnboardingStatus = {
|
||||
channel: typeof channel;
|
||||
configured: boolean;
|
||||
statusLines: string[];
|
||||
selectionHint?: string;
|
||||
quickstartScore?: number;
|
||||
};
|
||||
|
||||
type MatrixAccountOverrides = Partial<Record<typeof channel, string>>;
|
||||
|
||||
type MatrixOnboardingConfigureContext = {
|
||||
cfg: CoreConfig;
|
||||
runtime: RuntimeEnv;
|
||||
prompter: WizardPrompter;
|
||||
options?: unknown;
|
||||
forceAllowFrom: boolean;
|
||||
accountOverrides: MatrixAccountOverrides;
|
||||
shouldPromptAccountIds: boolean;
|
||||
};
|
||||
|
||||
type MatrixOnboardingInteractiveContext = MatrixOnboardingConfigureContext & {
|
||||
configured: boolean;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
type MatrixOnboardingAdapter = {
|
||||
channel: typeof channel;
|
||||
getStatus: (ctx: {
|
||||
cfg: CoreConfig;
|
||||
options?: unknown;
|
||||
accountOverrides: MatrixAccountOverrides;
|
||||
}) => Promise<MatrixOnboardingStatus>;
|
||||
configure: (
|
||||
ctx: MatrixOnboardingConfigureContext,
|
||||
) => Promise<{ cfg: CoreConfig; accountId?: string }>;
|
||||
configureInteractive?: (
|
||||
ctx: MatrixOnboardingInteractiveContext,
|
||||
) => Promise<{ cfg: CoreConfig; accountId?: string } | "skip">;
|
||||
afterConfigWritten?: (ctx: {
|
||||
previousCfg: CoreConfig;
|
||||
cfg: CoreConfig;
|
||||
accountId: string;
|
||||
runtime: RuntimeEnv;
|
||||
}) => Promise<void> | void;
|
||||
dmPolicy?: ChannelSetupDmPolicy;
|
||||
disable?: (cfg: CoreConfig) => CoreConfig;
|
||||
};
|
||||
|
||||
function resolveMatrixOnboardingAccountId(cfg: CoreConfig, accountId?: string): string {
|
||||
return normalizeAccountId(
|
||||
accountId?.trim() || resolveDefaultMatrixAccountId(cfg) || DEFAULT_ACCOUNT_ID,
|
||||
@@ -473,7 +518,7 @@ async function runMatrixConfigure(params: {
|
||||
return { cfg: next, accountId };
|
||||
}
|
||||
|
||||
export const matrixOnboardingAdapter: ChannelSetupWizardAdapter = {
|
||||
export const matrixOnboardingAdapter: MatrixOnboardingAdapter = {
|
||||
channel,
|
||||
getStatus: async ({ cfg, accountOverrides }) => {
|
||||
const resolvedCfg = cfg as CoreConfig;
|
||||
|
||||
Reference in New Issue
Block a user