mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:50:44 +00:00
190 lines
6.7 KiB
TypeScript
190 lines
6.7 KiB
TypeScript
import type {
|
|
ChannelApprovalCapabilityHandlerContext,
|
|
PendingApprovalView,
|
|
} 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 { buildPluginApprovalPendingReplyPayload } from "openclaw/plugin-sdk/approval-reply-runtime";
|
|
import {
|
|
buildApprovalInteractiveReplyFromActionDescriptors,
|
|
buildExecApprovalPendingReplyPayload,
|
|
type ExecApprovalPendingReplyParams,
|
|
type ExecApprovalRequest,
|
|
type PluginApprovalRequest,
|
|
} from "openclaw/plugin-sdk/infra-runtime";
|
|
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
import { resolveTelegramInlineButtons } from "./button-types.js";
|
|
import {
|
|
isTelegramExecApprovalHandlerConfigured,
|
|
shouldHandleTelegramExecApprovalRequest,
|
|
} from "./exec-approvals.js";
|
|
import { editMessageReplyMarkupTelegram, sendMessageTelegram, sendTypingTelegram } from "./send.js";
|
|
|
|
const log = createSubsystemLogger("telegram/approvals");
|
|
|
|
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
|
|
type PendingMessage = {
|
|
chatId: string;
|
|
messageId: string;
|
|
};
|
|
type TelegramPendingDelivery = {
|
|
text: string;
|
|
buttons: ReturnType<typeof resolveTelegramInlineButtons>;
|
|
};
|
|
|
|
export type TelegramExecApprovalHandlerDeps = {
|
|
nowMs?: () => number;
|
|
sendTyping?: typeof sendTypingTelegram;
|
|
sendMessage?: typeof sendMessageTelegram;
|
|
editReplyMarkup?: typeof editMessageReplyMarkupTelegram;
|
|
};
|
|
|
|
export type TelegramApprovalHandlerContext = {
|
|
token: string;
|
|
deps?: TelegramExecApprovalHandlerDeps;
|
|
};
|
|
|
|
function resolveHandlerContext(params: ChannelApprovalCapabilityHandlerContext): {
|
|
accountId: string;
|
|
context: TelegramApprovalHandlerContext;
|
|
} | null {
|
|
const context = params.context as TelegramApprovalHandlerContext | undefined;
|
|
const accountId = normalizeOptionalString(params.accountId) ?? "";
|
|
if (!context?.token || !accountId) {
|
|
return null;
|
|
}
|
|
return { accountId, context };
|
|
}
|
|
|
|
function buildPendingPayload(params: {
|
|
request: ApprovalRequest;
|
|
approvalKind: "exec" | "plugin";
|
|
nowMs: number;
|
|
view: PendingApprovalView;
|
|
}): TelegramPendingDelivery {
|
|
const payload =
|
|
params.approvalKind === "plugin"
|
|
? buildPluginApprovalPendingReplyPayload({
|
|
request: params.request as PluginApprovalRequest,
|
|
nowMs: params.nowMs,
|
|
})
|
|
: buildExecApprovalPendingReplyPayload({
|
|
approvalId: params.request.id,
|
|
approvalSlug: params.request.id.slice(0, 8),
|
|
approvalCommandId: params.request.id,
|
|
command: params.view.approvalKind === "exec" ? params.view.commandText : "",
|
|
cwd: params.view.approvalKind === "exec" ? (params.view.cwd ?? undefined) : undefined,
|
|
host:
|
|
params.view.approvalKind === "exec" && params.view.host === "node" ? "node" : "gateway",
|
|
nodeId:
|
|
params.view.approvalKind === "exec" ? (params.view.nodeId ?? undefined) : undefined,
|
|
allowedDecisions: params.view.actions.map((action) => action.decision),
|
|
expiresAtMs: params.request.expiresAtMs,
|
|
nowMs: params.nowMs,
|
|
} satisfies ExecApprovalPendingReplyParams);
|
|
return {
|
|
text: payload.text ?? "",
|
|
buttons: resolveTelegramInlineButtons({
|
|
interactive: buildApprovalInteractiveReplyFromActionDescriptors(params.view.actions),
|
|
}),
|
|
};
|
|
}
|
|
|
|
export const telegramApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter<
|
|
TelegramPendingDelivery,
|
|
{ chatId: string; messageThreadId?: number },
|
|
PendingMessage,
|
|
never
|
|
>({
|
|
eventKinds: ["exec", "plugin"],
|
|
availability: {
|
|
isConfigured: (params) => {
|
|
const resolved = resolveHandlerContext(params);
|
|
return resolved
|
|
? isTelegramExecApprovalHandlerConfigured({
|
|
cfg: params.cfg,
|
|
accountId: resolved.accountId,
|
|
})
|
|
: false;
|
|
},
|
|
shouldHandle: (params) => {
|
|
const resolved = resolveHandlerContext(params);
|
|
return resolved
|
|
? shouldHandleTelegramExecApprovalRequest({
|
|
cfg: params.cfg,
|
|
accountId: resolved.accountId,
|
|
request: params.request,
|
|
})
|
|
: false;
|
|
},
|
|
},
|
|
presentation: {
|
|
buildPendingPayload: ({ request, approvalKind, nowMs, view }) =>
|
|
buildPendingPayload({ request, approvalKind, nowMs, view }),
|
|
buildResolvedResult: () => ({ kind: "clear-actions" }),
|
|
buildExpiredResult: () => ({ kind: "clear-actions" }),
|
|
},
|
|
transport: {
|
|
prepareTarget: ({ plannedTarget }) => ({
|
|
dedupeKey: buildChannelApprovalNativeTargetKey(plannedTarget.target),
|
|
target: {
|
|
chatId: plannedTarget.target.to,
|
|
messageThreadId:
|
|
typeof plannedTarget.target.threadId === "number"
|
|
? plannedTarget.target.threadId
|
|
: undefined,
|
|
},
|
|
}),
|
|
deliverPending: async ({ cfg, accountId, context, preparedTarget, pendingPayload }) => {
|
|
const resolved = resolveHandlerContext({ cfg, accountId, context });
|
|
if (!resolved) {
|
|
return null;
|
|
}
|
|
const sendTyping = resolved.context.deps?.sendTyping ?? sendTypingTelegram;
|
|
const sendMessage = resolved.context.deps?.sendMessage ?? sendMessageTelegram;
|
|
await sendTyping(preparedTarget.chatId, {
|
|
cfg,
|
|
token: resolved.context.token,
|
|
accountId: resolved.accountId,
|
|
...(preparedTarget.messageThreadId != null
|
|
? { messageThreadId: preparedTarget.messageThreadId }
|
|
: {}),
|
|
}).catch(() => {});
|
|
const result = await sendMessage(preparedTarget.chatId, pendingPayload.text, {
|
|
cfg,
|
|
token: resolved.context.token,
|
|
accountId: resolved.accountId,
|
|
buttons: pendingPayload.buttons,
|
|
...(preparedTarget.messageThreadId != null
|
|
? { messageThreadId: preparedTarget.messageThreadId }
|
|
: {}),
|
|
});
|
|
return {
|
|
chatId: result.chatId,
|
|
messageId: result.messageId,
|
|
};
|
|
},
|
|
},
|
|
interactions: {
|
|
clearPendingActions: async ({ cfg, accountId, context, entry }) => {
|
|
const resolved = resolveHandlerContext({ cfg, accountId, context });
|
|
if (!resolved) {
|
|
return;
|
|
}
|
|
const editReplyMarkup =
|
|
resolved.context.deps?.editReplyMarkup ?? editMessageReplyMarkupTelegram;
|
|
await editReplyMarkup(entry.chatId, entry.messageId, [], {
|
|
cfg,
|
|
token: resolved.context.token,
|
|
accountId: resolved.accountId,
|
|
});
|
|
},
|
|
},
|
|
observe: {
|
|
onDeliveryError: ({ error, request }) => {
|
|
log.error(`telegram approvals: failed to send request ${request.id}: ${String(error)}`);
|
|
},
|
|
},
|
|
});
|