From 86872e0880062759e1b869054288776b46de67b2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 4 Jun 2026 22:38:03 -0400 Subject: [PATCH] docs: document channel approval ingress contracts --- src/plugin-sdk/approval-native-helpers.ts | 15 +++++++++++++++ src/plugin-sdk/channel-ingress.ts | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/plugin-sdk/approval-native-helpers.ts b/src/plugin-sdk/approval-native-helpers.ts index 881fbb2d78ba..3db8abb82661 100644 --- a/src/plugin-sdk/approval-native-helpers.ts +++ b/src/plugin-sdk/approval-native-helpers.ts @@ -74,6 +74,7 @@ type ApprovalForwardingTargetMatcher = type ApprovalOriginOrSessionTargetChecker = ChannelApprovalForwardingEvaluatorParams["hasOriginOrSessionTarget"]; +/** Inputs for checking whether one approval request can use session-native forwarding. */ export type ChannelApprovalForwardingEligibilityParams = { /** Full config containing exec/plugin approval forwarding settings. */ cfg: OpenClawConfig; @@ -85,6 +86,7 @@ export type ChannelApprovalForwardingEligibilityParams = { request: ApprovalRequest; }; +/** Inputs for checking whether approval forwarding is configured for a channel route. */ export type ChannelApprovalPotentialRouteParams = { /** Full config containing exec/plugin approval forwarding settings. */ cfg: OpenClawConfig; @@ -96,6 +98,7 @@ export type ChannelApprovalPotentialRouteParams = { nativeSessionOnly?: boolean; }; +/** Inputs for checking whether one configured target can receive an approval request. */ export type ChannelApprovalExplicitTargetEligibilityParams = ChannelApprovalForwardingEligibilityParams & { /** Forwarding target that may be handled by the channel-native approval route. */ @@ -233,6 +236,7 @@ type CustomOriginResolverParams = BaseOriginResolverParams & { targetsMatch: (a: TTarget, b: TTarget) => boolean; }; +/** Standard channel-native approval destination used by route and origin matchers. */ export type NativeApprovalTarget = { /** Channel-local destination id. */ to: string; @@ -242,6 +246,7 @@ export type NativeApprovalTarget = { threadId?: string | number | null; }; +/** Compare channel-native approval targets with the same normalization used by outbound routes. */ export function nativeApprovalTargetsMatch(params: { /** Channel id used for route target normalization. */ channel?: string | null; @@ -266,6 +271,7 @@ export function nativeApprovalTargetsMatch(params: { }); } +/** Decide whether a channel-native exec approval route replaces the local text prompt. */ export function shouldSuppressLocalNativeExecApprovalPrompt(params: { /** Full config containing top-level or channel-specific approval settings. */ cfg: OpenClawConfig; @@ -369,6 +375,7 @@ function nativeApprovalTargetMatcher(channel: string): (left: unknown, right: un nativeApprovalTargetsMatch({ channel, left, right }); } +/** Infer approval family from the request shape unless the caller already knows it. */ export function resolveApprovalKind( request: ApprovalRequest, approvalKind?: ApprovalKind, @@ -520,6 +527,7 @@ function isExplicitTargetApprovalEligibleViaForwarding( }); } +/** Build reusable forwarding gates for channels with custom target matching logic. */ export function createChannelApprovalForwardingEvaluator( params: ChannelApprovalForwardingEvaluatorParams, ) { @@ -597,6 +605,7 @@ function normalizeApprovalForwardingModeWithDefault(params: { return params.config.mode ?? params.defaultForwardingMode; } +/** Create the standard route gates for native channel approval forwarding. */ export function createNativeApprovalChannelRouteGates( params: NativeApprovalChannelRouteGateParams, ): NativeApprovalChannelRouteGates { @@ -638,6 +647,8 @@ export function createNativeApprovalChannelRouteGates normalizeAccountId(candidateAccountId)); + // Unscoped targets are safe for a non-default account only when exactly + // one enabled account can receive them; otherwise they would be ambiguous. return enabledAccountIds.length === 1 && enabledAccountIds[0] === normalizedAccountId; }; @@ -779,6 +790,7 @@ function normalizeOptionalAccountId(value?: string | null): string | undefined { return value?.trim() || undefined; } +/** Create a fallback suppressor that avoids duplicate approval prompts after native delivery. */ export function createNativeApprovalForwardingFallbackSuppressor< TTarget extends NativeApprovalTarget, >( @@ -909,9 +921,11 @@ function hasCustomTargetsMatch( return typeof params.targetsMatch === "function"; } +/** Resolve a request origin target using standard native approval target matching. */ export function createChannelNativeOriginTargetResolver( params: NativeOriginResolverParams, ): (input: ApprovalResolverParams) => TTarget | null; +/** Resolve a request origin target for channels with custom target shapes. */ export function createChannelNativeOriginTargetResolver( params: CustomOriginResolverParams, ): (input: ApprovalResolverParams) => TTarget | null; @@ -927,6 +941,7 @@ export function createChannelNativeOriginTargetResolver( }); } +/** Create a resolver for configured approver DM targets. */ export function createChannelApproverDmTargetResolver< TApprover, TTarget extends NativeApprovalTarget = NativeApprovalTarget, diff --git a/src/plugin-sdk/channel-ingress.ts b/src/plugin-sdk/channel-ingress.ts index 866b41dfe190..81d9faff3133 100644 --- a/src/plugin-sdk/channel-ingress.ts +++ b/src/plugin-sdk/channel-ingress.ts @@ -56,15 +56,22 @@ export type { RouteSenderPolicy, } from "../channels/message-access/index.js"; +/** Redacted identifier material that can be matched against channel allowlist entries. */ export type ChannelIngressSubjectIdentifier = InternalMatchMaterial; +/** Inbound actor identity described by one or more channel-specific identifiers. */ export type ChannelIngressSubject = InternalChannelIngressSubject; +/** Normalized allowlist entry produced by a channel ingress adapter. */ export type ChannelIngressAdapterEntry = InternalNormalizedEntry; +/** Adapter normalization output split into matchable, invalid, and disabled entries. */ export type ChannelIngressAdapterNormalizeResult = InternalChannelIngressNormalizeResult; +/** Channel-specific allowlist normalizer and subject matcher used by ingress policy. */ export type ChannelIngressAdapter = InternalChannelIngressAdapter; +/** SDK-facing input shape for resolving redacted channel ingress state. */ export type ChannelIngressStateInput = MessageAccessChannelIngressStateInput; declare const CHANNEL_INGRESS_PLUGIN_ID: unique symbol; +/** Branded plugin id used in stable ingress diagnostics and generated gate identifiers. */ export type ChannelIngressPluginId = string & { readonly [CHANNEL_INGRESS_PLUGIN_ID]: true; }; @@ -93,6 +100,7 @@ export type ChannelIngressSideEffectResult = | { kind: "pending-history-recorded" } | { kind: "local-event-handled" }; +/** Minimal redacted decision summary suitable for logs and plugin diagnostics. */ export type RedactedIngressDiagnostics = { decisiveGateId?: string; reasonCode: IngressReasonCode; @@ -107,6 +115,7 @@ export const CHANNEL_INGRESS_GATE_SELECTORS = { event: { phase: "event", kind: "event" }, } as const satisfies Record; +/** Input descriptor for a single channel subject identifier before redacted normalization. */ export type ChannelIngressSubjectIdentifierInput = { value: string; opaqueId?: string; @@ -115,6 +124,7 @@ export type ChannelIngressSubjectIdentifierInput = { sensitivity?: "normal" | "pii"; }; +/** Options for the common one-string-id allowlist adapter. */ export type CreateChannelIngressStringAdapterParams = { kind?: ChannelIngressIdentifierKind; normalizeEntry?: (value: string) => string | null | undefined; @@ -125,6 +135,7 @@ export type CreateChannelIngressStringAdapterParams = { sensitivity?: "normal" | "pii"; }; +/** Options for adapters that expand each allowlist entry into multiple identifier records. */ export type CreateChannelIngressMultiIdentifierAdapterParams = { normalizeEntry: (entry: string, index: number) => readonly ChannelIngressAdapterEntry[]; getEntryMatchKey?: (entry: ChannelIngressAdapterEntry) => string | null | undefined; @@ -134,12 +145,14 @@ export type CreateChannelIngressMultiIdentifierAdapterParams = { isWildcardEntry?: (entry: ChannelIngressAdapterEntry) => boolean; }; +/** Legacy DM/group access projection retained for older channel runtime callers. */ export type ChannelIngressDmGroupAccessProjection = { decision: DmGroupAccessDecision; reasonCode: DmGroupAccessReasonCode; reason: string; }; +/** Sender-only group access projection used when command and sender gates are evaluated separately. */ export type ChannelIngressSenderGroupAccessProjection = { allowed: boolean; groupPolicy: ChannelIngressPolicyInput["groupPolicy"]; @@ -325,6 +338,7 @@ export function projectIngressAccessFacts(decision: ChannelIngressDecision): Acc }; } +/** Convert an ingress graph decision plus any local side effect into channel turn admission. */ export function mapChannelIngressDecisionToTurnAdmission( decision: ChannelIngressDecision, sideEffect: ChannelIngressSideEffectResult,