feat(cron): add --account flag for multi-account delivery routing (#26284)

* feat(cron): add --account flag for multi-account delivery routing

Add support for explicit delivery account routing in cron jobs across CLI, normalization, delivery planning, and isolated delivery target resolution.

Highlights:
- Add --account <id> to cron add and cron edit
- Add optional delivery.accountId to cron types and delivery plan
- Normalize and trim delivery.accountId in cron create/update normalization
- Prefer explicit accountId over session lastAccountId and bindings fallback
- Thread accountId through isolated cron run delivery resolution
- Preserve cron edit --best-effort-deliver/--no-best-effort-deliver behavior by keeping implicit announce mode
- Expand tests for account passthrough/merge/precedence and CLI account flows

* cron: resolve rebase duplicate accountId fields

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Marvin
2026-02-28 17:57:49 +01:00
committed by GitHub
parent e1c8094ad0
commit 5e2ef0e883
13 changed files with 218 additions and 26 deletions

View File

@@ -42,8 +42,8 @@ export async function resolveDeliveryTarget(
jobPayload: {
channel?: "last" | ChannelId;
to?: string;
sessionKey?: string;
accountId?: string;
sessionKey?: string;
},
): Promise<DeliveryTargetResolution> {
const requestedChannel = typeof jobPayload.channel === "string" ? jobPayload.channel : "last";
@@ -101,11 +101,14 @@ export async function resolveDeliveryTarget(
const mode = resolved.mode as "explicit" | "implicit";
let toCandidate = resolved.to;
// When the session has no lastAccountId (e.g. first-run isolated cron
// session), fall back to the agent's bound account from bindings config.
// This ensures the message tool in isolated sessions resolves the correct
// bot token for multi-account setups.
let accountId = resolved.accountId;
// Prefer an explicit accountId from the job's delivery config (set via
// --account on cron add/edit). Fall back to the session's lastAccountId,
// then to the agent's bound account from bindings config.
const explicitAccountId =
typeof jobPayload.accountId === "string" && jobPayload.accountId.trim()
? jobPayload.accountId.trim()
: undefined;
let accountId = explicitAccountId ?? resolved.accountId;
if (!accountId && channel) {
const bindings = buildChannelAccountBindings(cfg);
const byAgent = bindings.get(channel);
@@ -115,11 +118,6 @@ export async function resolveDeliveryTarget(
}
}
// Explicit delivery account should override inferred session/binding account.
if (jobPayload.accountId) {
accountId = jobPayload.accountId;
}
// Carry threadId when it was explicitly set (from :topic: parsing or config)
// or when delivering to the same recipient as the session's last conversation.
// Session-derived threadIds are dropped when the target differs to prevent

View File

@@ -317,8 +317,8 @@ export async function runCronIsolatedAgentTurn(params: {
const resolvedDelivery = await resolveDeliveryTarget(cfgWithAgentDefaults, agentId, {
channel: deliveryPlan.channel ?? "last",
to: deliveryPlan.to,
sessionKey: params.job.sessionKey,
accountId: deliveryPlan.accountId,
sessionKey: params.job.sessionKey,
});
const { formattedTime, timeLine } = resolveCronStyleNow(params.cfg, now);