mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-09 05:19:32 +08:00
feat(heartbeat): add accountId config option for multi-agent routing (#8702)
* feat(heartbeat): add accountId config option for multi-agent routing Add optional accountId field to heartbeat configuration, allowing multi-agent setups to explicitly specify which Telegram account should be used for heartbeat delivery. Previously, heartbeat delivery would use the accountId from the session's deliveryContext. When a session had no prior conversation history, heartbeats would default to the first/primary account instead of the agent's intended bot. Changes: - Add accountId to HeartbeatSchema (zod-schema.agent-runtime.ts) - Use heartbeat.accountId with fallback to session accountId (targets.ts) Backward compatible: if accountId is not specified, behavior is unchanged. Closes #8695 * fix: improve heartbeat accountId routing (#8702) (thanks @lsh411) * fix: harden heartbeat accountId routing (#8702) (thanks @lsh411) * fix: expose heartbeat accountId in status (#8702) (thanks @lsh411) * chore: format status + heartbeat tests (#8702) (thanks @lsh411) --------- Co-authored-by: m1 16 512 <m116512@m1ui-MacBookAir-2.local> Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
This commit is contained in:
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Heartbeat: allow explicit accountId routing for multi-account channels. (#8702) Thanks @lsh411.
|
||||
- Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.
|
||||
- Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.
|
||||
- Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman.
|
||||
|
||||
@@ -87,6 +87,7 @@ and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
||||
includeReasoning: false, // default: false (deliver separate Reasoning: message when available)
|
||||
target: "last", // last | none | <channel id> (core or plugin, e.g. "bluebubbles")
|
||||
to: "+15551234567", // optional channel-specific override
|
||||
accountId: "ops-bot", // optional multi-account channel id
|
||||
prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
|
||||
ackMaxChars: 300, // max chars allowed after HEARTBEAT_OK
|
||||
},
|
||||
@@ -136,6 +137,35 @@ Example: two agents, only the second agent runs heartbeats.
|
||||
}
|
||||
```
|
||||
|
||||
### Multi account example
|
||||
|
||||
Use `accountId` to target a specific account on multi-account channels like Telegram:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "ops",
|
||||
heartbeat: {
|
||||
every: "1h",
|
||||
target: "telegram",
|
||||
to: "12345678",
|
||||
accountId: "ops-bot",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
"ops-bot": { botToken: "YOUR_TELEGRAM_BOT_TOKEN" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Field notes
|
||||
|
||||
- `every`: heartbeat interval (duration string; default unit = minutes).
|
||||
@@ -150,6 +180,7 @@ Example: two agents, only the second agent runs heartbeats.
|
||||
- explicit channel: `whatsapp` / `telegram` / `discord` / `googlechat` / `slack` / `msteams` / `signal` / `imessage`.
|
||||
- `none`: run the heartbeat but **do not deliver** externally.
|
||||
- `to`: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp or a Telegram chat id).
|
||||
- `accountId`: optional account id for multi-account channels. When `target: "last"`, the account id applies to the resolved last channel if it supports accounts; otherwise it is ignored. If the account id does not match a configured account for the resolved channel, delivery is skipped.
|
||||
- `prompt`: overrides the default prompt body (not merged).
|
||||
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { withProgress } from "../cli/progress.js";
|
||||
@@ -120,6 +121,14 @@ export async function statusCommand(
|
||||
}),
|
||||
)
|
||||
: undefined;
|
||||
const lastHeartbeat =
|
||||
opts.deep && gatewayReachable
|
||||
? await callGateway<HeartbeatEventPayload | null>({
|
||||
method: "last-heartbeat",
|
||||
params: {},
|
||||
timeoutMs: opts.timeoutMs,
|
||||
}).catch(() => null)
|
||||
: null;
|
||||
|
||||
const configChannel = normalizeUpdateChannel(cfg.update?.channel);
|
||||
const channelInfo = resolveEffectiveUpdateChannel({
|
||||
@@ -157,7 +166,7 @@ export async function statusCommand(
|
||||
nodeService: nodeDaemon,
|
||||
agents: agentStatus,
|
||||
securityAudit,
|
||||
...(health || usage ? { health, usage } : {}),
|
||||
...(health || usage || lastHeartbeat ? { health, usage, lastHeartbeat } : {}),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
@@ -275,6 +284,21 @@ export async function statusCommand(
|
||||
.filter(Boolean);
|
||||
return parts.length > 0 ? parts.join(", ") : "disabled";
|
||||
})();
|
||||
const lastHeartbeatValue = (() => {
|
||||
if (!opts.deep) {
|
||||
return null;
|
||||
}
|
||||
if (!gatewayReachable) {
|
||||
return warn("unavailable");
|
||||
}
|
||||
if (!lastHeartbeat) {
|
||||
return muted("none");
|
||||
}
|
||||
const age = formatAge(Date.now() - lastHeartbeat.ts);
|
||||
const channel = lastHeartbeat.channel ?? "unknown";
|
||||
const accountLabel = lastHeartbeat.accountId ? `account ${lastHeartbeat.accountId}` : null;
|
||||
return [lastHeartbeat.status, `${age} ago`, channel, accountLabel].filter(Boolean).join(" · ");
|
||||
})();
|
||||
|
||||
const storeLabel =
|
||||
summary.sessions.paths.length > 1
|
||||
@@ -371,6 +395,7 @@ export async function statusCommand(
|
||||
{ Item: "Probes", Value: probesValue },
|
||||
{ Item: "Events", Value: eventsValue },
|
||||
{ Item: "Heartbeat", Value: heartbeatValue },
|
||||
...(lastHeartbeatValue ? [{ Item: "Last heartbeat", Value: lastHeartbeatValue }] : []),
|
||||
{
|
||||
Item: "Sessions",
|
||||
Value: `${summary.sessions.count} active · default ${defaults.model ?? "unknown"}${defaultCtx} · ${storeLabel}`,
|
||||
|
||||
@@ -182,6 +182,8 @@ export type AgentDefaultsConfig = {
|
||||
target?: "last" | "none" | ChannelId;
|
||||
/** Optional delivery override (E.164 for WhatsApp, chat id for Telegram). */
|
||||
to?: string;
|
||||
/** Optional account id for multi-account channels. */
|
||||
accountId?: string;
|
||||
/** Override the heartbeat prompt body (default: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK."). */
|
||||
prompt?: string;
|
||||
/** Max chars allowed after HEARTBEAT_OK before delivery (default: 30). */
|
||||
|
||||
@@ -24,6 +24,7 @@ export const HeartbeatSchema = z
|
||||
includeReasoning: z.boolean().optional(),
|
||||
target: z.string().optional(),
|
||||
to: z.string().optional(),
|
||||
accountId: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
ackMaxChars: z.number().int().nonnegative().optional(),
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ export type HeartbeatEventPayload = {
|
||||
ts: number;
|
||||
status: "sent" | "ok-empty" | "ok-token" | "skipped" | "failed";
|
||||
to?: string;
|
||||
accountId?: string;
|
||||
preview?: string;
|
||||
durationMs?: number;
|
||||
hasMedia?: boolean;
|
||||
|
||||
@@ -483,6 +483,80 @@ describe("resolveHeartbeatIntervalMs", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("uses explicit heartbeat accountId for telegram delivery", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-hb-"));
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const replySpy = vi.spyOn(replyModule, "getReplyFromConfig");
|
||||
const prevTelegramToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||
process.env.TELEGRAM_BOT_TOKEN = "";
|
||||
try {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: tmpDir,
|
||||
heartbeat: { every: "5m", target: "telegram", accountId: "work" },
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
work: { botToken: "test-bot-token-123" },
|
||||
},
|
||||
},
|
||||
},
|
||||
session: { store: storePath },
|
||||
};
|
||||
const sessionKey = resolveMainSessionKey(cfg);
|
||||
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
[sessionKey]: {
|
||||
sessionId: "sid",
|
||||
updatedAt: Date.now(),
|
||||
lastChannel: "telegram",
|
||||
lastProvider: "telegram",
|
||||
lastTo: "123456",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
replySpy.mockResolvedValue({ text: "Hello from heartbeat" });
|
||||
const sendTelegram = vi.fn().mockResolvedValue({
|
||||
messageId: "m1",
|
||||
chatId: "123456",
|
||||
});
|
||||
|
||||
await runHeartbeatOnce({
|
||||
cfg,
|
||||
deps: {
|
||||
sendTelegram,
|
||||
getQueueSize: () => 0,
|
||||
nowMs: () => 0,
|
||||
},
|
||||
});
|
||||
|
||||
expect(sendTelegram).toHaveBeenCalledTimes(1);
|
||||
expect(sendTelegram).toHaveBeenCalledWith(
|
||||
"123456",
|
||||
"Hello from heartbeat",
|
||||
expect.objectContaining({ accountId: "work", verbose: false }),
|
||||
);
|
||||
} finally {
|
||||
replySpy.mockRestore();
|
||||
if (prevTelegramToken === undefined) {
|
||||
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||
} else {
|
||||
process.env.TELEGRAM_BOT_TOKEN = prevTelegramToken;
|
||||
}
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not pre-resolve telegram accountId (allows config-only account tokens)", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-hb-"));
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
|
||||
@@ -25,7 +25,10 @@ import {
|
||||
resolveHeartbeatPrompt,
|
||||
runHeartbeatOnce,
|
||||
} from "./heartbeat-runner.js";
|
||||
import { resolveHeartbeatDeliveryTarget } from "./outbound/targets.js";
|
||||
import {
|
||||
resolveHeartbeatDeliveryTarget,
|
||||
resolveHeartbeatSenderContext,
|
||||
} from "./outbound/targets.js";
|
||||
|
||||
// Avoid pulling optional runtime deps during isolated runs.
|
||||
vi.mock("jiti", () => ({ createJiti: () => () => ({}) }));
|
||||
@@ -264,6 +267,42 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses explicit heartbeat accountId when provided", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { target: "telegram", to: "123", accountId: "work" },
|
||||
},
|
||||
},
|
||||
channels: { telegram: { accounts: { work: { botToken: "token" } } } },
|
||||
};
|
||||
expect(resolveHeartbeatDeliveryTarget({ cfg, entry: baseEntry })).toEqual({
|
||||
channel: "telegram",
|
||||
to: "123",
|
||||
accountId: "work",
|
||||
lastChannel: undefined,
|
||||
lastAccountId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("skips when explicit heartbeat accountId is unknown", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { target: "telegram", to: "123", accountId: "missing" },
|
||||
},
|
||||
},
|
||||
channels: { telegram: { accounts: { work: { botToken: "token" } } } },
|
||||
};
|
||||
expect(resolveHeartbeatDeliveryTarget({ cfg, entry: baseEntry })).toEqual({
|
||||
channel: "none",
|
||||
reason: "unknown-account",
|
||||
accountId: "missing",
|
||||
lastChannel: undefined,
|
||||
lastAccountId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers per-agent heartbeat overrides when provided", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: { defaults: { heartbeat: { target: "telegram", to: "123" } } },
|
||||
@@ -285,6 +324,39 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveHeartbeatSenderContext", () => {
|
||||
it("prefers delivery accountId for allowFrom resolution", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
telegram: {
|
||||
allowFrom: ["111"],
|
||||
accounts: {
|
||||
work: { allowFrom: ["222"], botToken: "token" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const entry = {
|
||||
sessionId: "sid",
|
||||
updatedAt: Date.now(),
|
||||
lastChannel: "telegram" as const,
|
||||
lastTo: "111",
|
||||
lastAccountId: "default",
|
||||
};
|
||||
const delivery = {
|
||||
channel: "telegram" as const,
|
||||
to: "999",
|
||||
accountId: "work",
|
||||
lastChannel: "telegram" as const,
|
||||
lastAccountId: "default",
|
||||
};
|
||||
|
||||
const ctx = resolveHeartbeatSenderContext({ cfg, entry, delivery });
|
||||
|
||||
expect(ctx.allowFrom).toEqual(["222"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("runHeartbeatOnce", () => {
|
||||
it("skips when agent heartbeat is not enabled", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
|
||||
@@ -534,6 +534,19 @@ export async function runHeartbeatOnce(opts: {
|
||||
const { entry, sessionKey, storePath } = resolveHeartbeatSession(cfg, agentId, heartbeat);
|
||||
const previousUpdatedAt = entry?.updatedAt;
|
||||
const delivery = resolveHeartbeatDeliveryTarget({ cfg, entry, heartbeat });
|
||||
const heartbeatAccountId = heartbeat?.accountId?.trim();
|
||||
if (delivery.reason === "unknown-account") {
|
||||
log.warn("heartbeat: unknown accountId", {
|
||||
accountId: delivery.accountId ?? heartbeatAccountId ?? null,
|
||||
target: heartbeat?.target ?? "last",
|
||||
});
|
||||
} else if (heartbeatAccountId) {
|
||||
log.info("heartbeat: using explicit accountId", {
|
||||
accountId: delivery.accountId ?? heartbeatAccountId,
|
||||
target: heartbeat?.target ?? "last",
|
||||
channel: delivery.channel,
|
||||
});
|
||||
}
|
||||
const visibility =
|
||||
delivery.channel !== "none"
|
||||
? resolveHeartbeatVisibility({
|
||||
@@ -569,6 +582,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
reason: "alerts-disabled",
|
||||
durationMs: Date.now() - startedAt,
|
||||
channel: delivery.channel !== "none" ? delivery.channel : undefined,
|
||||
accountId: delivery.accountId,
|
||||
});
|
||||
return { status: "skipped", reason: "alerts-disabled" };
|
||||
}
|
||||
@@ -626,6 +640,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
reason: opts.reason,
|
||||
durationMs: Date.now() - startedAt,
|
||||
channel: delivery.channel !== "none" ? delivery.channel : undefined,
|
||||
accountId: delivery.accountId,
|
||||
silent: !okSent,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("ok-empty") : undefined,
|
||||
});
|
||||
@@ -659,6 +674,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
reason: opts.reason,
|
||||
durationMs: Date.now() - startedAt,
|
||||
channel: delivery.channel !== "none" ? delivery.channel : undefined,
|
||||
accountId: delivery.accountId,
|
||||
silent: !okSent,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("ok-token") : undefined,
|
||||
});
|
||||
@@ -695,6 +711,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
durationMs: Date.now() - startedAt,
|
||||
hasMedia: false,
|
||||
channel: delivery.channel !== "none" ? delivery.channel : undefined,
|
||||
accountId: delivery.accountId,
|
||||
});
|
||||
return { status: "ran", durationMs: Date.now() - startedAt };
|
||||
}
|
||||
@@ -714,6 +731,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
preview: previewText?.slice(0, 200),
|
||||
durationMs: Date.now() - startedAt,
|
||||
hasMedia: mediaUrls.length > 0,
|
||||
accountId: delivery.accountId,
|
||||
});
|
||||
return { status: "ran", durationMs: Date.now() - startedAt };
|
||||
}
|
||||
@@ -731,6 +749,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
durationMs: Date.now() - startedAt,
|
||||
channel: delivery.channel,
|
||||
hasMedia: mediaUrls.length > 0,
|
||||
accountId: delivery.accountId,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : undefined,
|
||||
});
|
||||
return { status: "ran", durationMs: Date.now() - startedAt };
|
||||
@@ -752,6 +771,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
durationMs: Date.now() - startedAt,
|
||||
hasMedia: mediaUrls.length > 0,
|
||||
channel: delivery.channel,
|
||||
accountId: delivery.accountId,
|
||||
});
|
||||
log.info("heartbeat: channel not ready", {
|
||||
channel: delivery.channel,
|
||||
@@ -801,6 +821,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
durationMs: Date.now() - startedAt,
|
||||
hasMedia: mediaUrls.length > 0,
|
||||
channel: delivery.channel,
|
||||
accountId: delivery.accountId,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : undefined,
|
||||
});
|
||||
return { status: "ran", durationMs: Date.now() - startedAt };
|
||||
@@ -811,6 +832,7 @@ export async function runHeartbeatOnce(opts: {
|
||||
reason,
|
||||
durationMs: Date.now() - startedAt,
|
||||
channel: delivery.channel !== "none" ? delivery.channel : undefined,
|
||||
accountId: delivery.accountId,
|
||||
indicatorType: visibility.useIndicator ? resolveIndicatorType("failed") : undefined,
|
||||
});
|
||||
log.error(`heartbeat failed: ${reason}`, { error: reason });
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
} from "../../utils/message-channel.js";
|
||||
import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import { normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { deliveryContextFromSession } from "../../utils/delivery-context.js";
|
||||
import {
|
||||
INTERNAL_MESSAGE_CHANNEL,
|
||||
@@ -207,11 +208,37 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
mode: "heartbeat",
|
||||
});
|
||||
|
||||
const heartbeatAccountId = heartbeat?.accountId?.trim();
|
||||
// Use explicit accountId from heartbeat config if provided, otherwise fall back to session
|
||||
let effectiveAccountId = heartbeatAccountId || resolvedTarget.accountId;
|
||||
|
||||
if (heartbeatAccountId && resolvedTarget.channel) {
|
||||
const plugin = getChannelPlugin(resolvedTarget.channel);
|
||||
const listAccountIds = plugin?.config.listAccountIds;
|
||||
const accountIds = listAccountIds ? listAccountIds(cfg) : [];
|
||||
if (accountIds.length > 0) {
|
||||
const normalizedAccountId = normalizeAccountId(heartbeatAccountId);
|
||||
const normalizedAccountIds = new Set(
|
||||
accountIds.map((accountId) => normalizeAccountId(accountId)),
|
||||
);
|
||||
if (!normalizedAccountIds.has(normalizedAccountId)) {
|
||||
return {
|
||||
channel: "none",
|
||||
reason: "unknown-account",
|
||||
accountId: normalizedAccountId,
|
||||
lastChannel: resolvedTarget.lastChannel,
|
||||
lastAccountId: resolvedTarget.lastAccountId,
|
||||
};
|
||||
}
|
||||
effectiveAccountId = normalizedAccountId;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolvedTarget.channel || !resolvedTarget.to) {
|
||||
return {
|
||||
channel: "none",
|
||||
reason: "no-target",
|
||||
accountId: resolvedTarget.accountId,
|
||||
accountId: effectiveAccountId,
|
||||
lastChannel: resolvedTarget.lastChannel,
|
||||
lastAccountId: resolvedTarget.lastAccountId,
|
||||
};
|
||||
@@ -221,14 +248,14 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
channel: resolvedTarget.channel,
|
||||
to: resolvedTarget.to,
|
||||
cfg,
|
||||
accountId: resolvedTarget.accountId,
|
||||
accountId: effectiveAccountId,
|
||||
mode: "heartbeat",
|
||||
});
|
||||
if (!resolved.ok) {
|
||||
return {
|
||||
channel: "none",
|
||||
reason: "no-target",
|
||||
accountId: resolvedTarget.accountId,
|
||||
accountId: effectiveAccountId,
|
||||
lastChannel: resolvedTarget.lastChannel,
|
||||
lastAccountId: resolvedTarget.lastAccountId,
|
||||
};
|
||||
@@ -241,7 +268,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
channel: resolvedTarget.channel,
|
||||
to: resolvedTarget.to,
|
||||
cfg,
|
||||
accountId: resolvedTarget.accountId,
|
||||
accountId: effectiveAccountId,
|
||||
mode: "explicit",
|
||||
});
|
||||
if (explicit.ok && explicit.to !== resolved.to) {
|
||||
@@ -253,7 +280,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
|
||||
channel: resolvedTarget.channel,
|
||||
to: resolved.to,
|
||||
reason,
|
||||
accountId: resolvedTarget.accountId,
|
||||
accountId: effectiveAccountId,
|
||||
lastChannel: resolvedTarget.lastChannel,
|
||||
lastAccountId: resolvedTarget.lastAccountId,
|
||||
};
|
||||
@@ -301,11 +328,13 @@ export function resolveHeartbeatSenderContext(params: {
|
||||
}): HeartbeatSenderContext {
|
||||
const provider =
|
||||
params.delivery.channel !== "none" ? params.delivery.channel : params.delivery.lastChannel;
|
||||
const accountId =
|
||||
params.delivery.accountId ??
|
||||
(provider === params.delivery.lastChannel ? params.delivery.lastAccountId : undefined);
|
||||
const allowFrom = provider
|
||||
? (getChannelPlugin(provider)?.config.resolveAllowFrom?.({
|
||||
cfg: params.cfg,
|
||||
accountId:
|
||||
provider === params.delivery.lastChannel ? params.delivery.lastAccountId : undefined,
|
||||
accountId,
|
||||
}) ?? [])
|
||||
: [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user