fix(telegram): scope native approvals by target account

This commit is contained in:
Peter Steinberger
2026-04-28 11:37:21 +01:00
parent 279e6453fc
commit 6dec2e1852
2 changed files with 123 additions and 0 deletions

View File

@@ -304,6 +304,83 @@ describe("telegram exec approvals", () => {
).toBe(false);
});
it("scopes native exec approval handling to configured target accountIds", () => {
const cfg = {
...buildMultiAccountTelegramConfig({}),
approvals: {
exec: {
enabled: true,
mode: "targets",
targets: [{ channel: "telegram", to: "123", accountId: "ops" }],
},
},
} as OpenClawConfig;
const request: TelegramExecApprovalRequest = {
id: "req-target-account",
request: {
command: "echo hi",
sessionKey: "agent:ops:main",
},
createdAtMs: 0,
expiresAtMs: 1000,
};
expect(
shouldHandleTelegramExecApprovalRequest({
cfg,
accountId: "default",
request,
}),
).toBe(false);
expect(
shouldHandleTelegramExecApprovalRequest({
cfg,
accountId: "ops",
request,
}),
).toBe(true);
});
it("preserves unscoped telegram targets when mixed with scoped target accountIds", () => {
const cfg = {
...buildMultiAccountTelegramConfig({}),
approvals: {
exec: {
enabled: true,
mode: "targets",
targets: [
{ channel: "telegram", to: "123" },
{ channel: "telegram", to: "456", accountId: "ops" },
],
},
},
} as OpenClawConfig;
const request: TelegramExecApprovalRequest = {
id: "req-mixed-target-account",
request: {
command: "echo hi",
sessionKey: "agent:ops:main",
},
createdAtMs: 0,
expiresAtMs: 1000,
};
expect(
shouldHandleTelegramExecApprovalRequest({
cfg,
accountId: "default",
request,
}),
).toBe(true);
expect(
shouldHandleTelegramExecApprovalRequest({
cfg,
accountId: "ops",
request,
}),
).toBe(true);
});
it("ignores disabled telegram accounts when checking foreign-channel ambiguity", () => {
const cfg = buildMultiAccountTelegramConfig({ opsOverrides: { enabled: false } });
const request = makeForeignChannelApprovalRequest({ id: "req-6" });

View File

@@ -109,11 +109,57 @@ function countTelegramExecApprovalEligibleAccounts(params: {
}).length;
}
function isExecApprovalRequest(
request: ExecApprovalRequest | PluginApprovalRequest,
): request is ExecApprovalRequest {
return "command" in request.request;
}
function isTargetForwardingMode(mode?: string): boolean {
return mode === "targets" || mode === "both";
}
function matchesExplicitTelegramForwardTargetAccount(params: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ExecApprovalRequest | PluginApprovalRequest;
}): boolean | undefined {
const forwardingConfig = isExecApprovalRequest(params.request)
? params.cfg.approvals?.exec
: params.cfg.approvals?.plugin;
if (!forwardingConfig?.enabled || !isTargetForwardingMode(forwardingConfig.mode)) {
return undefined;
}
const telegramTargets = (forwardingConfig.targets ?? []).filter(
(target) => normalizeLowercaseStringOrEmpty(target.channel) === "telegram",
);
if (telegramTargets.some((target) => !normalizeOptionalString(target.accountId))) {
return undefined;
}
const scopedTelegramAccountIds = telegramTargets
.map((target) => normalizeOptionalString(target.accountId))
.filter((accountId): accountId is string => Boolean(accountId));
if (scopedTelegramAccountIds.length === 0) {
return undefined;
}
const normalizedAccountId = params.accountId ? normalizeAccountId(params.accountId) : "";
return (
Boolean(normalizedAccountId) &&
scopedTelegramAccountIds.some(
(accountId) => normalizeAccountId(accountId) === normalizedAccountId,
)
);
}
function matchesTelegramRequestAccount(params: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ExecApprovalRequest | PluginApprovalRequest;
}): boolean {
const explicitTargetMatch = matchesExplicitTelegramForwardTargetAccount(params);
if (explicitTargetMatch !== undefined) {
return explicitTargetMatch;
}
const turnSourceChannel = normalizeLowercaseStringOrEmpty(
params.request.request.turnSourceChannel,
);