refactor(approvals): share native delivery runtime

This commit is contained in:
Peter Steinberger
2026-03-31 22:53:53 +01:00
parent 5997317c09
commit ddce362d34
6 changed files with 408 additions and 162 deletions

View File

@@ -0,0 +1,104 @@
import { describe, expect, it, vi } from "vitest";
import type { ChannelApprovalNativeAdapter } from "../channels/plugins/types.adapters.js";
import { deliverApprovalRequestViaChannelNativePlan } from "./approval-native-runtime.js";
const execRequest = {
id: "approval-1",
request: {
command: "uname -a",
},
createdAtMs: 0,
expiresAtMs: 120_000,
};
describe("deliverApprovalRequestViaChannelNativePlan", () => {
it("sends an origin notice and dedupes converged prepared targets", async () => {
const adapter: ChannelApprovalNativeAdapter = {
describeDeliveryCapabilities: () => ({
enabled: true,
preferredSurface: "approver-dm",
supportsOriginSurface: true,
supportsApproverDmSurface: true,
notifyOriginWhenDmOnly: true,
}),
resolveOriginTarget: async () => ({ to: "origin-room" }),
resolveApproverDmTargets: async () => [{ to: "approver-1" }, { to: "approver-2" }],
};
const sendOriginNotice = vi.fn().mockResolvedValue(undefined);
const prepareTarget = vi
.fn()
.mockImplementation(
async ({ plannedTarget }: { plannedTarget: { target: { to: string } } }) =>
plannedTarget.target.to === "approver-1"
? {
dedupeKey: "shared-dm",
target: { channelId: "shared-dm", recipientId: "approver-1" },
}
: {
dedupeKey: "shared-dm",
target: { channelId: "shared-dm", recipientId: "approver-2" },
},
);
const deliverTarget = vi
.fn()
.mockImplementation(
async ({ preparedTarget }: { preparedTarget: { channelId: string } }) => ({
channelId: preparedTarget.channelId,
}),
);
const onDuplicateSkipped = vi.fn();
const entries = await deliverApprovalRequestViaChannelNativePlan({
cfg: {} as never,
approvalKind: "exec",
request: execRequest,
adapter,
sendOriginNotice: async ({ originTarget }) => {
await sendOriginNotice(originTarget);
},
prepareTarget,
deliverTarget,
onDuplicateSkipped,
});
expect(sendOriginNotice).toHaveBeenCalledWith({ to: "origin-room" });
expect(prepareTarget).toHaveBeenCalledTimes(2);
expect(deliverTarget).toHaveBeenCalledTimes(1);
expect(onDuplicateSkipped).toHaveBeenCalledTimes(1);
expect(entries).toEqual([{ channelId: "shared-dm" }]);
});
it("continues after per-target delivery failures", async () => {
const adapter: ChannelApprovalNativeAdapter = {
describeDeliveryCapabilities: () => ({
enabled: true,
preferredSurface: "approver-dm",
supportsOriginSurface: false,
supportsApproverDmSurface: true,
}),
resolveApproverDmTargets: async () => [{ to: "approver-1" }, { to: "approver-2" }],
};
const onDeliveryError = vi.fn();
const entries = await deliverApprovalRequestViaChannelNativePlan({
cfg: {} as never,
approvalKind: "exec",
request: execRequest,
adapter,
prepareTarget: ({ plannedTarget }) => ({
dedupeKey: plannedTarget.target.to,
target: { channelId: plannedTarget.target.to },
}),
deliverTarget: async ({ preparedTarget }) => {
if (preparedTarget.channelId === "approver-1") {
throw new Error("boom");
}
return { channelId: preparedTarget.channelId };
},
onDeliveryError,
});
expect(onDeliveryError).toHaveBeenCalledTimes(1);
expect(entries).toEqual([{ channelId: "approver-2" }]);
});
});

View File

