mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-20 09:44:45 +00:00
212 lines
6.9 KiB
TypeScript
212 lines
6.9 KiB
TypeScript
import { resolveSessionConversationRef } from "../channels/plugins/session-conversation.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
|
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
|
import {
|
|
doesApprovalRequestMatchChannelAccount,
|
|
resolvePersistedApprovalRequestSessionEntry,
|
|
} from "./approval-request-account-binding.js";
|
|
import type { ExecApprovalRequest } from "./exec-approvals.js";
|
|
import { resolveSessionDeliveryTarget } from "./outbound/targets.js";
|
|
import type { PluginApprovalRequest } from "./plugin-approvals.js";
|
|
|
|
export type ExecApprovalSessionTarget = {
|
|
channel?: string;
|
|
to: string;
|
|
accountId?: string;
|
|
threadId?: string | number;
|
|
};
|
|
|
|
export type ApprovalRequestSessionConversation = {
|
|
channel: string;
|
|
kind: "group" | "channel";
|
|
id: string;
|
|
rawId: string;
|
|
threadId?: string;
|
|
baseSessionKey: string;
|
|
baseConversationId: string;
|
|
parentConversationCandidates: string[];
|
|
};
|
|
|
|
type ApprovalRequestLike = ExecApprovalRequest | PluginApprovalRequest;
|
|
type ApprovalRequestOriginTargetResolver<TTarget> = {
|
|
cfg: OpenClawConfig;
|
|
request: ApprovalRequestLike;
|
|
channel: string;
|
|
accountId?: string | null;
|
|
resolveTurnSourceTarget: (request: ApprovalRequestLike) => TTarget | null;
|
|
resolveSessionTarget: (sessionTarget: ExecApprovalSessionTarget) => TTarget | null;
|
|
targetsMatch: (a: TTarget, b: TTarget) => boolean;
|
|
resolveFallbackTarget?: (request: ApprovalRequestLike) => TTarget | null;
|
|
};
|
|
|
|
function normalizeOptionalThreadValue(value?: string | number | null): string | number | undefined {
|
|
if (typeof value === "number") {
|
|
return Number.isFinite(value) ? value : undefined;
|
|
}
|
|
if (typeof value !== "string") {
|
|
return undefined;
|
|
}
|
|
const normalized = value.trim();
|
|
return normalized ? normalized : undefined;
|
|
}
|
|
|
|
function isExecApprovalRequest(request: ApprovalRequestLike): request is ExecApprovalRequest {
|
|
return "command" in request.request;
|
|
}
|
|
|
|
function toExecLikeApprovalRequest(request: ApprovalRequestLike): ExecApprovalRequest {
|
|
if (isExecApprovalRequest(request)) {
|
|
return request;
|
|
}
|
|
return {
|
|
id: request.id,
|
|
request: {
|
|
command: request.request.title,
|
|
sessionKey: request.request.sessionKey ?? undefined,
|
|
turnSourceChannel: request.request.turnSourceChannel ?? undefined,
|
|
turnSourceTo: request.request.turnSourceTo ?? undefined,
|
|
turnSourceAccountId: request.request.turnSourceAccountId ?? undefined,
|
|
turnSourceThreadId: request.request.turnSourceThreadId ?? undefined,
|
|
},
|
|
createdAtMs: request.createdAtMs,
|
|
expiresAtMs: request.expiresAtMs,
|
|
};
|
|
}
|
|
|
|
function normalizeOptionalChannel(value?: string | null): string | undefined {
|
|
return normalizeMessageChannel(value);
|
|
}
|
|
|
|
export function resolveApprovalRequestSessionConversation(params: {
|
|
request: ApprovalRequestLike;
|
|
channel?: string | null;
|
|
bundledFallback?: boolean;
|
|
}): ApprovalRequestSessionConversation | null {
|
|
const sessionKey = normalizeOptionalString(params.request.request.sessionKey);
|
|
if (!sessionKey) {
|
|
return null;
|
|
}
|
|
const resolved = resolveSessionConversationRef(sessionKey, {
|
|
bundledFallback: params.bundledFallback,
|
|
});
|
|
if (!resolved) {
|
|
return null;
|
|
}
|
|
const expectedChannel = normalizeOptionalChannel(params.channel);
|
|
if (expectedChannel && normalizeOptionalChannel(resolved.channel) !== expectedChannel) {
|
|
return null;
|
|
}
|
|
return {
|
|
channel: resolved.channel,
|
|
kind: resolved.kind,
|
|
id: resolved.id,
|
|
rawId: resolved.rawId,
|
|
threadId: resolved.threadId,
|
|
baseSessionKey: resolved.baseSessionKey,
|
|
baseConversationId: resolved.baseConversationId,
|
|
parentConversationCandidates: resolved.parentConversationCandidates,
|
|
};
|
|
}
|
|
|
|
export function resolveExecApprovalSessionTarget(params: {
|
|
cfg: OpenClawConfig;
|
|
request: ExecApprovalRequest;
|
|
turnSourceChannel?: string | null;
|
|
turnSourceTo?: string | null;
|
|
turnSourceAccountId?: string | null;
|
|
turnSourceThreadId?: string | number | null;
|
|
}): ExecApprovalSessionTarget | null {
|
|
const sessionKey = normalizeOptionalString(params.request.request.sessionKey);
|
|
if (!sessionKey) {
|
|
return null;
|
|
}
|
|
const persisted = resolvePersistedApprovalRequestSessionEntry({
|
|
cfg: params.cfg,
|
|
request: params.request,
|
|
});
|
|
if (!persisted) {
|
|
return null;
|
|
}
|
|
|
|
const target = resolveSessionDeliveryTarget({
|
|
entry: persisted.entry,
|
|
requestedChannel: "last",
|
|
turnSourceChannel: normalizeOptionalString(params.turnSourceChannel),
|
|
turnSourceTo: normalizeOptionalString(params.turnSourceTo),
|
|
turnSourceAccountId: normalizeOptionalString(params.turnSourceAccountId),
|
|
turnSourceThreadId: normalizeOptionalThreadValue(params.turnSourceThreadId),
|
|
});
|
|
if (!target.to) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
channel: normalizeOptionalString(target.channel),
|
|
to: target.to,
|
|
accountId: normalizeOptionalString(target.accountId),
|
|
threadId: normalizeOptionalThreadValue(target.threadId),
|
|
};
|
|
}
|
|
|
|
export function resolveApprovalRequestSessionTarget(params: {
|
|
cfg: OpenClawConfig;
|
|
request: ApprovalRequestLike;
|
|
}): ExecApprovalSessionTarget | null {
|
|
const execLikeRequest = toExecLikeApprovalRequest(params.request);
|
|
return resolveExecApprovalSessionTarget({
|
|
cfg: params.cfg,
|
|
request: execLikeRequest,
|
|
turnSourceChannel: execLikeRequest.request.turnSourceChannel ?? undefined,
|
|
turnSourceTo: execLikeRequest.request.turnSourceTo ?? undefined,
|
|
turnSourceAccountId: execLikeRequest.request.turnSourceAccountId ?? undefined,
|
|
turnSourceThreadId: execLikeRequest.request.turnSourceThreadId ?? undefined,
|
|
});
|
|
}
|
|
|
|
function resolveApprovalRequestStoredSessionTarget(params: {
|
|
cfg: OpenClawConfig;
|
|
request: ApprovalRequestLike;
|
|
}): ExecApprovalSessionTarget | null {
|
|
const execLikeRequest = toExecLikeApprovalRequest(params.request);
|
|
return resolveExecApprovalSessionTarget({
|
|
cfg: params.cfg,
|
|
request: execLikeRequest,
|
|
});
|
|
}
|
|
|
|
export function resolveApprovalRequestOriginTarget<TTarget>(
|
|
params: ApprovalRequestOriginTargetResolver<TTarget>,
|
|
): TTarget | null {
|
|
if (
|
|
!doesApprovalRequestMatchChannelAccount({
|
|
cfg: params.cfg,
|
|
request: params.request,
|
|
channel: params.channel,
|
|
accountId: params.accountId,
|
|
})
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const turnSourceTarget = params.resolveTurnSourceTarget(params.request);
|
|
const expectedChannel = normalizeOptionalChannel(params.channel);
|
|
const sessionTargetBinding = resolveApprovalRequestStoredSessionTarget({
|
|
cfg: params.cfg,
|
|
request: params.request,
|
|
});
|
|
const sessionTarget =
|
|
sessionTargetBinding &&
|
|
normalizeOptionalChannel(sessionTargetBinding.channel) === expectedChannel
|
|
? params.resolveSessionTarget(sessionTargetBinding)
|
|
: null;
|
|
|
|
if (turnSourceTarget && sessionTarget && !params.targetsMatch(turnSourceTarget, sessionTarget)) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
turnSourceTarget ?? sessionTarget ?? params.resolveFallbackTarget?.(params.request) ?? null
|
|
);
|
|
}
|