import type { AgentToolResult } from "@mariozechner/pi-agent-core" ;
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime" ;
import { resolveMatrixAccountConfig } from "./matrix/accounts.js" ;
import {
bootstrapMatrixVerification,
acceptMatrixVerification,
cancelMatrixVerification,
confirmMatrixVerificationReciprocateQr,
confirmMatrixVerificationSas,
deleteMatrixMessage,
editMatrixMessage,
generateMatrixVerificationQr,
getMatrixEncryptionStatus,
getMatrixRoomKeyBackupStatus,
getMatrixVerificationStatus,
getMatrixMemberInfo,
getMatrixRoomInfo,
getMatrixVerificationSas,
listMatrixPins,
listMatrixReactions,
listMatrixVerifications,
mismatchMatrixVerificationSas,
pinMatrixMessage,
readMatrixMessages,
requestMatrixVerification,
restoreMatrixRoomKeyBackup,
removeMatrixReactions,
scanMatrixVerificationQr,
sendMatrixMessage,
startMatrixVerification,
unpinMatrixMessage,
voteMatrixPoll,
verifyMatrixRecoveryKey,
} from "./matrix/actions.js" ;
import { reactMatrixMessage } from "./matrix/send.js" ;
import { applyMatrixProfileUpdate } from "./profile-update.js" ;
import {
createActionGate,
jsonResult,
readNumberParam,
readReactionParams,
readStringArrayParam,
readStringParam,
} from "./runtime-api.js" ;
import type { CoreConfig } from "./types.js" ;
const messageActions = new Set(["sendMessage" , "editMessage" , "deleteMessage" , "readMessages" ]);
const reactionActions = new Set(["react" , "reactions" ]);
const pinActions = new Set(["pinMessage" , "unpinMessage" , "listPins" ]);
const pollActions = new Set(["pollVote" ]);
const profileActions = new Set(["setProfile" ]);
const verificationActions = new Set([
"encryptionStatus" ,
"verificationList" ,
"verificationRequest" ,
"verificationAccept" ,
"verificationCancel" ,
"verificationStart" ,
"verificationGenerateQr" ,
"verificationScanQr" ,
"verificationSas" ,
"verificationConfirm" ,
"verificationMismatch" ,
"verificationConfirmQr" ,
"verificationStatus" ,
"verificationBootstrap" ,
"verificationRecoveryKey" ,
"verificationBackupStatus" ,
"verificationBackupRestore" ,
]);
function readRoomId(params: Record<string, unknown>, required = true ): string {
const direct = readStringParam(params, "roomId" ) ?? readStringParam(params, "channelId" );
if (direct) {
return direct;
}
if (!required) {
return readStringParam(params, "to" ) ?? "" ;
}
return readStringParam(params, "to" , { required: true });
}
function toSnakeCaseKey(key: string): string {
return normalizeOptionalLowercaseString(
key.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2" ).replace(/([a-z0-9 ])([A-Z])/g, "$1_$2" ),
)!;
}
function readRawParam(params: Record<string, unknown>, key: string): unknown {
if (Object.hasOwn(params, key)) {
return params[key];
}
const snakeKey = toSnakeCaseKey(key);
if (snakeKey !== key && Object.hasOwn(params, snakeKey)) {
return params[snakeKey];
}
return undefined;
}
function readStringAliasParam(
params: Record<string, unknown>,
keys: string[],
options: { required?: boolean } = {},
): string | undefined {
for (const key of keys) {
const raw = readRawParam(params, key);
if (typeof raw !== "string" ) {
continue ;
}
const trimmed = raw.trim();
if (trimmed) {
return trimmed;
}
}
if (options.required) {
throw new Error(`${keys[0 ]} required`);
}
return undefined;
}
function readNumericArrayParam(
params: Record<string, unknown>,
key: string,
options: { integer?: boolean } = {},
): number[] {
const { integer = false } = options;
const raw = readRawParam(params, key);
if (raw === undefined) {
return [];
}
return (Array.isArray(raw) ? raw : [raw])
.map((value) => {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" ) {
const trimmed = value.trim();
if (!trimmed) {
return null ;
}
const parsed = Number(trimmed);
return Number.isFinite(parsed) ? parsed : null ;
}
return null ;
})
.filter((value): value is number => value !== null )
.map((value) => (integer ? Math.trunc(value) : value));
}
export async function handleMatrixAction(
params: Record<string, unknown>,
cfg: CoreConfig,
opts: { mediaLocalRoots?: readonly string[] } = {},
): Promise<AgentToolResult<unknown>> {
const action = readStringParam(params, "action" , { required: true });
const accountId = readStringParam(params, "accountId" ) ?? undefined;
const isActionEnabled = createActionGate(resolveMatrixAccountConfig({ cfg, accountId }).actions);
const clientOpts = {
cfg,
...(accountId ? { accountId } : {}),
};
if (reactionActions.has(action)) {
if (!isActionEnabled("reactions" )) {
throw new Error("Matrix reactions are disabled." );
}
const roomId = readRoomId(params);
const messageId = readStringParam(params, "messageId" , { required: true });
if (action === "react" ) {
const { emoji, remove, isEmpty } = readReactionParams(params, {
removeErrorMessage: "Emoji is required to remove a Matrix reaction." ,
});
if (remove || isEmpty) {
const result = await removeMatrixReactions(roomId, messageId, {
...clientOpts,
emoji: remove ? emoji : undefined,
});
return jsonResult({ ok: true , removed: result.removed });
}
await reactMatrixMessage(roomId, messageId, emoji, clientOpts);
return jsonResult({ ok: true , added: emoji });
}
const limit = readNumberParam(params, "limit" , { integer: true });
const reactions = await listMatrixReactions(roomId, messageId, {
...clientOpts,
limit: limit ?? undefined,
});
return jsonResult({ ok: true , reactions });
}
if (pollActions.has(action)) {
const roomId = readRoomId(params);
const pollId = readStringAliasParam(params, ["pollId" , "messageId" ], { required: true });
if (!pollId) {
throw new Error("pollId required" );
}
const optionId = readStringParam(params, "pollOptionId" );
const optionIndex = readNumberParam(params, "pollOptionIndex" , { integer: true });
const optionIds = [
...(readStringArrayParam(params, "pollOptionIds" ) ?? []),
...(optionId ? [optionId] : []),
];
const optionIndexes = [
...readNumericArrayParam(params, "pollOptionIndexes" , { integer: true }),
...(optionIndex !== undefined ? [optionIndex] : []),
];
const result = await voteMatrixPoll(roomId, pollId, {
...clientOpts,
optionIds,
optionIndexes,
});
return jsonResult({ ok: true , result });
}
if (messageActions.has(action)) {
if (!isActionEnabled("messages" )) {
throw new Error("Matrix messages are disabled." );
}
switch (action) {
case "sendMessage" : {
const to = readStringParam(params, "to" , { required: true });
const mediaUrl =
readStringParam(params, "mediaUrl" , { trim: false }) ??
readStringParam(params, "media" , { trim: false }) ??
readStringParam(params, "filePath" , { trim: false }) ??
readStringParam(params, "path" , { trim: false });
const content = readStringParam(params, "content" , {
required: !mediaUrl,
allowEmpty: true ,
});
const replyToId =
readStringParam(params, "replyToId" ) ?? readStringParam(params, "replyTo" );
const threadId = readStringParam(params, "threadId" );
const audioAsVoice =
typeof readRawParam(params, "audioAsVoice" ) === "boolean"
? (readRawParam(params, "audioAsVoice" ) as boolean )
: typeof readRawParam(params, "asVoice" ) === "boolean"
? (readRawParam(params, "asVoice" ) as boolean )
: undefined;
const result = await sendMatrixMessage(to, content, {
mediaUrl: mediaUrl ?? undefined,
mediaLocalRoots: opts.mediaLocalRoots,
replyToId: replyToId ?? undefined,
threadId: threadId ?? undefined,
audioAsVoice,
...clientOpts,
});
return jsonResult({ ok: true , result });
}
case "editMessage" : {
const roomId = readRoomId(params);
const messageId = readStringParam(params, "messageId" , { required: true });
const content = readStringParam(params, "content" , { required: true });
const result = await editMatrixMessage(roomId, messageId, content, clientOpts);
return jsonResult({ ok: true , result });
}
case "deleteMessage" : {
const roomId = readRoomId(params);
const messageId = readStringParam(params, "messageId" , { required: true });
const reason = readStringParam(params, "reason" );
await deleteMatrixMessage(roomId, messageId, {
reason: reason ?? undefined,
...clientOpts,
});
return jsonResult({ ok: true , deleted: true });
}
case "readMessages" : {
const roomId = readRoomId(params);
const limit = readNumberParam(params, "limit" , { integer: true });
const before = readStringParam(params, "before" );
const after = readStringParam(params, "after" );
const result = await readMatrixMessages(roomId, {
limit: limit ?? undefined,
before: before ?? undefined,
after: after ?? undefined,
...clientOpts,
});
return jsonResult({ ok: true , ...result });
}
default :
break ;
}
}
if (pinActions.has(action)) {
if (!isActionEnabled("pins" )) {
throw new Error("Matrix pins are disabled." );
}
const roomId = readRoomId(params);
if (action === "pinMessage" ) {
const messageId = readStringParam(params, "messageId" , { required: true });
const result = await pinMatrixMessage(roomId, messageId, clientOpts);
return jsonResult({ ok: true , pinned: result.pinned });
}
if (action === "unpinMessage" ) {
const messageId = readStringParam(params, "messageId" , { required: true });
const result = await unpinMatrixMessage(roomId, messageId, clientOpts);
return jsonResult({ ok: true , pinned: result.pinned });
}
const result = await listMatrixPins(roomId, clientOpts);
return jsonResult({ ok: true , pinned: result.pinned, events: result.events });
}
if (profileActions.has(action)) {
if (!isActionEnabled("profile" )) {
throw new Error("Matrix profile updates are disabled." );
}
const avatarPath =
readStringParam(params, "avatarPath" ) ??
readStringParam(params, "path" ) ??
readStringParam(params, "filePath" );
const result = await applyMatrixProfileUpdate({
cfg,
account: accountId,
displayName: readStringParam(params, "displayName" ) ?? readStringParam(params, "name" ),
avatarUrl: readStringParam(params, "avatarUrl" ),
avatarPath,
mediaLocalRoots: opts.mediaLocalRoots,
});
return jsonResult({ ok: true , ...result });
}
if (action === "memberInfo" ) {
if (!isActionEnabled("memberInfo" )) {
throw new Error("Matrix member info is disabled." );
}
const userId = readStringParam(params, "userId" , { required: true });
const roomId = readStringParam(params, "roomId" ) ?? readStringParam(params, "channelId" );
const result = await getMatrixMemberInfo(userId, {
roomId: roomId ?? undefined,
...clientOpts,
});
return jsonResult({ ok: true , member: result });
}
if (action === "channelInfo" ) {
if (!isActionEnabled("channelInfo" )) {
throw new Error("Matrix room info is disabled." );
}
const roomId = readRoomId(params);
const result = await getMatrixRoomInfo(roomId, clientOpts);
return jsonResult({ ok: true , room: result });
}
if (verificationActions.has(action)) {
if (!isActionEnabled("verification" )) {
throw new Error("Matrix verification actions are disabled." );
}
const requestId =
readStringParam(params, "requestId" ) ??
readStringParam(params, "verificationId" ) ??
readStringParam(params, "id" );
if (action === "encryptionStatus" ) {
const includeRecoveryKey = params.includeRecoveryKey === true ;
const status = await getMatrixEncryptionStatus({ includeRecoveryKey, ...clientOpts });
return jsonResult({ ok: true , status });
}
if (action === "verificationStatus" ) {
const includeRecoveryKey = params.includeRecoveryKey === true ;
const status = await getMatrixVerificationStatus({ includeRecoveryKey, ...clientOpts });
return jsonResult({ ok: true , status });
}
if (action === "verificationBootstrap" ) {
const recoveryKey =
readStringParam(params, "recoveryKey" , { trim: false }) ??
readStringParam(params, "key" , { trim: false });
const result = await bootstrapMatrixVerification({
recoveryKey: recoveryKey ?? undefined,
forceResetCrossSigning: params.forceResetCrossSigning === true ,
...clientOpts,
});
return jsonResult({ ok: result.success, result });
}
if (action === "verificationRecoveryKey" ) {
const recoveryKey =
readStringParam(params, "recoveryKey" , { trim: false }) ??
readStringParam(params, "key" , { trim: false });
const result = await verifyMatrixRecoveryKey(
readStringParam({ recoveryKey }, "recoveryKey" , { required: true , trim: false }),
clientOpts,
);
return jsonResult({ ok: result.success, result });
}
if (action === "verificationBackupStatus" ) {
const status = await getMatrixRoomKeyBackupStatus(clientOpts);
return jsonResult({ ok: true , status });
}
if (action === "verificationBackupRestore" ) {
const recoveryKey =
readStringParam(params, "recoveryKey" , { trim: false }) ??
readStringParam(params, "key" , { trim: false });
const result = await restoreMatrixRoomKeyBackup({
recoveryKey: recoveryKey ?? undefined,
...clientOpts,
});
return jsonResult({ ok: result.success, result });
}
if (action === "verificationList" ) {
const verifications = await listMatrixVerifications(clientOpts);
return jsonResult({ ok: true , verifications });
}
if (action === "verificationRequest" ) {
const userId = readStringParam(params, "userId" );
const deviceId = readStringParam(params, "deviceId" );
const roomId = readStringParam(params, "roomId" ) ?? readStringParam(params, "channelId" );
const ownUser = typeof params.ownUser === "boolean" ? params.ownUser : undefined;
const verification = await requestMatrixVerification({
ownUser,
userId: userId ?? undefined,
deviceId: deviceId ?? undefined,
roomId: roomId ?? undefined,
...clientOpts,
});
return jsonResult({ ok: true , verification });
}
if (action === "verificationAccept" ) {
const verification = await acceptMatrixVerification(
readStringParam({ requestId }, "requestId" , { required: true }),
clientOpts,
);
return jsonResult({ ok: true , verification });
}
if (action === "verificationCancel" ) {
const reason = readStringParam(params, "reason" );
const code = readStringParam(params, "code" );
const verification = await cancelMatrixVerification(
readStringParam({ requestId }, "requestId" , { required: true }),
{ reason: reason ?? undefined, code: code ?? undefined, ...clientOpts },
);
return jsonResult({ ok: true , verification });
}
if (action === "verificationStart" ) {
const methodRaw = readStringParam(params, "method" );
const method = normalizeOptionalLowercaseString(methodRaw);
if (method && method !== "sas" ) {
throw new Error(
"Matrix verificationStart only supports method=sas; use verificationGenerateQr/verificationScanQr for QR flows." ,
);
}
const verification = await startMatrixVerification(
readStringParam({ requestId }, "requestId" , { required: true }),
{ method: "sas" , ...clientOpts },
);
return jsonResult({ ok: true , verification });
}
if (action === "verificationGenerateQr" ) {
const qr = await generateMatrixVerificationQr(
readStringParam({ requestId }, "requestId" , { required: true }),
clientOpts,
);
return jsonResult({ ok: true , ...qr });
}
if (action === "verificationScanQr" ) {
const qrDataBase64 =
readStringParam(params, "qrDataBase64" ) ??
readStringParam(params, "qrData" ) ??
readStringParam(params, "qr" );
const verification = await scanMatrixVerificationQr(
readStringParam({ requestId }, "requestId" , { required: true }),
readStringParam({ qrDataBase64 }, "qrDataBase64" , { required: true }),
clientOpts,
);
return jsonResult({ ok: true , verification });
}
if (action === "verificationSas" ) {
const sas = await getMatrixVerificationSas(
readStringParam({ requestId }, "requestId" , { required: true }),
clientOpts,
);
return jsonResult({ ok: true , sas });
}
if (action === "verificationConfirm" ) {
const verification = await confirmMatrixVerificationSas(
readStringParam({ requestId }, "requestId" , { required: true }),
clientOpts,
);
return jsonResult({ ok: true , verification });
}
if (action === "verificationMismatch" ) {
const verification = await mismatchMatrixVerificationSas(
readStringParam({ requestId }, "requestId" , { required: true }),
clientOpts,
);
return jsonResult({ ok: true , verification });
}
if (action === "verificationConfirmQr" ) {
const verification = await confirmMatrixVerificationReciprocateQr(
readStringParam({ requestId }, "requestId" , { required: true }),
clientOpts,
);
return jsonResult({ ok: true , verification });
}
}
throw new Error(`Unsupported Matrix action: ${action}`);
}
Messung V0.5 in Prozent C=100 H=99 G=99
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland