types: preserve approval runtime payload typing

This commit is contained in:
Gustavo Madeira Santana
2026-04-07 17:08:57 -04:00
parent 5fb6aeaf86
commit af4a2faa1d
10 changed files with 118 additions and 36 deletions

View File

@@ -1,4 +1,5 @@
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-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";
@@ -239,7 +240,8 @@ export function createDiscordApprovalCapability(configOverride?: DiscordExecAppr
configOverride,
}),
load: async () =>
(await import("./approval-handler.runtime.js")).discordApprovalNativeRuntime,
(await import("./approval-handler.runtime.js"))
.discordApprovalNativeRuntime as unknown as ChannelApprovalNativeRuntimeAdapter,
}),
});
}

View File

@@ -223,7 +223,8 @@ export const matrixApprovalNativeRuntime = createChannelApprovalNativeRuntimeAda
PendingApprovalContent,
PreparedMatrixTarget,
PendingMessage,
ReactionTargetRef
ReactionTargetRef,
string
>({
eventKinds: ["exec", "plugin"],
availability: {
@@ -330,7 +331,7 @@ export const matrixApprovalNativeRuntime = createChannelApprovalNativeRuntimeAda
if (!primaryMessageId) {
return;
}
const text = payload as string;
const text = payload;
await Promise.allSettled([
editMessage(entry.roomId, primaryMessageId, text, {
cfg: cfg as CoreConfig,

View File

@@ -4,6 +4,7 @@ import {
splitChannelApprovalCapability,
} from "openclaw/plugin-sdk/approval-delivery-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import {
createChannelNativeOriginTargetResolver,
resolveApprovalRequestSessionConversation,
@@ -237,7 +238,9 @@ const matrixNativeApprovalCapability = createApproverRestrictedNativeApprovalCap
accountId,
request,
}),
load: async () => (await import("./approval-handler.runtime.js")).matrixApprovalNativeRuntime,
load: async () =>
(await import("./approval-handler.runtime.js"))
.matrixApprovalNativeRuntime as unknown as ChannelApprovalNativeRuntimeAdapter,
}),
});

View File

@@ -103,8 +103,6 @@ describe("slackApprovalNativeRuntime", () => {
}
expect(result.payload.text).toContain("*Exec approval: Allowed once*");
expect(result.payload.text).toContain("Resolved by <@U123APPROVER>.");
expect(
(result.payload.blocks as Array<{ type?: string }>).some((block) => block.type === "actions"),
).toBe(false);
expect(result.payload.blocks.some((block) => block.type === "actions")).toBe(false);
});
});

View File

@@ -229,7 +229,8 @@ export const slackApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdap
SlackPendingDelivery,
{ to: string; threadTs?: string },
SlackPendingApproval,
never
never,
SlackPendingDelivery
>({
eventKinds: ["exec"],
availability: {
@@ -313,7 +314,7 @@ export const slackApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdap
if (!resolved) {
return;
}
const nextPayload = payload as SlackPendingDelivery;
const nextPayload = payload;
await updateMessage({
app: resolved.context.app,
channelId: entry.channelId,

View File

@@ -3,6 +3,7 @@ import {
splitChannelApprovalCapability,
} from "openclaw/plugin-sdk/approval-delivery-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import {
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,
@@ -183,7 +184,9 @@ export const slackApprovalCapability = createApproverRestrictedNativeApprovalCap
accountId,
request,
}),
load: async () => (await import("./approval-handler.runtime.js")).slackApprovalNativeRuntime,
load: async () =>
(await import("./approval-handler.runtime.js"))
.slackApprovalNativeRuntime as unknown as ChannelApprovalNativeRuntimeAdapter,
}),
});

View File

@@ -3,6 +3,7 @@ import {
splitChannelApprovalCapability,
} from "openclaw/plugin-sdk/approval-delivery-runtime";
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import {
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,
@@ -128,7 +129,9 @@ const telegramNativeApprovalCapability = createApproverRestrictedNativeApprovalC
accountId,
request,
}),
load: async () => (await import("./approval-handler.runtime.js")).telegramApprovalNativeRuntime,
load: async () =>
(await import("./approval-handler.runtime.js"))
.telegramApprovalNativeRuntime as unknown as ChannelApprovalNativeRuntimeAdapter,
}),
});

View File