@@ -0,0 +1,154 @@
import type {
ChannelApprovalKind,
ChannelApprovalNativeAdapter,
ChannelApprovalNativeTarget,
} from "../channels/plugins/types.adapters.js";
import type { OpenClawConfig } from "../config/config.js";
import {
resolveChannelNativeApprovalDeliveryPlan,
type ChannelApprovalNativePlannedTarget,
} from "./approval-native-delivery.js";
import type { ExecApprovalRequest } from "./exec-approvals.js";
import type { PluginApprovalRequest } from "./plugin-approvals.js";
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
export type PreparedChannelNativeApprovalTarget<TPreparedTarget> = {
dedupeKey: string;
target: TPreparedTarget;
};
function buildTargetKey(target: ChannelApprovalNativeTarget): string {
return `${target.to}:${target.threadId == null ? "" : String(target.threadId)}`;
}
export async function deliverApprovalRequestViaChannelNativePlan<
TPreparedTarget,
TPendingEntry,
TRequest extends ApprovalRequest = ApprovalRequest,
>(params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ChannelApprovalKind;
request: TRequest;
adapter?: ChannelApprovalNativeAdapter | null;
sendOriginNotice?: (params: {
originTarget: ChannelApprovalNativeTarget;
request: TRequest;
}) => Promise<void>;
prepareTarget: (params: {
plannedTarget: ChannelApprovalNativePlannedTarget;
request: TRequest;
}) =>
| PreparedChannelNativeApprovalTarget<TPreparedTarget>
| null
| Promise<PreparedChannelNativeApprovalTarget<TPreparedTarget> | null>;
deliverTarget: (params: {
plannedTarget: ChannelApprovalNativePlannedTarget;
preparedTarget: TPreparedTarget;
request: TRequest;
}) => TPendingEntry | null | Promise<TPendingEntry | null>;
onOriginNoticeError?: (params: {
error: unknown;
originTarget: ChannelApprovalNativeTarget;
request: TRequest;
}) => void;
onDeliveryError?: (params: {
error: unknown;
plannedTarget: ChannelApprovalNativePlannedTarget;
request: TRequest;
}) => void;
onDuplicateSkipped?: (params: {
plannedTarget: ChannelApprovalNativePlannedTarget;
preparedTarget: PreparedChannelNativeApprovalTarget<TPreparedTarget>;
request: TRequest;
}) => void;
onDelivered?: (params: {
plannedTarget: ChannelApprovalNativePlannedTarget;
preparedTarget: PreparedChannelNativeApprovalTarget<TPreparedTarget>;
request: TRequest;
entry: TPendingEntry;
}) => void;
}): Promise<TPendingEntry[]> {
const deliveryPlan = await resolveChannelNativeApprovalDeliveryPlan({
cfg: params.cfg,
accountId: params.accountId,
approvalKind: params.approvalKind,
request: params.request,
adapter: params.adapter,
});
const originTargetKey = deliveryPlan.originTarget
? buildTargetKey(deliveryPlan.originTarget)
: null;
const plannedTargetKeys = new Set(
deliveryPlan.targets.map((plannedTarget) => buildTargetKey(plannedTarget.target)),
);
if (
deliveryPlan.notifyOriginWhenDmOnly &&
deliveryPlan.originTarget &&
(originTargetKey == null || !plannedTargetKeys.has(originTargetKey))
) {
try {
await params.sendOriginNotice?.({
originTarget: deliveryPlan.originTarget,
request: params.request,
});
} catch (error) {
params.onOriginNoticeError?.({
error,
originTarget: deliveryPlan.originTarget,
request: params.request,
});
}
}
const deliveredKeys = new Set<string>();
const pendingEntries: TPendingEntry[] = [];
for (const plannedTarget of deliveryPlan.targets) {
try {
const preparedTarget = await params.prepareTarget({
plannedTarget,
request: params.request,
});
if (!preparedTarget) {
continue;
}
if (deliveredKeys.has(preparedTarget.dedupeKey)) {
params.onDuplicateSkipped?.({
plannedTarget,
preparedTarget,
request: params.request,
});
continue;
}
const entry = await params.deliverTarget({
plannedTarget,
preparedTarget: preparedTarget.target,
request: params.request,
});
if (!entry) {
continue;
}
deliveredKeys.add(preparedTarget.dedupeKey);
pendingEntries.push(entry);
params.onDelivered?.({
plannedTarget,
preparedTarget,
request: params.request,
entry,
});
} catch (error) {
params.onDeliveryError?.({
error,
plannedTarget,
request: params.request,
});
}
}
return pendingEntries;
}

View File

@@ -13,6 +13,7 @@ export * from "../infra/exec-approval-reply.ts";
export * from "../infra/exec-approval-session-target.ts";
export * from "../infra/exec-approvals.ts";
export * from "../infra/approval-native-delivery.ts";
export * from "../infra/approval-native-runtime.ts";
export * from "../infra/plugin-approvals.ts";
export * from "../infra/fetch.js";
export * from "../infra/file-lock.js";