Plugin SDK: split approval adapter seams

This commit is contained in:
Gustavo Madeira Santana
2026-04-07 16:04:09 -04:00
parent 9bcef781e7
commit 28fc5d9b5e
17 changed files with 118 additions and 91 deletions

View File

@@ -1,4 +1,4 @@
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
import type { DiscordExecApprovalConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";

View File

@@ -1,6 +1,6 @@
import { Button, type ButtonInteraction, type ComponentData } from "@buape/carbon";
import { ButtonStyle } from "discord-api-types/v10";
import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-handler-runtime";
import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-gateway-runtime";
import type { DiscordExecApprovalConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type {
ExecApprovalDecision,

View File

@@ -8,7 +8,7 @@ import {
} from "@buape/carbon";
import { GatewayCloseCodes, type GatewayPlugin } from "@buape/carbon/gateway";
import { Routes } from "discord-api-types/v10";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
import {
listNativeCommandSpecsForConfig,

View File

@@ -3,7 +3,7 @@ import {
createApproverRestrictedNativeApprovalCapability,
splitChannelApprovalCapability,
} from "openclaw/plugin-sdk/approval-delivery-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import {
createChannelNativeOriginTargetResolver,
resolveApprovalRequestSessionConversation,

View File

@@ -1,4 +1,4 @@
import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-handler-runtime";
import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-gateway-runtime";
import type { ExecApprovalReplyDecision } from "openclaw/plugin-sdk/approval-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";

View File

@@ -1,5 +1,5 @@
import { format } from "node:util";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
import {
GROUP_POLICY_BLOCKED_LABEL,

View File

@@ -2,7 +2,7 @@ import {
createApproverRestrictedNativeApprovalCapability,
splitChannelApprovalCapability,
} from "openclaw/plugin-sdk/approval-delivery-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import {
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,

View File

@@ -7,7 +7,7 @@ import {
patchAllowlistUsersInConfigEntries,
summarizeMapping,
} from "openclaw/plugin-sdk/allow-from";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
import type { SessionScope } from "openclaw/plugin-sdk/config-runtime";
import { createConnectedChannelStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";

View File

@@ -2,7 +2,7 @@ import {
createApproverRestrictedNativeApprovalCapability,
splitChannelApprovalCapability,
} from "openclaw/plugin-sdk/approval-delivery-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import {
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,

View File

@@ -1,4 +1,4 @@
import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-handler-runtime";
import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-gateway-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { ExecApprovalReplyDecision } from "openclaw/plugin-sdk/infra-runtime";

View File

@@ -1,5 +1,5 @@
import type { RunOptions } from "@grammyjs/runner";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { PluginRuntime } from "openclaw/plugin-sdk/channel-core";
import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
import { resolveAgentMaxConcurrent } from "openclaw/plugin-sdk/config-runtime";

View File

@@ -116,6 +116,14 @@
"types": "./dist/plugin-sdk/approval-delivery-runtime.d.ts",
"default": "./dist/plugin-sdk/approval-delivery-runtime.js"
},
"./plugin-sdk/approval-gateway-runtime": {
"types": "./dist/plugin-sdk/approval-gateway-runtime.d.ts",
"default": "./dist/plugin-sdk/approval-gateway-runtime.js"
},
"./plugin-sdk/approval-handler-adapter-runtime": {
"types": "./dist/plugin-sdk/approval-handler-adapter-runtime.d.ts",
"default": "./dist/plugin-sdk/approval-handler-adapter-runtime.js"
},
"./plugin-sdk/approval-handler-runtime": {
"types": "./dist/plugin-sdk/approval-handler-runtime.d.ts",
"default": "./dist/plugin-sdk/approval-handler-runtime.js"

View File

@@ -0,0 +1,85 @@
import { createLazyRuntimeModule } from "../shared/lazy-runtime.js";
import type {
ChannelApprovalNativeAvailabilityAdapter,
ChannelApprovalNativeRuntimeAdapter,
} from "./approval-handler-runtime.js";
import type { ExecApprovalChannelRuntimeEventKind } from "./exec-approval-channel-runtime.js";
export const CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY = "approval.native";
type LazyChannelApprovalNativeRuntimeParams = {
load: () => Promise<ChannelApprovalNativeRuntimeAdapter>;
isConfigured: ChannelApprovalNativeAvailabilityAdapter["isConfigured"];
shouldHandle: ChannelApprovalNativeAvailabilityAdapter["shouldHandle"];
eventKinds?: readonly ExecApprovalChannelRuntimeEventKind[];
resolveApprovalKind?: ChannelApprovalNativeRuntimeAdapter["resolveApprovalKind"];
};
export function createLazyChannelApprovalNativeRuntimeAdapter(
params: LazyChannelApprovalNativeRuntimeParams,
): ChannelApprovalNativeRuntimeAdapter {
const loadRuntime = createLazyRuntimeModule(params.load);
let loadedRuntime: ChannelApprovalNativeRuntimeAdapter | null = null;
const loadResolvedRuntime = async (): Promise<ChannelApprovalNativeRuntimeAdapter> => {
const runtime = await loadRuntime();
loadedRuntime = runtime;
return runtime;
};
const loadRequired = async <TResult>(
select: (runtime: ChannelApprovalNativeRuntimeAdapter) => TResult,
): Promise<TResult> => select(await loadResolvedRuntime());
const loadOptional = async <TResult>(
select: (runtime: ChannelApprovalNativeRuntimeAdapter) => TResult | undefined,
): Promise<TResult | undefined> => select(await loadResolvedRuntime());
return {
...(params.eventKinds ? { eventKinds: params.eventKinds } : {}),
...(params.resolveApprovalKind ? { resolveApprovalKind: params.resolveApprovalKind } : {}),
availability: {
isConfigured: params.isConfigured,
shouldHandle: params.shouldHandle,
},
presentation: {
buildPendingPayload: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.presentation.buildPendingPayload))(runtimeParams),
buildResolvedResult: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.presentation.buildResolvedResult))(runtimeParams),
buildExpiredResult: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.presentation.buildExpiredResult))(runtimeParams),
},
transport: {
prepareTarget: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.transport.prepareTarget))(runtimeParams),
deliverPending: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.transport.deliverPending))(runtimeParams),
updateEntry: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.transport.updateEntry)
)?.(runtimeParams),
deleteEntry: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.transport.deleteEntry)
)?.(runtimeParams),
},
interactions: {
bindPending: async (runtimeParams) =>
(await loadOptional((runtime) => runtime.interactions?.bindPending))?.(runtimeParams),
unbindPending: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.interactions?.unbindPending)
)?.(runtimeParams),
clearPendingActions: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.interactions?.clearPendingActions)
)?.(runtimeParams),
},
observe: {
// Observe hooks are fire-and-forget at call sites. Reuse the already
// loaded runtime instead of introducing unawaited lazy-load promises.
onDeliveryError: (runtimeParams) => loadedRuntime?.observe?.onDeliveryError?.(runtimeParams),
onDuplicateSkipped: (runtimeParams) =>
loadedRuntime?.observe?.onDuplicateSkipped?.(runtimeParams),
onDelivered: (runtimeParams) => loadedRuntime?.observe?.onDelivered?.(runtimeParams),
},
};
}

