mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 18:58:26 +00:00
* fix(cron): comprehensive cron scheduling and delivery fixes - Fix delivery target resolution for isolated agent cron jobs - Improve schedule parsing and validation - Add job retry logic and error handling - Enhance cron ops with better state management - Add timer improvements for more reliable cron execution - Add cron event type to protocol schema - Support cron events in heartbeat runner (skip empty-heartbeat check, use dedicated CRON_EVENT_PROMPT for relay) * fix: remove cron debug test and add changelog/docs notes (#11641) (thanks @tyler6204)
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
import type { ChannelId } from "../../channels/plugins/types.js";
|
|
import type { OpenClawConfig } from "../../config/config.js";
|
|
import type { OutboundChannel } from "../../infra/outbound/targets.js";
|
|
import { DEFAULT_CHAT_CHANNEL } from "../../channels/registry.js";
|
|
import {
|
|
loadSessionStore,
|
|
resolveAgentMainSessionKey,
|
|
resolveStorePath,
|
|
} from "../../config/sessions.js";
|
|
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
|
|
import {
|
|
resolveOutboundTarget,
|
|
resolveSessionDeliveryTarget,
|
|
} from "../../infra/outbound/targets.js";
|
|
|
|
export async function resolveDeliveryTarget(
|
|
cfg: OpenClawConfig,
|
|
agentId: string,
|
|
jobPayload: {
|
|
channel?: "last" | ChannelId;
|
|
to?: string;
|
|
},
|
|
): Promise<{
|
|
channel: Exclude<OutboundChannel, "none">;
|
|
to?: string;
|
|
accountId?: string;
|
|
threadId?: string | number;
|
|
mode: "explicit" | "implicit";
|
|
error?: Error;
|
|
}> {
|
|
const requestedChannel = typeof jobPayload.channel === "string" ? jobPayload.channel : "last";
|
|
const explicitTo = typeof jobPayload.to === "string" ? jobPayload.to : undefined;
|
|
const allowMismatchedLastTo = requestedChannel === "last";
|
|
|
|
const sessionCfg = cfg.session;
|
|
const mainSessionKey = resolveAgentMainSessionKey({ cfg, agentId });
|
|
const storePath = resolveStorePath(sessionCfg?.store, { agentId });
|
|
const store = loadSessionStore(storePath);
|
|
const main = store[mainSessionKey];
|
|
|
|
const preliminary = resolveSessionDeliveryTarget({
|
|
entry: main,
|
|
requestedChannel,
|
|
explicitTo,
|
|
allowMismatchedLastTo,
|
|
});
|
|
|
|
let fallbackChannel: Exclude<OutboundChannel, "none"> | undefined;
|
|
if (!preliminary.channel) {
|
|
try {
|
|
const selection = await resolveMessageChannelSelection({ cfg });
|
|
fallbackChannel = selection.channel;
|
|
} catch {
|
|
fallbackChannel = preliminary.lastChannel ?? DEFAULT_CHAT_CHANNEL;
|
|
}
|
|
}
|
|
|
|
const resolved = fallbackChannel
|
|
? resolveSessionDeliveryTarget({
|
|
entry: main,
|
|
requestedChannel,
|
|
explicitTo,
|
|
fallbackChannel,
|
|
allowMismatchedLastTo,
|
|
mode: preliminary.mode,
|
|
})
|
|
: preliminary;
|
|
|
|
const channel = resolved.channel ?? fallbackChannel ?? DEFAULT_CHAT_CHANNEL;
|
|
const mode = resolved.mode as "explicit" | "implicit";
|
|
const toCandidate = resolved.to;
|
|
|
|
// Only carry threadId when delivering to the same recipient as the session's
|
|
// last conversation. This prevents stale thread IDs (e.g. from a Telegram
|
|
// supergroup topic) from being sent to a different target (e.g. a private
|
|
// chat) where they would cause API errors.
|
|
const threadId =
|
|
resolved.threadId && resolved.to && resolved.to === resolved.lastTo
|
|
? resolved.threadId
|
|
: undefined;
|
|
|
|
if (!toCandidate) {
|
|
return {
|
|
channel,
|
|
to: undefined,
|
|
accountId: resolved.accountId,
|
|
threadId,
|
|
mode,
|
|
};
|
|
}
|
|
|
|
const docked = resolveOutboundTarget({
|
|
channel,
|
|
to: toCandidate,
|
|
cfg,
|
|
accountId: resolved.accountId,
|
|
mode,
|
|
});
|
|
return {
|
|
channel,
|
|
to: docked.ok ? docked.to : undefined,
|
|
accountId: resolved.accountId,
|
|
threadId,
|
|
mode,
|
|
error: docked.ok ? undefined : docked.error,
|
|
};
|
|
}
|