Skip to content

Commit 89f520e

Browse files
committed
fix: scope native approval fallback suppression
1 parent 40f50a6 commit 89f520e

3 files changed

Lines changed: 80 additions & 0 deletions

File tree

extensions/telegram/src/approval-native.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ const telegramNativeApprovalCapability = createApproverRestrictedNativeApprovalC
106106
resolveNativeDeliveryMode: ({ cfg, accountId }) =>
107107
resolveTelegramExecApprovalTarget({ cfg, accountId }),
108108
requireMatchingTurnSourceChannel: true,
109+
allowMissingTurnSourceChannelForSuppression: true,
109110
resolveSuppressionAccountId: ({ target, request }) =>
110111
normalizeOptionalString(target.accountId) ??
111112
normalizeOptionalString(request.request.turnSourceAccountId),

src/plugin-sdk/approval-delivery-helpers.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ describe("createApproverRestrictedNativeApprovalAdapter", () => {
175175
isNativeDeliveryEnabled,
176176
resolveNativeDeliveryMode: () => "both",
177177
requireMatchingTurnSourceChannel: true,
178+
allowMissingTurnSourceChannelForSuppression: true,
178179
resolveSuppressionAccountId: ({ request }) =>
179180
request.request.turnSourceAccountId?.trim() || undefined,
180181
});
@@ -263,6 +264,38 @@ describe("createApproverRestrictedNativeApprovalAdapter", () => {
263264
}),
264265
).toBe(true);
265266
});
267+
268+
it("keeps forwarding fallback for missing-source channels unless explicitly opted in", () => {
269+
const adapter = createApproverRestrictedNativeApprovalAdapter({
270+
channel: "matrix",
271+
channelLabel: "Matrix",
272+
listAccountIds: () => [],
273+
hasApprovers: () => true,
274+
isExecAuthorizedSender: () => true,
275+
isNativeDeliveryEnabled: () => true,
276+
resolveNativeDeliveryMode: () => "both",
277+
requireMatchingTurnSourceChannel: true,
278+
});
279+
const shouldSuppressForwardingFallback = adapter.delivery?.shouldSuppressForwardingFallback;
280+
if (!shouldSuppressForwardingFallback) {
281+
throw new Error("delivery suppression helper unavailable");
282+
}
283+
284+
expect(
285+
shouldSuppressForwardingFallback({
286+
cfg: {} as never,
287+
approvalKind: "exec",
288+
target: { channel: "matrix", to: "target-1" },
289+
request: {
290+
request: {
291+
command: "pwd",
292+
turnSourceChannel: null,
293+
turnSourceAccountId: "room-1",
294+
},
295+
} as never,
296+
}),
297+
).toBe(false);
298+
});
266299
});
267300