@@ -7,29 +7,75 @@ import type { ExecApprovalChannelRuntimeEventKind } from "./exec-approval-channe
export const CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY = "approval.native";
type LazyChannelApprovalNativeRuntimeParams = {
load: () => Promise<ChannelApprovalNativeRuntimeAdapter>;
export function createLazyChannelApprovalNativeRuntimeAdapter<
TPendingPayload = unknown,
TPreparedTarget = unknown,
TPendingEntry = unknown,
TBinding = unknown,
TFinalPayload = unknown,
>(params: {
load: () => Promise<
ChannelApprovalNativeRuntimeAdapter<
TPendingPayload,
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload
>
>;
isConfigured: ChannelApprovalNativeAvailabilityAdapter["isConfigured"];
shouldHandle: ChannelApprovalNativeAvailabilityAdapter["shouldHandle"];
eventKinds?: readonly ExecApprovalChannelRuntimeEventKind[];
resolveApprovalKind?: ChannelApprovalNativeRuntimeAdapter["resolveApprovalKind"];
};
export function createLazyChannelApprovalNativeRuntimeAdapter(
params: LazyChannelApprovalNativeRuntimeParams,
): ChannelApprovalNativeRuntimeAdapter {
}): ChannelApprovalNativeRuntimeAdapter<
TPendingPayload,
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload
> {
const loadRuntime = createLazyRuntimeModule(params.load);
let loadedRuntime: ChannelApprovalNativeRuntimeAdapter | null = null;
const loadResolvedRuntime = async (): Promise<ChannelApprovalNativeRuntimeAdapter> => {
let loadedRuntime: ChannelApprovalNativeRuntimeAdapter<
TPendingPayload,
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload
> | null = null;
const loadResolvedRuntime = async (): Promise<
ChannelApprovalNativeRuntimeAdapter<
TPendingPayload,
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload
>
> => {
const runtime = await loadRuntime();
loadedRuntime = runtime;
return runtime;
};
const loadRequired = async <TResult>(
select: (runtime: ChannelApprovalNativeRuntimeAdapter) => TResult,
select: (
runtime: ChannelApprovalNativeRuntimeAdapter<
TPendingPayload,
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload
>,
) => TResult,
): Promise<TResult> => select(await loadResolvedRuntime());
const loadOptional = async <TResult>(
select: (runtime: ChannelApprovalNativeRuntimeAdapter) => TResult | undefined,
select: (
runtime: ChannelApprovalNativeRuntimeAdapter<
TPendingPayload,
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload
>,
) => TResult | undefined,
): Promise<TResult | undefined> => select(await loadResolvedRuntime());
return {
@@ -63,7 +109,8 @@ export function createLazyChannelApprovalNativeRuntimeAdapter(
},
interactions: {
bindPending: async (runtimeParams) =>
(await loadOptional((runtime) => runtime.interactions?.bindPending))?.(runtimeParams),
(await loadOptional((runtime) => runtime.interactions?.bindPending))?.(runtimeParams) ??
null,
unbindPending: async (runtimeParams) =>
await (
await loadOptional((runtime) => runtime.interactions?.unbindPending)

View File

@@ -222,13 +222,24 @@ export type ChannelApprovalNativeObserveAdapter<
) => void;
};
export type ChannelApprovalNativeRuntimeAdapter = {
export type ChannelApprovalNativeRuntimeAdapter<
TPendingPayload = unknown,
TPreparedTarget = unknown,
TPendingEntry = unknown,
TBinding = unknown,
TFinalPayload = unknown,
> = {
eventKinds?: readonly ExecApprovalChannelRuntimeEventKind[];
resolveApprovalKind?: (request: ApprovalRequest) => ChannelApprovalKind;
availability: ChannelApprovalNativeAvailabilityAdapter;
presentation: ChannelApprovalNativePresentationAdapter;
transport: ChannelApprovalNativeTransportAdapter;
interactions?: ChannelApprovalNativeInteractionAdapter;
presentation: ChannelApprovalNativePresentationAdapter<TPendingPayload, TFinalPayload>;
transport: ChannelApprovalNativeTransportAdapter<
TPreparedTarget,
TPendingEntry,
TPendingPayload,
TFinalPayload
>;
interactions?: ChannelApprovalNativeInteractionAdapter<TPendingEntry, TBinding>;
observe?: ChannelApprovalNativeObserveAdapter;
};
@@ -237,6 +248,7 @@ export type ChannelApprovalNativeRuntimeSpec<
TPreparedTarget,
TPendingEntry,
TBinding = unknown,
TFinalPayload = unknown,
TPendingView extends PendingApprovalView = PendingApprovalView,
TResolvedView extends ResolvedApprovalView = ResolvedApprovalView,
TExpiredView extends ExpiredApprovalView = ExpiredApprovalView,
@@ -261,8 +273,8 @@ export type ChannelApprovalNativeRuntimeSpec<
entry: TPendingEntry;
},
) =>
| ChannelApprovalNativeFinalAction<unknown>
| Promise<ChannelApprovalNativeFinalAction<unknown>>;
| ChannelApprovalNativeFinalAction<TFinalPayload>
| Promise<ChannelApprovalNativeFinalAction<TFinalPayload>>;
buildExpiredResult: (
params: ChannelApprovalCapabilityHandlerContext & {
request: ApprovalRequest;
@@ -270,8 +282,8 @@ export type ChannelApprovalNativeRuntimeSpec<
entry: TPendingEntry;
},
) =>
| ChannelApprovalNativeFinalAction<unknown>
| Promise<ChannelApprovalNativeFinalAction<unknown>>;
| ChannelApprovalNativeFinalAction<TFinalPayload>
| Promise<ChannelApprovalNativeFinalAction<TFinalPayload>>;
};
transport: {
prepareTarget: (
@@ -299,7 +311,7 @@ export type ChannelApprovalNativeRuntimeSpec<
updateEntry?: (
params: ChannelApprovalCapabilityHandlerContext & {
entry: TPendingEntry;
payload: unknown;
payload: TFinalPayload;
phase: "resolved" | "expired";
},
) => Promise<void>;
@@ -487,6 +499,7 @@ export function createChannelApprovalNativeRuntimeAdapter<
TPreparedTarget,
TPendingEntry,
TBinding = unknown,
TFinalPayload = unknown,
TPendingView extends PendingApprovalView = PendingApprovalView,
TResolvedView extends ResolvedApprovalView = ResolvedApprovalView,
TExpiredView extends ExpiredApprovalView = ExpiredApprovalView,
@@ -496,11 +509,18 @@ export function createChannelApprovalNativeRuntimeAdapter<
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload,
TPendingView,
TResolvedView,
TExpiredView
>,
): ChannelApprovalNativeRuntimeAdapter {
): ChannelApprovalNativeRuntimeAdapter<
TPendingPayload,
TPreparedTarget,
TPendingEntry,
TBinding,
TFinalPayload
> {
return {
...(spec.eventKinds ? { eventKinds: spec.eventKinds } : {}),
...(spec.resolveApprovalKind ? { resolveApprovalKind: spec.resolveApprovalKind } : {}),
@@ -547,7 +567,7 @@ export function createChannelApprovalNativeRuntimeAdapter<
...(spec.interactions.bindPending
? {
bindPending: async (params) =>
await spec.interactions?.bindPending?.(params as never),
(await spec.interactions!.bindPending!(params as never)) ?? null,
}
: {}),
...(spec.interactions.unbindPending

View File

@@ -978,7 +978,9 @@ describe("fetchWithSsrFGuard hardening", () => {
fetch: vi.fn(async () => okResponse()),
};
const lookupFn: LookupFn = vi.fn(async (hostname: string) => {
if (hostname === "localhost") return [{ address: "127.0.0.1", family: 4 }];
if (hostname === "localhost") {
return [{ address: "127.0.0.1", family: 4 }];
}
return [{ address: "149.154.167.220", family: 4 }];
}) as unknown as LookupFn;
const fetchImpl = vi.fn(async () => okResponse());
@@ -1008,7 +1010,9 @@ describe("fetchWithSsrFGuard hardening", () => {
fetch: vi.fn(async () => okResponse()),
};
const lookupFn: LookupFn = vi.fn(async (hostname: string) => {
if (hostname === "localhost") return [{ address: "127.0.0.1", family: 4 }];
if (hostname === "localhost") {
return [{ address: "127.0.0.1", family: 4 }];
}
return [{ address: "149.154.167.220", family: 4 }];
}) as unknown as LookupFn;
const fetchImpl = vi.fn();