refactor: reuse agent outbound target resolution

Co-authored-by: Adam Holt <mail@adamholt.co.nz>
This commit is contained in:
Peter Steinberger
2026-01-17 06:54:12 +00:00
parent 6a3ed5c850
commit 7b31b280f8
4 changed files with 151 additions and 19 deletions

View File

@@ -10,8 +10,10 @@ import {
normalizeOutboundPayloads,
normalizeOutboundPayloadsForJson,
} from "../../infra/outbound/payloads.js";
import { resolveAgentDeliveryPlan } from "../../infra/outbound/agent-delivery.js";
import { resolveOutboundTarget } from "../../infra/outbound/targets.js";
import {
resolveAgentDeliveryPlan,
resolveAgentOutboundTarget,
} from "../../infra/outbound/agent-delivery.js";
import type { RuntimeEnv } from "../../runtime.js";
import { isInternalMessageChannel } from "../../utils/message-channel.js";
import type { AgentCommandOpts } from "./types.js";
@@ -51,17 +53,21 @@ export async function deliverAgentCommandResult(params: {
const targetMode =
opts.deliveryTargetMode ?? deliveryPlan.deliveryTargetMode ?? (opts.to ? "explicit" : "implicit");
const resolvedAccountId = deliveryPlan.resolvedAccountId;
const resolvedTarget =
const resolved =
deliver && isDeliveryChannelKnown && deliveryChannel
? resolveOutboundTarget({
channel: deliveryChannel,
to: deliveryPlan.resolvedTo,
? resolveAgentOutboundTarget({
cfg,
accountId: resolvedAccountId,
mode: targetMode,
plan: deliveryPlan,
targetMode,
validateExplicitTarget: true,
})
: null;
const deliveryTarget = resolvedTarget?.ok ? resolvedTarget.to : undefined;
: {
resolvedTarget: null,
resolvedTo: deliveryPlan.resolvedTo,
targetMode,
};
const resolvedTarget = resolved.resolvedTarget;
const deliveryTarget = resolved.resolvedTo;
const logDeliveryError = (err: unknown) => {
const message = `Delivery failed (${deliveryChannel}${deliveryTarget ? ` to ${deliveryTarget}` : ""}): ${String(err)}`;

View File

@@ -8,8 +8,10 @@ import {
updateSessionStore,
} from "../../config/sessions.js";
import { registerAgentRunContext } from "../../infra/agent-events.js";
import { resolveAgentDeliveryPlan } from "../../infra/outbound/agent-delivery.js";
import { resolveOutboundTarget } from "../../infra/outbound/targets.js";
import {
resolveAgentDeliveryPlan,
resolveAgentOutboundTarget,
} from "../../infra/outbound/agent-delivery.js";
import { defaultRuntime } from "../../runtime.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js";
import { normalizeSessionDeliveryFields } from "../../utils/delivery-context.js";
@@ -218,14 +220,14 @@ export const agentHandlers: GatewayRequestHandlers = {
if (!resolvedTo && isDeliverableMessageChannel(resolvedChannel)) {
const cfg = cfgForAgent ?? loadConfig();
const fallback = resolveOutboundTarget({
channel: resolvedChannel,
const fallback = resolveAgentOutboundTarget({
cfg,
accountId: resolvedAccountId,
mode: "implicit",
plan: deliveryPlan,
targetMode: "implicit",
validateExplicitTarget: false,
});
if (fallback.ok) {
resolvedTo = fallback.to;
if (fallback.resolvedTarget?.ok) {
resolvedTo = fallback.resolvedTo;
}
}

View File

@@ -0,0 +1,80 @@
import { describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
resolveOutboundTarget: vi.fn(() => ({ ok: true as const, to: "+1999" })),
}));
vi.mock("./targets.js", async () => {
const actual = await vi.importActual<typeof import("./targets.js")>("./targets.js");
return {
...actual,
resolveOutboundTarget: mocks.resolveOutboundTarget,
};
});
import type { ClawdbotConfig } from "../../config/config.js";
import { resolveAgentDeliveryPlan, resolveAgentOutboundTarget } from "./agent-delivery.js";
describe("agent delivery helpers", () => {
it("builds a delivery plan from session delivery context", () => {
const plan = resolveAgentDeliveryPlan({
sessionEntry: {
deliveryContext: { channel: "whatsapp", to: "+1555", accountId: "work" },
},
requestedChannel: "last",
explicitTo: undefined,
accountId: undefined,
wantsDelivery: true,
});
expect(plan.resolvedChannel).toBe("whatsapp");
expect(plan.resolvedTo).toBe("+1555");
expect(plan.resolvedAccountId).toBe("work");
expect(plan.deliveryTargetMode).toBe("implicit");
});
it("resolves fallback targets when no explicit destination is provided", () => {
const plan = resolveAgentDeliveryPlan({
sessionEntry: {
deliveryContext: { channel: "whatsapp" },
},
requestedChannel: "last",
explicitTo: undefined,
accountId: undefined,
wantsDelivery: true,
});
const resolved = resolveAgentOutboundTarget({
cfg: {} as ClawdbotConfig,
plan,
targetMode: "implicit",
});
expect(mocks.resolveOutboundTarget).toHaveBeenCalledTimes(1);
expect(resolved.resolvedTarget?.ok).toBe(true);
expect(resolved.resolvedTo).toBe("+1999");
});
it("skips outbound target resolution when explicit target validation is disabled", () => {
const plan = resolveAgentDeliveryPlan({
sessionEntry: {
deliveryContext: { channel: "whatsapp", to: "+1555" },
},
requestedChannel: "last",
explicitTo: "+1555",
accountId: undefined,
wantsDelivery: true,
});
mocks.resolveOutboundTarget.mockClear();
const resolved = resolveAgentOutboundTarget({
cfg: {} as ClawdbotConfig,
plan,
targetMode: "explicit",
validateExplicitTarget: false,
});
expect(mocks.resolveOutboundTarget).not.toHaveBeenCalled();
expect(resolved.resolvedTo).toBe("+1555");
});
});

View File

@@ -9,7 +9,9 @@ import {
normalizeMessageChannel,
type GatewayMessageChannel,
} from "../../utils/message-channel.js";
import { resolveSessionDeliveryTarget, type SessionDeliveryTarget } from "./targets.js";
import { resolveOutboundTarget, resolveSessionDeliveryTarget, type SessionDeliveryTarget } from "./targets.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { OutboundTargetResolution } from "./targets.js";
export type AgentDeliveryPlan = {
baseDelivery: SessionDeliveryTarget;
@@ -86,3 +88,45 @@ export function resolveAgentDeliveryPlan(params: {
deliveryTargetMode,
};
}
export function resolveAgentOutboundTarget(params: {
cfg: ClawdbotConfig;
plan: AgentDeliveryPlan;
targetMode?: ChannelOutboundTargetMode;
validateExplicitTarget?: boolean;
}): {
resolvedTarget: OutboundTargetResolution | null;
resolvedTo?: string;
targetMode: ChannelOutboundTargetMode;
} {
const targetMode =
params.targetMode ??
params.plan.deliveryTargetMode ??
(params.plan.resolvedTo ? "explicit" : "implicit");
if (!isDeliverableMessageChannel(params.plan.resolvedChannel)) {
return {
resolvedTarget: null,
resolvedTo: params.plan.resolvedTo,
targetMode,
};
}
if (params.validateExplicitTarget !== true && params.plan.resolvedTo) {
return {
resolvedTarget: null,
resolvedTo: params.plan.resolvedTo,
targetMode,
};
}
const resolvedTarget = resolveOutboundTarget({
channel: params.plan.resolvedChannel,
to: params.plan.resolvedTo,
cfg: params.cfg,
accountId: params.plan.resolvedAccountId,
mode: targetMode,
});
return {
resolvedTarget,
resolvedTo: resolvedTarget.ok ? resolvedTarget.to : params.plan.resolvedTo,
targetMode,
};
}