268301
describe("createApproverRestrictedNativeApprovalCapability", () => {

src/plugin-sdk/approval-delivery-helpers.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Approval delivery helpers format approval prompts and results for channel plugins.
12
import type { ExecApprovalRequest } from "../infra/exec-approvals.js";
23
import type { PluginApprovalRequest } from "../infra/plugin-approvals.js";
34
import type { ChannelApprovalCapability } from "./channel-contract.js";
@@ -15,49 +16,74 @@ type ChannelApprovalCapabilitySurfaces = Pick<
1516
>;
1617

1718
type ApprovalAdapterParams = {
19+
/** Full config used to inspect channel approval settings. */
1820
cfg: OpenClawConfig;
21+
/** Optional channel account id for account-scoped approval settings. */
1922
accountId?: string | null;
23+
/** Actor attempting the approval action. */
2024
senderId?: string | null;
2125
};
2226

2327
type DeliverySuppressionParams = {
28+
/** Full config used to inspect native approval delivery settings. */
2429
cfg: OpenClawConfig;
30+
/** Approval kind being delivered. */
2531
approvalKind: ApprovalKind;
32+
/** Forwarding fallback target under consideration. */
2633
target: { channel: string; accountId?: string | null };
34+
/** Approval request metadata, including original turn source when available. */
2735
request: { request: { turnSourceChannel?: string | null; turnSourceAccountId?: string | null } };
2836
};
2937

3038
type ApproverRestrictedNativeApprovalParams = {
39+
/** Channel id that owns this native approval capability. */
3140
channel: string;
41+
/** Human-readable channel label used in denial messages. */
3242
channelLabel: string;
43+
/** Lists configured account ids so DM-route availability can scan every account. */
3344
listAccountIds: (cfg: OpenClawConfig) => string[];
45+
/** Whether an account has approvers configured. */
3446
hasApprovers: (params: ApprovalAdapterParams) => boolean;
47+
/** Whether a sender can approve exec approvals for this account. */
3548
isExecAuthorizedSender: (params: ApprovalAdapterParams) => boolean;
49+
/** Optional plugin approval authorization hook; defaults to exec authorization. */
3650
isPluginAuthorizedSender?: (params: ApprovalAdapterParams) => boolean;
51+
/** Whether native approval delivery is enabled for an account. */
3752
isNativeDeliveryEnabled: (params: { cfg: OpenClawConfig; accountId?: string | null }) => boolean;
53+
/** Native delivery target preference for an account. */
3854
resolveNativeDeliveryMode: (params: {
3955
cfg: OpenClawConfig;
4056
accountId?: string | null;
4157
}) => NativeApprovalDeliveryMode;
58+
/** Requires the approval request's original turn channel to match this channel before suppression. */
4259
requireMatchingTurnSourceChannel?: boolean;
60+
/** Allows suppression when the original turn source is unavailable but the native route can still handle it. */
61+
allowMissingTurnSourceChannelForSuppression?: boolean;
62+
/** Optional account id resolver used when deciding forwarding-fallback suppression. */
4363
resolveSuppressionAccountId?: (params: DeliverySuppressionParams) => string | undefined;
64+
/** Resolves the original channel target for native approval delivery. */
4465
resolveOriginTarget?: (params: {
4566
cfg: OpenClawConfig;
4667
accountId?: string | null;
4768
approvalKind: ApprovalKind;
4869
request: NativeApprovalRequest;
4970
}) => NativeApprovalTarget | null | Promise<NativeApprovalTarget | null>;
71+
/** Resolves approver DM targets for native approval delivery. */
5072
resolveApproverDmTargets?: (params: {
5173
cfg: OpenClawConfig;
5274
accountId?: string | null;
5375
approvalKind: ApprovalKind;
5476
request: NativeApprovalRequest;
5577
}) => NativeApprovalTarget[] | Promise<NativeApprovalTarget[]>;
78+
/** Whether DM-only native delivery should also notify the origin channel. */
5679
notifyOriginWhenDmOnly?: boolean;
80+
/** Native runtime hooks used by channel-specific delivery implementations. */
5781
nativeRuntime?: ChannelApprovalCapability["nativeRuntime"];
82+
/** Optional setup description helper shown when exec approvals are unavailable. */
5883
describeExecApprovalSetup?: ChannelApprovalCapability["describeExecApprovalSetup"];
5984
};
6085

86+
/** Build the canonical approval capability for channels that restrict approvals to configured approvers. */
6187
function buildApproverRestrictedNativeApprovalCapability(
6288
params: ApproverRestrictedNativeApprovalParams,
6389
): ChannelApprovalCapability {
@@ -148,6 +174,9 @@ function buildApproverRestrictedNativeApprovalCapability(
148174
const turnSourceChannel = normalizeMessageChannel(
149175
input.request.request.turnSourceChannel,
150176
);
177+
if (!turnSourceChannel && !params.allowMissingTurnSourceChannelForSuppression) {
178+
return false;
179+
}
151180
if (turnSourceChannel && turnSourceChannel !== params.channel) {
152181
return false;
153182
}
@@ -157,6 +186,8 @@ function buildApproverRestrictedNativeApprovalCapability(
157186
(resolvedAccountId === undefined
158187
? input.target.accountId?.trim()
159188
: resolvedAccountId.trim()) || undefined;
189+
// Suppress generic forwarding only when this channel's native route can
190+
// handle the same account; otherwise the fallback is the only delivery path.
160191
return params.isNativeDeliveryEnabled({ cfg: input.cfg, accountId });
161192
},
162193
},
@@ -188,25 +219,38 @@ function buildApproverRestrictedNativeApprovalCapability(
188219
});
189220
}
190221

222+
/** Build the legacy split approval adapter shape for approver-restricted native channels. */
191223
export function createApproverRestrictedNativeApprovalAdapter(
192224
params: ApproverRestrictedNativeApprovalParams,
193225
) {
194226
return splitChannelApprovalCapability(buildApproverRestrictedNativeApprovalCapability(params));
195227
}
196228

229+
/** Assemble a channel approval capability from its auth, delivery, render, and native surfaces. */
197230
export function createChannelApprovalCapability(params: {
231+
/** Authorizes actors attempting approval actions. */
198232
authorizeActorAction?: ChannelApprovalCapability["authorizeActorAction"];
233+
/** Reports whether approval actions are generally available. */
199234
getActionAvailabilityState?: ChannelApprovalCapability["getActionAvailabilityState"];
235+
/** Reports whether exec approvals can start from the initiating surface. */
200236
getExecInitiatingSurfaceState?: ChannelApprovalCapability["getExecInitiatingSurfaceState"];
237+
/** Optional command behavior override for approval replies. */
201238
resolveApproveCommandBehavior?: ChannelApprovalCapability["resolveApproveCommandBehavior"];
239+
/** Optional setup copy for unavailable exec approval paths. */
202240
describeExecApprovalSetup?: ChannelApprovalCapability["describeExecApprovalSetup"];
241+
/** Delivery fallback and DM-route helpers. */
203242
delivery?: ChannelApprovalCapability["delivery"];
243+
/** Native runtime hooks for channel-specific approval delivery. */
204244
nativeRuntime?: ChannelApprovalCapability["nativeRuntime"];
245+
/** Render hooks for pending/resolved approval payloads. */
205246
render?: ChannelApprovalCapability["render"];
247+
/** Native target/capability discovery hooks. */
206248
native?: ChannelApprovalCapability["native"];
207249
/** @deprecated Pass delivery/nativeRuntime/render/native directly. */
208250
approvals?: Partial<ChannelApprovalCapabilitySurfaces>;
209251
}): ChannelApprovalCapability {
252+
// Keep the approvals alias for shipped plugin-sdk callers; registry tests track
253+
// this compatibility marker until the public deprecation window closes.
210254
const surfaces: ChannelApprovalCapabilitySurfaces = {
211255
delivery: params.delivery ?? params.approvals?.delivery,
212256
nativeRuntime: params.nativeRuntime ?? params.approvals?.nativeRuntime,
@@ -226,6 +270,7 @@ export function createChannelApprovalCapability(params: {
226270
};
227271
}
228272

273+
/** Split the canonical approval capability into the adapter shape older channel loaders consume. */
229274
export function splitChannelApprovalCapability(capability: ChannelApprovalCapability): {
230275
auth: {
231276
authorizeActorAction?: ChannelApprovalCapability["authorizeActorAction"];
@@ -254,6 +299,7 @@ export function splitChannelApprovalCapability(capability: ChannelApprovalCapabi
254299
};
255300
}
256301

302+
/** Build the canonical approval capability for approver-restricted native delivery channels. */
257303
export function createApproverRestrictedNativeApprovalCapability(
258304
params: ApproverRestrictedNativeApprovalParams,
259305
): ChannelApprovalCapability {

0 commit comments

Comments
 (0)