diff --git a/src/cron/isolated-agent/delivery-target.runtime.ts b/src/cron/isolated-agent/delivery-target.runtime.ts new file mode 100644 index 00000000000..d22ffaa3d35 --- /dev/null +++ b/src/cron/isolated-agent/delivery-target.runtime.ts @@ -0,0 +1,4 @@ +export { getLoadedChannelPluginForRead } from "../../channels/plugins/registry-loaded-read.js"; +export { readChannelAllowFromStoreEntriesSync } from "../../pairing/allow-from-store-read.js"; +export { mapAllowFromEntries } from "../../plugin-sdk/channel-config-helpers.js"; +export { resolveFirstBoundAccountId } from "../../routing/bound-account-read.js"; diff --git a/src/cron/isolated-agent/delivery-target.ts b/src/cron/isolated-agent/delivery-target.ts index 20da3a12667..7946747ee4c 100644 --- a/src/cron/isolated-agent/delivery-target.ts +++ b/src/cron/isolated-agent/delivery-target.ts @@ -1,4 +1,3 @@ -import { getLoadedChannelPluginForRead } from "../../channels/plugins/registry-loaded-read.js"; import type { ChannelId } from "../../channels/plugins/types.public.js"; import { resolveAgentMainSessionKey } from "../../config/sessions/main-session.js"; import { resolveStorePath } from "../../config/sessions/paths.js"; @@ -9,9 +8,6 @@ import { maybeResolveIdLikeTarget } from "../../infra/outbound/target-id-resolut import { tryResolveLoadedOutboundTarget } from "../../infra/outbound/targets-loaded.js"; import { resolveSessionDeliveryTarget } from "../../infra/outbound/targets-session.js"; import type { OutboundChannel } from "../../infra/outbound/targets.js"; -import { readChannelAllowFromStoreEntriesSync } from "../../pairing/allow-from-store-read.js"; -import { mapAllowFromEntries } from "../../plugin-sdk/channel-config-helpers.js"; -import { resolveFirstBoundAccountId } from "../../routing/bound-account-read.js"; import { normalizeAccountId } from "../../routing/session-key.js"; export type DeliveryTargetResolution = @@ -56,11 +52,19 @@ async function resolveOutboundTargetWithRuntime( let channelSelectionRuntimePromise: | Promise | undefined; +let deliveryTargetRuntimePromise: + | Promise + | undefined; async function loadChannelSelectionRuntime() { channelSelectionRuntimePromise ??= import("../../infra/outbound/channel-selection.runtime.js"); return await channelSelectionRuntimePromise; } + +async function loadDeliveryTargetRuntime() { + deliveryTargetRuntimePromise ??= import("./delivery-target.runtime.js"); + return await deliveryTargetRuntimePromise; +} export async function resolveDeliveryTarget( cfg: OpenClawConfig, agentId: string, @@ -140,6 +144,7 @@ export async function resolveDeliveryTarget( : undefined; let accountId = explicitAccountId ?? resolved.accountId; if (!accountId && channel) { + const { resolveFirstBoundAccountId } = await loadDeliveryTargetRuntime(); accountId = resolveFirstBoundAccountId({ cfg, channelId: channel, agentId }); } @@ -172,34 +177,42 @@ export async function resolveDeliveryTarget( }; } - const channelPlugin = getLoadedChannelPluginForRead(channel); - const resolvedAccountId = normalizeAccountId(accountId); - const configuredAllowFromRaw = channelPlugin?.config.resolveAllowFrom?.({ - cfg, - accountId: resolvedAccountId, - }); - const configuredAllowFrom = configuredAllowFromRaw - ? mapAllowFromEntries(configuredAllowFromRaw) - : []; - const storeAllowFrom = readChannelAllowFromStoreEntriesSync( - channel, - process.env, - resolvedAccountId, - ); - const allowFromOverride = [...new Set([...configuredAllowFrom, ...storeAllowFrom])]; - const effectiveAllowFrom = mode === "implicit" ? allowFromOverride : undefined; - - if (toCandidate && mode === "implicit" && allowFromOverride.length > 0) { - const currentTargetResolution = await resolveOutboundTargetWithRuntime({ - channel, - to: toCandidate, + let effectiveAllowFrom: string[] | undefined; + if (mode === "implicit") { + const { + getLoadedChannelPluginForRead, + mapAllowFromEntries, + readChannelAllowFromStoreEntriesSync, + } = await loadDeliveryTargetRuntime(); + const channelPlugin = getLoadedChannelPluginForRead(channel); + const resolvedAccountId = normalizeAccountId(accountId); + const configuredAllowFromRaw = channelPlugin?.config.resolveAllowFrom?.({ cfg, - accountId, - mode, - allowFrom: effectiveAllowFrom, + accountId: resolvedAccountId, }); - if (!currentTargetResolution.ok) { - toCandidate = allowFromOverride[0]; + const configuredAllowFrom = configuredAllowFromRaw + ? mapAllowFromEntries(configuredAllowFromRaw) + : []; + const storeAllowFrom = readChannelAllowFromStoreEntriesSync( + channel, + process.env, + resolvedAccountId, + ); + const allowFromOverride = [...new Set([...configuredAllowFrom, ...storeAllowFrom])]; + effectiveAllowFrom = allowFromOverride; + + if (toCandidate && allowFromOverride.length > 0) { + const currentTargetResolution = await resolveOutboundTargetWithRuntime({ + channel, + to: toCandidate, + cfg, + accountId, + mode, + allowFrom: effectiveAllowFrom, + }); + if (!currentTargetResolution.ok) { + toCandidate = allowFromOverride[0]; + } } } diff --git a/src/cron/isolated-agent/run-delivery.runtime.ts b/src/cron/isolated-agent/run-delivery.runtime.ts new file mode 100644 index 00000000000..d1601a6ec4d --- /dev/null +++ b/src/cron/isolated-agent/run-delivery.runtime.ts @@ -0,0 +1,6 @@ +export { resolveDeliveryTarget } from "./delivery-target.js"; +export { + dispatchCronDelivery, + matchesMessagingToolDeliveryTarget, + resolveCronDeliveryBestEffort, +} from "./delivery-dispatch.js"; diff --git a/src/cron/isolated-agent/run.test-harness.ts b/src/cron/isolated-agent/run.test-harness.ts index c3c51ff3a67..537ec80e552 100644 --- a/src/cron/isolated-agent/run.test-harness.ts +++ b/src/cron/isolated-agent/run.test-harness.ts @@ -199,15 +199,13 @@ vi.mock("../delivery-plan.js", () => ({ resolveCronDeliveryPlan: resolveCronDeliveryPlanMock, })); -vi.mock("./delivery-target.js", () => ({ - resolveDeliveryTarget: resolveDeliveryTargetMock, -})); - -vi.mock("./delivery-dispatch.js", async () => { - const actual = - await vi.importActual("./delivery-dispatch.js"); +vi.mock("./run-delivery.runtime.js", async () => { + const actual = await vi.importActual( + "./run-delivery.runtime.js", + ); return { ...actual, + resolveDeliveryTarget: resolveDeliveryTargetMock, dispatchCronDelivery: dispatchCronDeliveryMock, }; }); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index d2c0fd876cd..d601af956de 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -6,12 +6,6 @@ import type { AgentDefaultsConfig } from "../../config/types.agent-defaults.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { resolveCronDeliveryPlan } from "../delivery-plan.js"; import type { CronJob, CronRunTelemetry } from "../types.js"; -import { - dispatchCronDelivery, - matchesMessagingToolDeliveryTarget, - resolveCronDeliveryBestEffort, -} from "./delivery-dispatch.js"; -import { resolveDeliveryTarget } from "./delivery-target.js"; import { isHeartbeatOnlyResponse, resolveCronPayloadOutcome, @@ -69,6 +63,7 @@ let cronContextRuntimePromise: Promise | undefined; +let cronDeliveryRuntimePromise: Promise | undefined; async function loadSessionStoreRuntime() { sessionStoreRuntimePromise ??= import("../../config/sessions/store.runtime.js"); @@ -100,6 +95,11 @@ async function loadCronModelCatalogRuntime() { return await cronModelCatalogRuntimePromise; } +async function loadCronDeliveryRuntime() { + cronDeliveryRuntimePromise ??= import("./run-delivery.runtime.js"); + return await cronDeliveryRuntimePromise; +} + function hasConfiguredAuthProfiles(cfg: OpenClawConfig): boolean { return ( Boolean(cfg.auth?.profiles && Object.keys(cfg.auth.profiles).length > 0) || @@ -115,8 +115,9 @@ export type { RunCronAgentTurnResult } from "./run.types.js"; type CronExecutionRuntime = typeof import("./run-executor.runtime.js"); type CronExecutionResult = Awaited>; -type ResolvedCronDeliveryTarget = Awaited>; type CronModelCatalogRuntime = typeof import("./run-model-catalog.runtime.js"); +type CronDeliveryRuntime = typeof import("./run-delivery.runtime.js"); +type ResolvedCronDeliveryTarget = Awaited>; type IsolatedDeliveryContract = "cron-owned" | "shared"; @@ -165,6 +166,7 @@ async function resolveCronDeliveryContext(params: { }), }; } + const { resolveDeliveryTarget } = await loadCronDeliveryRuntime(); const resolvedDelivery = await resolveDeliveryTarget(params.cfg, params.agentId, { channel: deliveryPlan.channel ?? "last", to: deliveryPlan.to, @@ -644,6 +646,11 @@ async function finalizeCronRun(params: { const skipHeartbeatDelivery = prepared.deliveryRequested && isHeartbeatOnlyResponse(payloads, resolveHeartbeatAckMaxChars(prepared.agentCfg)); + const { + dispatchCronDelivery, + matchesMessagingToolDeliveryTarget, + resolveCronDeliveryBestEffort, + } = await loadCronDeliveryRuntime(); const skipMessagingToolDelivery = (prepared.input.deliveryContract ?? "cron-owned") === "shared" && prepared.deliveryRequested &&