View File

@@ -5,8 +5,11 @@ import type {
} from "../channels/plugins/types.adapters.js";
import type { OpenClawConfig } from "../config/config.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { createLazyRuntimeModule } from "../shared/lazy-runtime.js";
import { resolveApprovalOverGateway } from "./approval-gateway-resolver.js";
import {
CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY,
createLazyChannelApprovalNativeRuntimeAdapter,
} from "./approval-handler-adapter-runtime.js";
import type { ChannelApprovalNativePlannedTarget } from "./approval-native-delivery.js";
import {
createChannelNativeApprovalRuntime,
@@ -46,10 +49,13 @@ export type {
ResolvedApprovalView,
} from "./approval-view-model.js";
export { resolveApprovalOverGateway };
export {
CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY,
createLazyChannelApprovalNativeRuntimeAdapter,
};
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
type ApprovalResolved = ExecApprovalResolved | PluginApprovalResolved;
export const CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY = "approval.native";
export type ChannelApprovalHandler<
TRequest extends ApprovalRequest = ApprovalRequest,
@@ -584,83 +590,6 @@ export function createChannelApprovalNativeRuntimeAdapter<
};
}
type LazyChannelApprovalNativeRuntimeParams = {
load: () => Promise<ChannelApprovalNativeRuntimeAdapter>;
isConfigured: ChannelApprovalNativeAvailabilityAdapter["isConfigured"];
shouldHandle: ChannelApprovalNativeAvailabilityAdapter["shouldHandle"];
eventKinds?: readonly ExecApprovalChannelRuntimeEventKind[];
resolveApprovalKind?: ChannelApprovalNativeRuntimeAdapter["resolveApprovalKind"];
};
export function createLazyChannelApprovalNativeRuntimeAdapter(
params: LazyChannelApprovalNativeRuntimeParams,
): ChannelApprovalNativeRuntimeAdapter {
const loadRuntime = createLazyRuntimeModule(params.load);
let loadedRuntime: ChannelApprovalNativeRuntimeAdapter | null = null;
const loadResolvedRuntime = async (): Promise<ChannelApprovalNativeRuntimeAdapter> => {
const runtime = await loadRuntime();
loadedRuntime = runtime;
return runtime;
};
const loadRequired = async <TResult>(
select: (runtime: ChannelApprovalNativeRuntimeAdapter) => TResult,
): Promise<TResult> => select(await loadResolvedRuntime());
const loadOptional = async <TResult>(
select: (runtime: ChannelApprovalNativeRuntimeAdapter) => TResult | undefined,
): Promise<TResult | undefined> => select(await loadResolvedRuntime());
return {
...(params.eventKinds ? { eventKinds: params.eventKinds } : {}),
...(params.resolveApprovalKind ? { resolveApprovalKind: params.resolveApprovalKind } : {}),
availability: {
isConfigured: params.isConfigured,
shouldHandle: params.shouldHandle,
},
presentation: {
buildPendingPayload: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.presentation.buildPendingPayload))(runtimeParams),
buildResolvedResult: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.presentation.buildResolvedResult))(runtimeParams),
buildExpiredResult: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.presentation.buildExpiredResult))(runtimeParams),
},
transport: {
prepareTarget: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.transport.prepareTarget))(runtimeParams),
deliverPending: async (runtimeParams) =>
(await loadRequired((runtime) => runtime.transport.deliverPending))(runtimeParams),
updateEntry: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.transport.updateEntry)
)?.(runtimeParams),
deleteEntry: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.transport.deleteEntry)
)?.(runtimeParams),
},
interactions: {
bindPending: async (runtimeParams) =>
(await loadOptional((runtime) => runtime.interactions?.bindPending))?.(runtimeParams),
unbindPending: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.interactions?.unbindPending)
)?.(runtimeParams),
clearPendingActions: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.interactions?.clearPendingActions)
)?.(runtimeParams),
},
observe: {
// Observe hooks are fire-and-forget at call sites. Reuse the already
// loaded runtime instead of introducing unawaited lazy-load promises.
onDeliveryError: (runtimeParams) => loadedRuntime?.observe?.onDeliveryError?.(runtimeParams),
onDuplicateSkipped: (runtimeParams) =>
loadedRuntime?.observe?.onDuplicateSkipped?.(runtimeParams),
onDelivered: (runtimeParams) => loadedRuntime?.observe?.onDelivered?.(runtimeParams),
},
};
}
export type ChannelApprovalHandlerRuntimeSpec<TRequest extends ApprovalRequest> = {
label: string;
clientDisplayName: string;

View File

@@ -0,0 +1 @@
export { resolveApprovalOverGateway } from "../infra/approval-gateway-resolver.js";

View File

@@ -0,0 +1,4 @@
export {
CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY,
createLazyChannelApprovalNativeRuntimeAdapter,
} from "../infra/approval-handler-adapter-runtime.js";

View File

@@ -3,7 +3,6 @@ export {
createChannelApprovalNativeRuntimeAdapter,
createChannelApprovalHandlerFromCapability,
createLazyChannelApprovalNativeRuntimeAdapter,
resolveApprovalOverGateway,
CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY,
type ApprovalActionView,
type ApprovalMetadataView,
@@ -29,3 +28,4 @@ export {
type PluginApprovalResolvedView,
type ResolvedApprovalView,
} from "../infra/approval-handler-runtime.js";
export { resolveApprovalOverGateway } from "./approval-gateway-runtime.js";