mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 10:12:10 +00:00
* refactor: centralize inbound supplemental context * refactor: trim supplemental finalizer typing * docs: clarify supplemental context projection * refactor: move inbound finalization into core * refactor: simplify channel inbound facts * refactor: fold supplemental media into inbound finalizer * refactor: migrate channel inbound callers to builder * docs: mark inbound finalizer compat types deprecated * refactor: wire runtime turn context builder * refactor: replace channel turn runtime API * fix: respect discord quote visibility * fix: avoid deprecated line dispatch helper * refactor: deprecate channel message SDK seams * docs: trim channel outbound SDK page * test: migrate irc inbound assertion * refactor: deprecate outbound SDK facades * refactor: deprecate channel helper SDK facades * refactor: deprecate channel streaming SDK facade * refactor: move direct dm helpers into inbound SDK * chore: mark legacy test-utils SDK alias deprecated * refactor: remove unused allow-from read helper * refactor: route remaining channel dispatch through core * refactor: enforce modern extension SDK imports * test: give slow image root tests more time * ci: support node fallback on windows * fix: add transcripts tool display metadata * refactor: trim legacy channel test seams * fix: preserve channel compat after rebase * fix: keep deprecated channel inbound aliases * fix: preserve discord thread context visibility * fix: clean final rebase conflicts * fix: preserve channel message dispatch aliases * fix: sync channel refactor after rebase * fix: sync channel refactor after latest main * fix: dedupe memory-core subagent mock * test: align clickclack inbound dispatch assertions * fix: sync plugin sdk api hash after rebase * fix: sync channel refactor after latest main * fix: sync plugin sdk api hash after rebase * fix: sync plugin sdk api hash after latest main * test: remove stale inbound context awaits
642 lines
21 KiB
TypeScript
642 lines
21 KiB
TypeScript
import { setTimeout as sleep } from "node:timers/promises";
|
|
import type {
|
|
ChannelApprovalCapabilityHandlerContext,
|
|
PendingApprovalView,
|
|
ResolvedApprovalView,
|
|
} from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
import { buildChannelApprovalNativeTargetKey } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
import {
|
|
buildExecApprovalPendingReplyPayload,
|
|
buildPluginApprovalPendingReplyPayload,
|
|
type ExecApprovalReplyDecision,
|
|
} from "openclaw/plugin-sdk/approval-reply-runtime";
|
|
import { buildPluginApprovalResolvedReplyPayload } from "openclaw/plugin-sdk/approval-runtime";
|
|
import type {
|
|
ExecApprovalRequest,
|
|
PluginApprovalRequest,
|
|
} from "openclaw/plugin-sdk/approval-runtime";
|
|
import {
|
|
listMessageReceiptPlatformIds,
|
|
resolveMessageReceiptPrimaryId,
|
|
} from "openclaw/plugin-sdk/channel-outbound";
|
|
import { normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import {
|
|
buildMatrixApprovalReactionHint,
|
|
listMatrixApprovalReactionBindings,
|
|
registerMatrixApprovalReactionTarget,
|
|
unregisterMatrixApprovalReactionTarget,
|
|
} from "./approval-reactions.js";
|
|
import {
|
|
isMatrixAnyApprovalClientEnabled,
|
|
shouldHandleMatrixApprovalRequest,
|
|
} from "./exec-approvals.js";
|
|
import { resolveMatrixAccount } from "./matrix/accounts.js";
|
|
import { deleteMatrixMessage, editMatrixMessage } from "./matrix/actions/messages.js";
|
|
import { repairMatrixDirectRooms } from "./matrix/direct-management.js";
|
|
import type { MatrixClient } from "./matrix/sdk.js";
|
|
import {
|
|
reactMatrixMessage,
|
|
sendMessageMatrix,
|
|
sendSingleTextMessageMatrix,
|
|
} from "./matrix/send.js";
|
|
import { resolveMatrixTargetIdentity } from "./matrix/target-ids.js";
|
|
import type { CoreConfig } from "./types.js";
|
|
|
|
// OpenClaw Matrix custom event content for capable clients; body and reactions remain fallback.
|
|
const MATRIX_APPROVAL_METADATA_KEY = "com.openclaw.approval" as const;
|
|
|
|
type PendingMessage = {
|
|
roomId: string;
|
|
platformMessageIds: readonly string[];
|
|
reactionEventId: string;
|
|
};
|
|
type PreparedMatrixTarget = {
|
|
to: string;
|
|
roomId: string;
|
|
threadId?: string;
|
|
};
|
|
type MatrixApprovalMetadataAction = {
|
|
kind: PendingApprovalView["actions"][number]["kind"];
|
|
decision?: ExecApprovalReplyDecision;
|
|
label: string;
|
|
style: PendingApprovalView["actions"][number]["style"];
|
|
command: string;
|
|
};
|
|
type MutablePluginApprovalActions = Array<
|
|
NonNullable<PluginApprovalRequest["request"]["actions"]>[number]
|
|
>;
|
|
type MatrixApprovalMetadataBase = {
|
|
version: 1;
|
|
type: "approval.request";
|
|
id: string;
|
|
state: "pending";
|
|
kind: PendingApprovalView["approvalKind"];
|
|
phase: "pending";
|
|
title: string;
|
|
description?: string;
|
|
expiresAtMs: number;
|
|
metadata: PendingApprovalView["metadata"];
|
|
allowedDecisions: ExecApprovalReplyDecision[];
|
|
actions: MatrixApprovalMetadataAction[];
|
|
};
|
|
type MatrixExecApprovalMetadata = MatrixApprovalMetadataBase & {
|
|
kind: "exec";
|
|
ask?: string;
|
|
agentId?: string;
|
|
commandText: string;
|
|
commandPreview?: string;
|
|
cwd?: string;
|
|
envKeys?: readonly string[];
|
|
host?: string;
|
|
nodeId?: string;
|
|
sessionKey?: string;
|
|
};
|
|
type MatrixPluginApprovalSeverity = Extract<
|
|
PendingApprovalView,
|
|
{ approvalKind: "plugin" }
|
|
>["severity"];
|
|
type MatrixPluginApprovalMetadata = MatrixApprovalMetadataBase & {
|
|
kind: "plugin";
|
|
agentId?: string;
|
|
pluginId?: string;
|
|
toolName?: string;
|
|
severity: MatrixPluginApprovalSeverity;
|
|
};
|
|
type MatrixApprovalMetadata = MatrixExecApprovalMetadata | MatrixPluginApprovalMetadata;
|
|
type MatrixApprovalExtraContent = {
|
|
[MATRIX_APPROVAL_METADATA_KEY]: MatrixApprovalMetadata;
|
|
};
|
|
type PendingApprovalContent = {
|
|
approvalId: string;
|
|
text: string;
|
|
allowedDecisions: readonly ExecApprovalReplyDecision[];
|
|
extraContent: MatrixApprovalExtraContent;
|
|
};
|
|
type ReactionTargetRef = {
|
|
roomId: string;
|
|
eventId: string;
|
|
};
|
|
type MatrixRawApprovalTarget = {
|
|
to: string;
|
|
threadId?: string | number | null;
|
|
};
|
|
type MatrixPrepareTargetParams = {
|
|
cfg: CoreConfig;
|
|
accountId?: string | null;
|
|
gatewayUrl?: string;
|
|
context?: unknown;
|
|
rawTarget: MatrixRawApprovalTarget;
|
|
};
|
|
|
|
const MATRIX_APPROVAL_DELIVERY_ATTEMPTS = 3;
|
|
const MATRIX_APPROVAL_DELIVERY_RETRY_DELAY_MS = 250;
|
|
|
|
export type MatrixApprovalHandlerDeps = {
|
|
nowMs?: () => number;
|
|
sendMessage?: typeof sendMessageMatrix;
|
|
sendSingleTextMessage?: typeof sendSingleTextMessageMatrix;
|
|
reactMessage?: typeof reactMatrixMessage;
|
|
editMessage?: typeof editMatrixMessage;
|
|
deleteMessage?: typeof deleteMatrixMessage;
|
|
repairDirectRooms?: typeof repairMatrixDirectRooms;
|
|
};
|
|
|
|
export type MatrixApprovalHandlerContext = {
|
|
client: MatrixClient;
|
|
deps?: MatrixApprovalHandlerDeps;
|
|
};
|
|
|
|
function resolveHandlerContext(params: ChannelApprovalCapabilityHandlerContext): {
|
|
accountId: string;
|
|
context: MatrixApprovalHandlerContext;
|
|
} | null {
|
|
const context = params.context as MatrixApprovalHandlerContext | undefined;
|
|
const accountId = params.accountId?.trim() || "";
|
|
if (!context?.client || !accountId) {
|
|
return null;
|
|
}
|
|
return { accountId, context };
|
|
}
|
|
|
|
function normalizePendingMessageIds(entry: PendingMessage): string[] {
|
|
return normalizeUniqueStringEntries(entry.platformMessageIds);
|
|
}
|
|
|
|
function normalizeReactionTargetRef(params: ReactionTargetRef): ReactionTargetRef | null {
|
|
const roomId = params.roomId.trim();
|
|
const eventId = params.eventId.trim();
|
|
if (!roomId || !eventId) {
|
|
return null;
|
|
}
|
|
return { roomId, eventId };
|
|
}
|
|
|
|
function normalizeThreadId(value?: string | number | null): string | undefined {
|
|
const trimmed = value == null ? "" : String(value).trim();
|
|
return trimmed || undefined;
|
|
}
|
|
|
|
function isSingleMatrixMessageLimitError(error: unknown): boolean {
|
|
return (
|
|
error instanceof Error && error.message.includes("Matrix single-message text exceeds limit")
|
|
);
|
|
}
|
|
|
|
async function retryMatrixApprovalDelivery<T>(
|
|
operation: () => Promise<T>,
|
|
params: { shouldRetry?: (error: unknown) => boolean } = {},
|
|
): Promise<T> {
|
|
let lastError: unknown;
|
|
for (let attempt = 1; attempt <= MATRIX_APPROVAL_DELIVERY_ATTEMPTS; attempt += 1) {
|
|
try {
|
|
return await operation();
|
|
} catch (error) {
|
|
lastError = error;
|
|
if (attempt === MATRIX_APPROVAL_DELIVERY_ATTEMPTS || params.shouldRetry?.(error) === false) {
|
|
break;
|
|
}
|
|
await sleep(MATRIX_APPROVAL_DELIVERY_RETRY_DELAY_MS * attempt);
|
|
}
|
|
}
|
|
throw lastError;
|
|
}
|
|
|
|
async function prepareTarget(
|
|
params: MatrixPrepareTargetParams,
|
|
): Promise<PreparedMatrixTarget | null> {
|
|
const resolved = resolveHandlerContext(params);
|
|
if (!resolved) {
|
|
return null;
|
|
}
|
|
const target = resolveMatrixTargetIdentity(params.rawTarget.to);
|
|
if (!target) {
|
|
return null;
|
|
}
|
|
const threadId = normalizeThreadId(params.rawTarget.threadId);
|
|
if (target.kind === "user") {
|
|
const account = resolveMatrixAccount({
|
|
cfg: params.cfg,
|
|
accountId: resolved.accountId,
|
|
});
|
|
const repairDirectRooms = resolved.context.deps?.repairDirectRooms ?? repairMatrixDirectRooms;
|
|
const repaired = await retryMatrixApprovalDelivery(
|
|
async () =>
|
|
await repairDirectRooms({
|
|
client: resolved.context.client,
|
|
remoteUserId: target.id,
|
|
encrypted: account.config.encryption === true,
|
|
}),
|
|
);
|
|
if (!repaired.activeRoomId) {
|
|
return null;
|
|
}
|
|
return {
|
|
to: `room:${repaired.activeRoomId}`,
|
|
roomId: repaired.activeRoomId,
|
|
threadId,
|
|
};
|
|
}
|
|
return {
|
|
to: `room:${target.id}`,
|
|
roomId: target.id,
|
|
threadId,
|
|
};
|
|
}
|
|
|
|
function buildMatrixApprovalMetadata(params: {
|
|
view: PendingApprovalView;
|
|
allowedDecisions: readonly ExecApprovalReplyDecision[];
|
|
}): MatrixApprovalMetadata {
|
|
const base: MatrixApprovalMetadataBase = {
|
|
version: 1,
|
|
type: "approval.request",
|
|
id: params.view.approvalId,
|
|
state: "pending",
|
|
kind: params.view.approvalKind,
|
|
phase: params.view.phase,
|
|
title: params.view.title,
|
|
expiresAtMs: params.view.expiresAtMs,
|
|
metadata: params.view.metadata,
|
|
allowedDecisions: Array.from(params.allowedDecisions),
|
|
actions: params.view.actions.map((action) => ({
|
|
kind: action.kind,
|
|
...(action.kind === "decision" ? { decision: action.decision } : {}),
|
|
label: action.label,
|
|
style: action.style,
|
|
command: action.command,
|
|
})),
|
|
...(params.view.description != null ? { description: params.view.description } : {}),
|
|
};
|
|
|
|
if (params.view.approvalKind === "plugin") {
|
|
return {
|
|
...base,
|
|
kind: "plugin",
|
|
severity: params.view.severity,
|
|
...(params.view.agentId != null ? { agentId: params.view.agentId } : {}),
|
|
...(params.view.pluginId != null ? { pluginId: params.view.pluginId } : {}),
|
|
...(params.view.toolName != null ? { toolName: params.view.toolName } : {}),
|
|
};
|
|
}
|
|
|
|
return {
|
|
...base,
|
|
kind: "exec",
|
|
commandText: params.view.commandText,
|
|
...(params.view.ask != null ? { ask: params.view.ask } : {}),
|
|
...(params.view.agentId != null ? { agentId: params.view.agentId } : {}),
|
|
...(params.view.commandPreview != null ? { commandPreview: params.view.commandPreview } : {}),
|
|
...(params.view.cwd != null ? { cwd: params.view.cwd } : {}),
|
|
...(params.view.envKeys != null ? { envKeys: params.view.envKeys } : {}),
|
|
...(params.view.host != null ? { host: params.view.host } : {}),
|
|
...(params.view.nodeId != null ? { nodeId: params.view.nodeId } : {}),
|
|
...(params.view.sessionKey != null ? { sessionKey: params.view.sessionKey } : {}),
|
|
};
|
|
}
|
|
|
|
function listDecisionActions(view: PendingApprovalView): ExecApprovalReplyDecision[] {
|
|
return view.actions.flatMap((action) => (action.kind === "decision" ? [action.decision] : []));
|
|
}
|
|
|
|
function listPluginApprovalActionDescriptors(
|
|
view: PendingApprovalView,
|
|
): NonNullable<PluginApprovalRequest["request"]["actions"]> | undefined {
|
|
if (view.approvalKind !== "plugin") {
|
|
return undefined;
|
|
}
|
|
|
|
const actions: MutablePluginApprovalActions = [];
|
|
for (const action of view.actions) {
|
|
if (
|
|
(action.kind !== "command" && action.kind !== "decision") ||
|
|
!action.command.trim() ||
|
|
!action.label.trim()
|
|
) {
|
|
continue;
|
|
}
|
|
if (action.kind === "command") {
|
|
actions.push({
|
|
kind: "command",
|
|
label: action.label,
|
|
style: action.style,
|
|
command: action.command,
|
|
});
|
|
continue;
|
|
}
|
|
actions.push({
|
|
kind: "decision",
|
|
label: action.label,
|
|
style: action.style,
|
|
command: action.command,
|
|
decision: action.decision,
|
|
});
|
|
}
|
|
return actions.length > 0 ? actions : undefined;
|
|
}
|
|
|
|
function buildPendingApprovalContent(params: {
|
|
view: PendingApprovalView;
|
|
nowMs: number;
|
|
}): PendingApprovalContent {
|
|
const allowedDecisions = listDecisionActions(params.view);
|
|
const pluginActions = listPluginApprovalActionDescriptors(params.view);
|
|
const payload =
|
|
params.view.approvalKind === "plugin"
|
|
? buildPluginApprovalPendingReplyPayload({
|
|
request: {
|
|
id: params.view.approvalId,
|
|
request: {
|
|
title: params.view.title,
|
|
description: params.view.description ?? "",
|
|
severity: params.view.severity,
|
|
toolName: params.view.toolName ?? undefined,
|
|
pluginId: params.view.pluginId ?? undefined,
|
|
agentId: params.view.agentId ?? undefined,
|
|
actions: pluginActions,
|
|
},
|
|
createdAtMs: 0,
|
|
expiresAtMs: params.view.expiresAtMs,
|
|
} satisfies PluginApprovalRequest,
|
|
nowMs: params.nowMs,
|
|
allowedDecisions,
|
|
})
|
|
: buildExecApprovalPendingReplyPayload({
|
|
approvalId: params.view.approvalId,
|
|
approvalSlug: params.view.approvalId.slice(0, 8),
|
|
approvalCommandId: params.view.approvalId,
|
|
ask: params.view.ask ?? undefined,
|
|
agentId: params.view.agentId ?? undefined,
|
|
allowedDecisions,
|
|
command: params.view.commandText,
|
|
cwd: params.view.cwd ?? undefined,
|
|
host: params.view.host === "node" ? "node" : "gateway",
|
|
nodeId: params.view.nodeId ?? undefined,
|
|
sessionKey: params.view.sessionKey ?? undefined,
|
|
expiresAtMs: params.view.expiresAtMs,
|
|
nowMs: params.nowMs,
|
|
});
|
|
const hint = buildMatrixApprovalReactionHint(allowedDecisions);
|
|
const text = payload.text ?? "";
|
|
return {
|
|
approvalId: params.view.approvalId,
|
|
text: hint ? (text ? `${hint}\n\n${text}` : hint) : text,
|
|
allowedDecisions,
|
|
extraContent: {
|
|
[MATRIX_APPROVAL_METADATA_KEY]: buildMatrixApprovalMetadata({
|
|
view: params.view,
|
|
allowedDecisions,
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildResolvedApprovalText(view: ResolvedApprovalView): string {
|
|
if (view.approvalKind === "plugin") {
|
|
return (
|
|
buildPluginApprovalResolvedReplyPayload({
|
|
resolved: {
|
|
id: view.approvalId,
|
|
decision: view.decision,
|
|
resolvedBy: view.resolvedBy ?? undefined,
|
|
ts: 0,
|
|
},
|
|
}).text ?? ""
|
|
);
|
|
}
|
|
const decisionLabel =
|
|
view.decision === "allow-once"
|
|
? "Allowed once"
|
|
: view.decision === "allow-always"
|
|
? "Allowed always"
|
|
: "Denied";
|
|
return [
|
|
`Exec approval: ${decisionLabel}`,
|
|
"",
|
|
"Command",
|
|
buildMarkdownCodeBlock(view.commandText),
|
|
].join("\n");
|
|
}
|
|
|
|
function buildMarkdownCodeBlock(text: string): string {
|
|
const longestFence = Math.max(...Array.from(text.matchAll(/`+/g), (match) => match[0].length), 0);
|
|
const fence = "`".repeat(Math.max(3, longestFence + 1));
|
|
return [fence, text, fence].join("\n");
|
|
}
|
|
|
|
export const matrixApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter<
|
|
PendingApprovalContent,
|
|
PreparedMatrixTarget,
|
|
PendingMessage,
|
|
ReactionTargetRef,
|
|
string
|
|
>({
|
|
eventKinds: ["exec", "plugin"],
|
|
availability: {
|
|
isConfigured: ({ cfg, accountId, context }) => {
|
|
const resolved = resolveHandlerContext({ cfg, accountId, context });
|
|
if (!resolved) {
|
|
return false;
|
|
}
|
|
return isMatrixAnyApprovalClientEnabled({
|
|
cfg,
|
|
accountId: resolved.accountId,
|
|
});
|
|
},
|
|
shouldHandle: ({ cfg, accountId, request, context }) => {
|
|
const resolved = resolveHandlerContext({ cfg, accountId, context });
|
|
if (!resolved) {
|
|
return false;
|
|
}
|
|
return shouldHandleMatrixApprovalRequest({
|
|
cfg,
|
|
accountId: resolved.accountId,
|
|
request: request as ExecApprovalRequest | PluginApprovalRequest,
|
|
});
|
|
},
|
|
},
|
|
presentation: {
|
|
buildPendingPayload: ({ view, nowMs }) =>
|
|
buildPendingApprovalContent({
|
|
view,
|
|
nowMs,
|
|
}),
|
|
buildResolvedResult: ({ view }) => ({
|
|
kind: "update",
|
|
payload: buildResolvedApprovalText(view),
|
|
}),
|
|
buildExpiredResult: () => ({ kind: "delete" }),
|
|
},
|
|
transport: {
|
|
prepareTarget: ({ cfg, accountId, context, plannedTarget }) => {
|
|
return prepareTarget({
|
|
cfg,
|
|
accountId,
|
|
context,
|
|
rawTarget: plannedTarget.target,
|
|
}).then((preparedTarget) =>
|
|
preparedTarget
|
|
? {
|
|
dedupeKey: buildChannelApprovalNativeTargetKey({
|
|
to: preparedTarget.roomId,
|
|
threadId: preparedTarget.threadId,
|
|
}),
|
|
target: preparedTarget,
|
|
}
|
|
: null,
|
|
);
|
|
},
|
|
deliverPending: async ({ cfg, accountId, context, preparedTarget, pendingPayload, view }) => {
|
|
const resolved = resolveHandlerContext({ cfg, accountId, context });
|
|
if (!resolved) {
|
|
return null;
|
|
}
|
|
const sendSingleTextMessage =
|
|
resolved.context.deps?.sendSingleTextMessage ?? sendSingleTextMessageMatrix;
|
|
const reactMessage = resolved.context.deps?.reactMessage ?? reactMatrixMessage;
|
|
let result;
|
|
try {
|
|
result = await retryMatrixApprovalDelivery(
|
|
async () =>
|
|
await sendSingleTextMessage(preparedTarget.to, pendingPayload.text, {
|
|
cfg: cfg as CoreConfig,
|
|
accountId: resolved.accountId,
|
|
client: resolved.context.client,
|
|
threadId: preparedTarget.threadId,
|
|
extraContent: pendingPayload.extraContent,
|
|
}),
|
|
{ shouldRetry: (error) => !isSingleMatrixMessageLimitError(error) },
|
|
);
|
|
} catch (error) {
|
|
if (!isSingleMatrixMessageLimitError(error)) {
|
|
throw error;
|
|
}
|
|
const sendMessage = resolved.context.deps?.sendMessage ?? sendMessageMatrix;
|
|
result = await retryMatrixApprovalDelivery(
|
|
async () =>
|
|
await sendMessage(preparedTarget.to, pendingPayload.text, {
|
|
cfg: cfg as CoreConfig,
|
|
accountId: resolved.accountId,
|
|
client: resolved.context.client,
|
|
threadId: preparedTarget.threadId,
|
|
extraContent: pendingPayload.extraContent,
|
|
}),
|
|
);
|
|
}
|
|
const receiptMessageIds = listMessageReceiptPlatformIds(result.receipt);
|
|
const platformMessageIds = receiptMessageIds.length
|
|
? receiptMessageIds
|
|
: [result.messageId.trim()].filter(Boolean);
|
|
const reactionEventId =
|
|
resolveMessageReceiptPrimaryId(result.receipt) ||
|
|
result.primaryMessageId?.trim() ||
|
|
platformMessageIds[0] ||
|
|
result.messageId.trim();
|
|
registerMatrixApprovalReactionTarget({
|
|
roomId: result.roomId,
|
|
eventId: reactionEventId,
|
|
approvalId: pendingPayload.approvalId,
|
|
allowedDecisions: pendingPayload.allowedDecisions,
|
|
ttlMs: view.expiresAtMs - Date.now(),
|
|
});
|
|
await Promise.allSettled(
|
|
listMatrixApprovalReactionBindings(pendingPayload.allowedDecisions).map(
|
|
async ({ emoji }) => {
|
|
await reactMessage(result.roomId, reactionEventId, emoji, {
|
|
cfg: cfg as CoreConfig,
|
|
accountId: resolved.accountId,
|
|
client: resolved.context.client,
|
|
});
|
|
},
|
|
),
|
|
);
|
|
return {
|
|
roomId: result.roomId,
|
|
platformMessageIds,
|
|
reactionEventId,
|
|
};
|
|
},
|
|
updateEntry: async ({ cfg, accountId, context, entry, payload }) => {
|
|
const resolved = resolveHandlerContext({ cfg, accountId, context });
|
|
if (!resolved) {
|
|
return;
|
|
}
|
|
const editMessage = resolved.context.deps?.editMessage ?? editMatrixMessage;
|
|
const deleteMessage = resolved.context.deps?.deleteMessage ?? deleteMatrixMessage;
|
|
const [primaryMessageId, ...staleMessageIds] = normalizePendingMessageIds(entry);
|
|
if (!primaryMessageId) {
|
|
return;
|
|
}
|
|
const text = payload;
|
|
await Promise.allSettled([
|
|
editMessage(entry.roomId, primaryMessageId, text, {
|
|
cfg: cfg as CoreConfig,
|
|
accountId: resolved.accountId,
|
|
client: resolved.context.client,
|
|
}),
|
|
...staleMessageIds.map(async (messageId) => {
|
|
await deleteMessage(entry.roomId, messageId, {
|
|
cfg: cfg as CoreConfig,
|
|
accountId: resolved.accountId,
|
|
client: resolved.context.client,
|
|
reason: "approval resolved",
|
|
});
|
|
}),
|
|
]);
|
|
},
|
|
deleteEntry: async ({ cfg, accountId, context, entry, phase }) => {
|
|
const resolved = resolveHandlerContext({ cfg, accountId, context });
|
|
if (!resolved) {
|
|
return;
|
|
}
|
|
const deleteMessage = resolved.context.deps?.deleteMessage ?? deleteMatrixMessage;
|
|
await Promise.allSettled(
|
|
normalizePendingMessageIds(entry).map(async (messageId) => {
|
|
await deleteMessage(entry.roomId, messageId, {
|
|
cfg: cfg as CoreConfig,
|
|
accountId: resolved.accountId,
|
|
client: resolved.context.client,
|
|
reason: phase === "expired" ? "approval expired" : "approval resolved",
|
|
});
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
interactions: {
|
|
bindPending: (params) => {
|
|
const target = normalizeReactionTargetRef({
|
|
roomId: params.entry.roomId,
|
|
eventId: params.entry.reactionEventId,
|
|
});
|
|
if (!target) {
|
|
return null;
|
|
}
|
|
registerMatrixApprovalReactionTarget({
|
|
roomId: target.roomId,
|
|
eventId: target.eventId,
|
|
approvalId: params.pendingPayload.approvalId,
|
|
allowedDecisions: params.pendingPayload.allowedDecisions,
|
|
ttlMs: params.view.expiresAtMs - Date.now(),
|
|
});
|
|
return target;
|
|
},
|
|
unbindPending: (params) => {
|
|
const target = normalizeReactionTargetRef(params.binding);
|
|
if (!target) {
|
|
return;
|
|
}
|
|
unregisterMatrixApprovalReactionTarget(target);
|
|
},
|
|
cancelDelivered: (params) => {
|
|
const target = normalizeReactionTargetRef({
|
|
roomId: params.entry.roomId,
|
|
eventId: params.entry.reactionEventId,
|
|
});
|
|
if (!target) {
|
|
return;
|
|
}
|
|
unregisterMatrixApprovalReactionTarget(target);
|
|
},
|
|
},
|
|
});
|