Files
openclaw/src/plugin-sdk/approval-native-helpers.ts
Peter Steinberger 00d8d7ead0 refactor: extract normalization core package
Extract shared normalization/coercion helpers into private @openclaw/normalization-core workspace package while preserving existing plugin SDK helper subpaths.\n\nAlso keeps direct normalization-core imports internal, wires UI/build/loader resolution, and replaces the slow PR network CodeQL lane with a fast added-line boundary scan while retaining full CodeQL for scheduled/manual runs.\n\nVerification: local moved tests, plugin SDK boundary tests, extension loader tests, agents-support shard, UI build/test, build artifacts, lint, workflow guards, autoreview, and GitHub CI passed on PR head 963d893715.
2026-05-31 01:33:00 +01:00

917 lines
29 KiB
TypeScript

import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../../packages/normalization-core/src/string-coerce.js";
import type {
ExecApprovalForwardingConfig,
ExecApprovalForwardingMode,
} from "../config/types.approvals.js";
import { doesApprovalRequestMatchChannelAccount } from "../infra/approval-request-account-binding.js";
import { matchesApprovalRequestFilters } from "../infra/approval-request-filters.js";
import {
getExecApprovalReplyMetadata,
type ExecApprovalReplyMetadata,
} from "../infra/exec-approval-reply.js";
import type { ExecApprovalSessionTarget } from "../infra/exec-approval-session-target.js";
import {
resolveApprovalRequestOriginTarget,
resolveApprovalRequestSessionTarget,
} from "../infra/exec-approval-session-target.js";
import type { ExecApprovalRequest } from "../infra/exec-approvals.js";
import type { PluginApprovalRequest } from "../infra/plugin-approvals.js";
import { normalizeAccountId } from "../routing/session-key.js";
import type { ChannelApprovalCapability, ChannelOutboundPayloadHint } from "./channel-contract.js";
import { channelRouteTargetsMatchExact } from "./channel-route.js";
import type { OpenClawConfig } from "./config-runtime.js";
import type { ReplyPayload } from "./reply-payload.js";
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
type ApprovalKind = "exec" | "plugin";
type DeliverySuppressionInput = Parameters<
NonNullable<
NonNullable<ChannelApprovalCapability["delivery"]>["shouldSuppressForwardingFallback"]
>
>[0];
type NativeApprovalForwardTarget = DeliverySuppressionInput["target"];
type LocalNativeExecApprovalConfig = {
enabled?: boolean | "auto";
mode?: string | null;
agentFilter?: string[];
sessionFilter?: string[];
};
type ChannelApprovalForwardTarget = DeliverySuppressionInput["target"];
type ApprovalResolverParams = {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
};
type ChannelApprovalForwardingEvaluatorParams = {
channel: string;
isTransportEnabled: (params: { cfg: OpenClawConfig; accountId?: string | null }) => boolean;
hasMatchingTarget: (params: {
cfg: OpenClawConfig;
config: ExecApprovalForwardingConfig;
accountId?: string | null;
target?: ChannelApprovalForwardTarget;
}) => boolean;
hasOriginOrSessionTarget: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ApprovalRequest;
}) => boolean;
};
type ApprovalTransportChecker = ChannelApprovalForwardingEvaluatorParams["isTransportEnabled"];
type ApprovalForwardingModeResolver = (
config: ExecApprovalForwardingConfig,
) => ExecApprovalForwardingMode;
type ApprovalForwardingTargetMatcher =
ChannelApprovalForwardingEvaluatorParams["hasMatchingTarget"];
type ApprovalOriginOrSessionTargetChecker =
ChannelApprovalForwardingEvaluatorParams["hasOriginOrSessionTarget"];
export type ChannelApprovalForwardingEligibilityParams = {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
};
export type ChannelApprovalPotentialRouteParams = {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
nativeSessionOnly?: boolean;
};
export type ChannelApprovalExplicitTargetEligibilityParams =
ChannelApprovalForwardingEligibilityParams & {
target: ChannelApprovalForwardTarget;
};
type NativeApprovalTargetNormalizer<TTarget> = (
target: TTarget,
request: ApprovalRequest,
) => TTarget | null | undefined;
type NativeApprovalForwardingFallbackSuppressorParams<TTarget extends NativeApprovalTarget> = {
channel: string;
normalizeForwardTarget: (target: NativeApprovalForwardTarget) => TTarget | null;
resolveAccountId?: (params: {
forwardingTarget: TTarget;
target: NativeApprovalForwardTarget;
request: ApprovalRequest;
}) => string | null | undefined;
resolveApprovalKind?: (params: {
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}) => ApprovalKind;
isSessionRouteEligible: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}) => boolean;
isExplicitTargetEligible?: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
target: NativeApprovalForwardTarget;
}) => boolean;
resolveForwardingTargetForMatch?: (params: {
forwardingTarget: TTarget;
accountId?: string | null;
target: NativeApprovalForwardTarget;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}) => TTarget | null;
resolveOriginTarget: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}) => TTarget | null;
resolveApproverDmTargets: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}) => readonly TTarget[];
targetsMatch?: (left: TTarget, right: TTarget) => boolean;
};
type NativeApprovalChannelRouteGateParams<TTarget extends NativeApprovalTarget> = {
channel: string;
defaultForwardingMode: ExecApprovalForwardingMode;
isTransportEnabled: (params: { cfg: OpenClawConfig; accountId?: string | null }) => boolean;
listAccountIds: (cfg: OpenClawConfig) => readonly string[];
resolveDefaultAccountId: (cfg: OpenClawConfig) => string;
normalizeForwardTarget: (target: NativeApprovalForwardTarget) => TTarget | null;
resolveTurnSourceTarget: (request: ApprovalRequest) => TTarget | null;
targetsMatch?: (left: TTarget, right: TTarget) => boolean;
};
type NativeApprovalChannelRouteGates = {
canApprovalPotentiallyRouteToChannel: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
nativeSessionOnly?: boolean;
}) => boolean;
canAnyApprovalPotentiallyRouteToChannel: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
nativeSessionOnly?: boolean;
}) => boolean;
isNativeApprovalHandlerConfigured: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
}) => boolean;
isSessionApprovalEligible: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}) => boolean;
isExplicitTargetEligible: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
target: NativeApprovalForwardTarget;
}) => boolean;
shouldHandleApprovalRequest: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}) => boolean;
};
type BaseOriginResolverParams<TTarget> = {
channel: string;
shouldHandleRequest?: (params: ApprovalResolverParams) => boolean;
resolveTurnSourceTarget: (request: ApprovalRequest) => TTarget | null;
resolveSessionTarget: (
sessionTarget: ExecApprovalSessionTarget,
request: ApprovalRequest,
) => TTarget | null;
normalizeTarget?: NativeApprovalTargetNormalizer<TTarget>;
normalizeTargetForMatch?: NativeApprovalTargetNormalizer<TTarget>;
resolveFallbackTarget?: (request: ApprovalRequest) => TTarget | null;
};
type NativeOriginResolverParams<TTarget extends NativeApprovalTarget> =
BaseOriginResolverParams<TTarget> & {
targetsMatch?: (a: TTarget, b: TTarget) => boolean;
};
type CustomOriginResolverParams<TTarget> = BaseOriginResolverParams<TTarget> & {
targetsMatch: (a: TTarget, b: TTarget) => boolean;
};
export type NativeApprovalTarget = {
to: string;
accountId?: string | null;
threadId?: string | number | null;
};
export function nativeApprovalTargetsMatch(params: {
channel?: string | null;
left: NativeApprovalTarget;
right: NativeApprovalTarget;
}): boolean {
return channelRouteTargetsMatchExact({
left: {
channel: params.channel,
to: params.left.to,
accountId: params.left.accountId,
threadId: params.left.threadId,
},
right: {
channel: params.channel,
to: params.right.to,
accountId: params.right.accountId,
threadId: params.right.threadId,
},
});
}
export function shouldSuppressLocalNativeExecApprovalPrompt(params: {
cfg: OpenClawConfig;
accountId?: string | null;
payload: ReplyPayload;
hint?: ChannelOutboundPayloadHint;
isTransportEnabled?: (params: { cfg: OpenClawConfig; accountId?: string | null }) => boolean;
isNativeDeliveryEnabled?: (params: { cfg: OpenClawConfig; accountId?: string | null }) => boolean;
resolveApprovalConfig?: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
metadata: ExecApprovalReplyMetadata;
}) => LocalNativeExecApprovalConfig | undefined;
requireApprovalConfigEnabled?: boolean;
enforceForwardingMode?: boolean;
isSessionRouteEligible?: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
metadata: ExecApprovalReplyMetadata;
}) => boolean;
hasExactTargetProof?: boolean;
fallbackAgentIdFromSessionKey?: boolean;
}): boolean {
if (params.hint?.kind !== "approval-pending" || params.hint.approvalKind !== "exec") {
return false;
}
if (params.hint.nativeRouteActive !== true) {
return false;
}
const metadata = getExecApprovalReplyMetadata(params.payload);
if (!metadata || metadata.approvalKind !== "exec") {
return false;
}
const isDeliveryEnabled = params.isNativeDeliveryEnabled ?? params.isTransportEnabled;
if (!isDeliveryEnabled?.({ cfg: params.cfg, accountId: params.accountId })) {
return false;
}
const config =
params.resolveApprovalConfig?.({
cfg: params.cfg,
accountId: params.accountId,
metadata,
}) ?? params.cfg.approvals?.exec;
const requireConfigEnabled =
params.requireApprovalConfigEnabled ?? params.resolveApprovalConfig === undefined;
if (requireConfigEnabled && !config?.enabled) {
return false;
}
const enforceForwardingMode =
params.enforceForwardingMode ?? params.resolveApprovalConfig === undefined;
if (enforceForwardingMode) {
const mode = config?.mode ?? "session";
if (mode !== "session" && mode !== "both" && !params.hasExactTargetProof) {
return false;
}
}
if (
params.isSessionRouteEligible &&
!params.isSessionRouteEligible({
cfg: params.cfg,
accountId: params.accountId,
metadata,
})
) {
return false;
}
return matchesApprovalRequestFilters({
request: {
agentId: metadata.agentId,
sessionKey: metadata.sessionKey,
},
agentFilter: config?.agentFilter,
sessionFilter: config?.sessionFilter,
fallbackAgentIdFromSessionKey: params.fallbackAgentIdFromSessionKey ?? true,
});
}
function isNativeApprovalTarget(value: unknown): value is NativeApprovalTarget {
return Boolean(
value && typeof value === "object" && typeof (value as { to?: unknown }).to === "string",
);
}
function nativeApprovalTargetMatcher(channel: string): (left: unknown, right: unknown) => boolean {
return (left, right) =>
isNativeApprovalTarget(left) &&
isNativeApprovalTarget(right) &&
nativeApprovalTargetsMatch({ channel, left, right });
}
export function resolveApprovalKind(
request: ApprovalRequest,
approvalKind?: ApprovalKind,
): ApprovalKind {
if (approvalKind) {
return approvalKind;
}
return "command" in request.request ? "exec" : "plugin";
}
function resolveApprovalForwardingConfig(params: {
cfg: OpenClawConfig;
approvalKind: ApprovalKind;
}): ExecApprovalForwardingConfig | undefined {
return params.approvalKind === "plugin"
? params.cfg.approvals?.plugin
: params.cfg.approvals?.exec;
}
function normalizeApprovalForwardingMode(
mode: ExecApprovalForwardingConfig["mode"] | undefined,
): ExecApprovalForwardingMode {
return mode ?? "session";
}
function approvalModeIncludesSession(mode: ExecApprovalForwardingMode): boolean {
return mode === "session" || mode === "both";
}
function approvalModeIncludesTargets(mode: ExecApprovalForwardingMode): boolean {
return mode === "targets" || mode === "both";
}
function matchesForwardingFilters(params: {
config: ExecApprovalForwardingConfig;
request: ApprovalRequest;
}): boolean {
return matchesApprovalRequestFilters({
request: params.request.request,
agentFilter: params.config.agentFilter,
sessionFilter: params.config.sessionFilter,
fallbackAgentIdFromSessionKey: true,
});
}
function resolveActiveApprovalForwarding(
params: ChannelApprovalPotentialRouteParams & {
isTransportEnabled: ApprovalTransportChecker;
resolveMode: ApprovalForwardingModeResolver;
},
): { config: ExecApprovalForwardingConfig; mode: ExecApprovalForwardingMode } | null {
if (!params.isTransportEnabled(params)) {
return null;
}
const config = resolveApprovalForwardingConfig(params);
if (!config?.enabled) {
return null;
}
return {
config,
mode: params.resolveMode(config),
};
}
function canApprovalPotentiallyRoute(
params: ChannelApprovalPotentialRouteParams & {
isTransportEnabled: ApprovalTransportChecker;
resolveMode: ApprovalForwardingModeResolver;
hasMatchingTarget: ApprovalForwardingTargetMatcher;
},
): boolean {
const forwarding = resolveActiveApprovalForwarding(params);
if (!forwarding) {
return false;
}
if (approvalModeIncludesSession(forwarding.mode)) {
return true;
}
if (params.nativeSessionOnly) {
return false;
}
return (
approvalModeIncludesTargets(forwarding.mode) &&
params.hasMatchingTarget({
cfg: params.cfg,
config: forwarding.config,
accountId: params.accountId,
})
);
}
function isSessionApprovalEligibleViaForwarding(
params: ChannelApprovalForwardingEligibilityParams & {
channel: string;
isTransportEnabled: ApprovalTransportChecker;
resolveMode: ApprovalForwardingModeResolver;
hasOriginOrSessionTarget: ApprovalOriginOrSessionTargetChecker;
},
): boolean {
const forwarding = resolveActiveApprovalForwarding(params);
if (!forwarding) {
return false;
}
if (!approvalModeIncludesSession(forwarding.mode)) {
return false;
}
if (!matchesForwardingFilters({ config: forwarding.config, request: params.request })) {
return false;
}
if (
!doesApprovalRequestMatchChannelAccount({
cfg: params.cfg,
request: params.request,
channel: params.channel,
accountId: params.accountId,
})
) {
return false;
}
return params.hasOriginOrSessionTarget({
cfg: params.cfg,
accountId: params.accountId,
request: params.request,
});
}
function isExplicitTargetApprovalEligibleViaForwarding(
params: ChannelApprovalExplicitTargetEligibilityParams & {
isTransportEnabled: ApprovalTransportChecker;
resolveMode: ApprovalForwardingModeResolver;
hasMatchingTarget: ApprovalForwardingTargetMatcher;
},
): boolean {
const forwarding = resolveActiveApprovalForwarding(params);
if (!forwarding) {
return false;
}
if (!approvalModeIncludesTargets(forwarding.mode)) {
return false;
}
if (!matchesForwardingFilters({ config: forwarding.config, request: params.request })) {
return false;
}
return params.hasMatchingTarget({
cfg: params.cfg,
config: forwarding.config,
accountId: params.accountId,
target: params.target,
});
}
export function createChannelApprovalForwardingEvaluator(
params: ChannelApprovalForwardingEvaluatorParams,
) {
const resolveForwardingMode = (config: ExecApprovalForwardingConfig) =>
normalizeApprovalForwardingMode(config.mode);
const isPotentialRoute = (input: ChannelApprovalPotentialRouteParams): boolean => {
return canApprovalPotentiallyRoute({
...input,
isTransportEnabled: params.isTransportEnabled,
resolveMode: resolveForwardingMode,
hasMatchingTarget: params.hasMatchingTarget,
});
};
const isSessionEligible = (input: ChannelApprovalForwardingEligibilityParams): boolean => {
return isSessionApprovalEligibleViaForwarding({
...input,
channel: params.channel,
isTransportEnabled: params.isTransportEnabled,
resolveMode: resolveForwardingMode,
hasOriginOrSessionTarget: params.hasOriginOrSessionTarget,
});
};
const isExplicitTargetEligible = (
input: ChannelApprovalExplicitTargetEligibilityParams,
): boolean => {
return isExplicitTargetApprovalEligibleViaForwarding({
...input,
isTransportEnabled: params.isTransportEnabled,
resolveMode: resolveForwardingMode,
hasMatchingTarget: params.hasMatchingTarget,
});
};
const canAnyPotentiallyRoute = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
nativeSessionOnly?: boolean;
}): boolean =>
isPotentialRoute({
...input,
approvalKind: "exec",
}) ||
isPotentialRoute({
...input,
approvalKind: "plugin",
});
const shouldHandleRequest = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}): boolean =>
isSessionEligible({
...input,
approvalKind: resolveApprovalKind(input.request, input.approvalKind),
});
return {
canAnyPotentiallyRoute,
isExplicitTargetEligible,
isPotentialRoute,
isSessionEligible,
shouldHandleRequest,
};
}
function normalizeApprovalForwardingModeWithDefault(params: {
config: ExecApprovalForwardingConfig;
defaultForwardingMode: ExecApprovalForwardingMode;
}): ExecApprovalForwardingMode {
return params.config.mode ?? params.defaultForwardingMode;
}
export function createNativeApprovalChannelRouteGates<TTarget extends NativeApprovalTarget>(
params: NativeApprovalChannelRouteGateParams<TTarget>,
): NativeApprovalChannelRouteGates {
const resolveForwardingMode = (config: ExecApprovalForwardingConfig) =>
normalizeApprovalForwardingModeWithDefault({
config,
defaultForwardingMode: params.defaultForwardingMode,
});
const targetsMatch =
params.targetsMatch ??
((left: TTarget, right: TTarget) =>
nativeApprovalTargetsMatch({ channel: params.channel, left, right }));
const targetAccountMatchesChannelAccount = (input: {
cfg: OpenClawConfig;
targetAccountId?: string | null;
accountId?: string | null;
}): boolean => {
const targetAccountId = normalizeOptionalString(input.targetAccountId);
const accountId = normalizeOptionalString(input.accountId);
if (targetAccountId) {
return !accountId || normalizeAccountId(targetAccountId) === normalizeAccountId(accountId);
}
if (!accountId) {
return true;
}
const normalizedAccountId = normalizeAccountId(accountId);
const defaultAccountId = normalizeAccountId(params.resolveDefaultAccountId(input.cfg));
if (normalizedAccountId === defaultAccountId) {
return true;
}
const enabledAccountIds = params
.listAccountIds(input.cfg)
.filter((candidateAccountId) =>
params.isTransportEnabled({
cfg: input.cfg,
accountId: candidateAccountId,
}),
)
.map((candidateAccountId) => normalizeAccountId(candidateAccountId));
return enabledAccountIds.length === 1 && enabledAccountIds[0] === normalizedAccountId;
};
const hasMatchingChannelTarget = (input: {
cfg: OpenClawConfig;
config: ExecApprovalForwardingConfig;
accountId?: string | null;
target?: NativeApprovalForwardTarget;
}): boolean => {
const candidateTarget = input.target ? params.normalizeForwardTarget(input.target) : null;
return (input.config.targets ?? []).some((target) => {
const configuredTarget = params.normalizeForwardTarget(target);
if (!configuredTarget) {
return false;
}
if (
!targetAccountMatchesChannelAccount({
cfg: input.cfg,
targetAccountId: configuredTarget.accountId,
accountId: input.accountId,
})
) {
return false;
}
if (!candidateTarget) {
return true;
}
return targetsMatch(configuredTarget, candidateTarget);
});
};
const hasChannelOriginOrSessionTarget = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ApprovalRequest;
}): boolean => {
if (params.resolveTurnSourceTarget(input.request)) {
return true;
}
const sessionTarget = resolveApprovalRequestSessionTarget({
cfg: input.cfg,
request: input.request,
});
return (
normalizeLowercaseStringOrEmpty(sessionTarget?.channel) === params.channel &&
targetAccountMatchesChannelAccount({
cfg: input.cfg,
targetAccountId: sessionTarget?.accountId,
accountId: input.accountId,
})
);
};
const canApprovalPotentiallyRouteToChannel = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
nativeSessionOnly?: boolean;
}): boolean => {
return canApprovalPotentiallyRoute({
...input,
isTransportEnabled: params.isTransportEnabled,
resolveMode: resolveForwardingMode,
hasMatchingTarget: hasMatchingChannelTarget,
});
};
const canAnyApprovalPotentiallyRouteToChannel = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
nativeSessionOnly?: boolean;
}): boolean =>
canApprovalPotentiallyRouteToChannel({
...input,
approvalKind: "exec",
}) ||
canApprovalPotentiallyRouteToChannel({
...input,
approvalKind: "plugin",
});
const isSessionApprovalEligible = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}): boolean => {
return isSessionApprovalEligibleViaForwarding({
...input,
channel: params.channel,
isTransportEnabled: params.isTransportEnabled,
resolveMode: resolveForwardingMode,
hasOriginOrSessionTarget: hasChannelOriginOrSessionTarget,
});
};
const isExplicitTargetEligible = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
target: NativeApprovalForwardTarget;
}): boolean => {
return isExplicitTargetApprovalEligibleViaForwarding({
...input,
isTransportEnabled: params.isTransportEnabled,
resolveMode: resolveForwardingMode,
hasMatchingTarget: hasMatchingChannelTarget,
});
};
const shouldHandleApprovalRequest = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}): boolean =>
isSessionApprovalEligible({
...input,
approvalKind: resolveApprovalKind(input.request, input.approvalKind),
});
return {
canApprovalPotentiallyRouteToChannel,
canAnyApprovalPotentiallyRouteToChannel,
isNativeApprovalHandlerConfigured: (input) =>
canAnyApprovalPotentiallyRouteToChannel({
...input,
nativeSessionOnly: true,
}),
isSessionApprovalEligible,
isExplicitTargetEligible,
shouldHandleApprovalRequest,
};
}
function normalizeOptionalAccountId(value?: string | null): string | undefined {
return value?.trim() || undefined;
}
export function createNativeApprovalForwardingFallbackSuppressor<
TTarget extends NativeApprovalTarget,
>(
params: NativeApprovalForwardingFallbackSuppressorParams<TTarget>,
): NonNullable<
NonNullable<ChannelApprovalCapability["delivery"]>["shouldSuppressForwardingFallback"]
> {
const targetsMatch =
params.targetsMatch ??
((left: TTarget, right: TTarget) =>
nativeApprovalTargetsMatch({ channel: params.channel, left, right }));
return (input: DeliverySuppressionInput): boolean => {
const forwardingTarget = params.normalizeForwardTarget(input.target);
if (!forwardingTarget) {
return false;
}
const accountId =
normalizeOptionalAccountId(
params.resolveAccountId?.({
forwardingTarget,
target: input.target,
request: input.request,
}),
) ??
normalizeOptionalAccountId(forwardingTarget.accountId) ??
normalizeOptionalAccountId(input.request.request.turnSourceAccountId);
const approvalKind =
params.resolveApprovalKind?.({
approvalKind: input.approvalKind,
request: input.request,
}) ?? resolveApprovalKind(input.request, input.approvalKind);
const explicitTarget = input.target.source === "target";
const eligible = explicitTarget
? (params.isExplicitTargetEligible?.({
cfg: input.cfg,
accountId,
approvalKind,
request: input.request,
target: input.target,
}) ?? false)
: params.isSessionRouteEligible({
cfg: input.cfg,
accountId,
approvalKind,
request: input.request,
});
if (!eligible) {
return false;
}
const forwardingTargetForMatch =
params.resolveForwardingTargetForMatch?.({
forwardingTarget,
accountId,
target: input.target,
approvalKind,
request: input.request,
}) ?? forwardingTarget;
if (!forwardingTargetForMatch) {
return false;
}
const originTarget = params.resolveOriginTarget({
cfg: input.cfg,
accountId,
approvalKind,
request: input.request,
});
if (originTarget && targetsMatch(forwardingTargetForMatch, originTarget)) {
return true;
}
return params
.resolveApproverDmTargets({
cfg: input.cfg,
accountId,
approvalKind,
request: input.request,
})
.some((approverTarget) => targetsMatch(forwardingTargetForMatch, approverTarget));
};
}
function createOriginTargetResolver<TTarget>(
params: CustomOriginResolverParams<TTarget>,
): (input: ApprovalResolverParams) => TTarget | null {
return (input: ApprovalResolverParams): TTarget | null => {
if (params.shouldHandleRequest && !params.shouldHandleRequest(input)) {
return null;
}
const normalizeTarget = (target: TTarget | null): TTarget | null => {
if (!target) {
return null;
}
return params.normalizeTarget
? (params.normalizeTarget(target, input.request) ?? null)
: target;
};
const normalizeTargetForMatch = (target: TTarget): TTarget | null =>
params.normalizeTargetForMatch?.(target, input.request) ?? target;
return resolveApprovalRequestOriginTarget({
cfg: input.cfg,
request: input.request,
channel: params.channel,
accountId: input.accountId,
resolveTurnSourceTarget: (request) =>
normalizeTarget(params.resolveTurnSourceTarget(request)),
resolveSessionTarget: (sessionTarget) =>
normalizeTarget(params.resolveSessionTarget(sessionTarget, input.request)),
targetsMatch: (left, right) => {
const normalizedLeft = normalizeTargetForMatch(left);
const normalizedRight = normalizeTargetForMatch(right);
return Boolean(
normalizedLeft && normalizedRight && params.targetsMatch(normalizedLeft, normalizedRight),
);
},
resolveFallbackTarget: params.resolveFallbackTarget
? (request) => normalizeTarget(params.resolveFallbackTarget?.(request) ?? null)
: undefined,
});
};
}
function hasCustomTargetsMatch<TTarget>(
params: NativeOriginResolverParams<NativeApprovalTarget> | CustomOriginResolverParams<TTarget>,
): params is CustomOriginResolverParams<TTarget> {
return typeof params.targetsMatch === "function";
}
export function createChannelNativeOriginTargetResolver<TTarget extends NativeApprovalTarget>(
params: NativeOriginResolverParams<TTarget>,
): (input: ApprovalResolverParams) => TTarget | null;
export function createChannelNativeOriginTargetResolver<TTarget>(
params: CustomOriginResolverParams<TTarget>,
): (input: ApprovalResolverParams) => TTarget | null;
export function createChannelNativeOriginTargetResolver<TTarget>(
params: NativeOriginResolverParams<NativeApprovalTarget> | CustomOriginResolverParams<TTarget>,
): (input: ApprovalResolverParams) => NativeApprovalTarget | TTarget | null {
if (hasCustomTargetsMatch(params)) {
return createOriginTargetResolver(params);
}
return createOriginTargetResolver({
...params,
targetsMatch: nativeApprovalTargetMatcher(params.channel),
});
}
export function createChannelApproverDmTargetResolver<
TApprover,
TTarget extends NativeApprovalTarget = NativeApprovalTarget,
>(params: {
shouldHandleRequest?: (params: ApprovalResolverParams) => boolean;
resolveApprovers: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
}) => readonly TApprover[];
mapApprover: (approver: TApprover, params: ApprovalResolverParams) => TTarget | null | undefined;
}) {
return (input: ApprovalResolverParams): TTarget[] => {
if (params.shouldHandleRequest && !params.shouldHandleRequest(input)) {
return [];
}
const targets: TTarget[] = [];
for (const approver of params.resolveApprovers({
cfg: input.cfg,
accountId: input.accountId,
})) {
const target = params.mapApprover(approver, input);
if (target) {
targets.push(target);
}
}
return targets;
};
}