feat(cron): support custom session IDs and auto-bind to current session (#16511)

feat(cron): support persistent session targets for cron jobs (#9765)

Add support for `sessionTarget: "current"` and `session:<id>` so cron jobs can
bind to the creating session or a persistent named session instead of only
`main` or ephemeral `isolated` sessions.

Also:
- preserve custom session targets across reloads and restarts
- update gateway validation and normalization for the new target forms
- add cron coverage for current/custom session targets and fallback behavior
- fix merged CI regressions in Discord and diffs tests
- add a changelog entry for the new cron session behavior

Co-authored-by: kkhomej33-netizen <kkhomej33-netizen@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
This commit is contained in:
kkhomej33-netizen
2026-03-14 13:48:46 +08:00
committed by GitHub
parent 61d171ab0b
commit e7d9648fba
33 changed files with 617 additions and 118 deletions

View File

@@ -194,8 +194,13 @@ export function registerCronAddCommand(cron: Command) {
const inferredSessionTarget = payload.kind === "agentTurn" ? "isolated" : "main";
const sessionTarget =
sessionSource === "cli" ? sessionTargetRaw || "" : inferredSessionTarget;
if (sessionTarget !== "main" && sessionTarget !== "isolated") {
throw new Error("--session must be main or isolated");
const isCustomSessionTarget =
sessionTarget.toLowerCase().startsWith("session:") &&
sessionTarget.slice(8).trim().length > 0;
const isIsolatedLikeSessionTarget =
sessionTarget === "isolated" || sessionTarget === "current" || isCustomSessionTarget;
if (sessionTarget !== "main" && !isIsolatedLikeSessionTarget) {
throw new Error("--session must be main, isolated, current, or session:<id>");
}
if (opts.deleteAfterRun && opts.keepAfterRun) {
@@ -205,14 +210,14 @@ export function registerCronAddCommand(cron: Command) {
if (sessionTarget === "main" && payload.kind !== "systemEvent") {
throw new Error("Main jobs require --system-event (systemEvent).");
}
if (sessionTarget === "isolated" && payload.kind !== "agentTurn") {
throw new Error("Isolated jobs require --message (agentTurn).");
if (isIsolatedLikeSessionTarget && payload.kind !== "agentTurn") {
throw new Error("Isolated/current/custom-session jobs require --message (agentTurn).");
}
if (
(opts.announce || typeof opts.deliver === "boolean") &&
(sessionTarget !== "isolated" || payload.kind !== "agentTurn")
(!isIsolatedLikeSessionTarget || payload.kind !== "agentTurn")
) {
throw new Error("--announce/--no-deliver require --session isolated.");
throw new Error("--announce/--no-deliver require a non-main agentTurn session target.");
}
const accountId =
@@ -220,12 +225,12 @@ export function registerCronAddCommand(cron: Command) {
? opts.account.trim()
: undefined;
if (accountId && (sessionTarget !== "isolated" || payload.kind !== "agentTurn")) {
throw new Error("--account requires an isolated agentTurn job with delivery.");
if (accountId && (!isIsolatedLikeSessionTarget || payload.kind !== "agentTurn")) {
throw new Error("--account requires a non-main agentTurn job with delivery.");
}
const deliveryMode =
sessionTarget === "isolated" && payload.kind === "agentTurn"
isIsolatedLikeSessionTarget && payload.kind === "agentTurn"
? hasAnnounce
? "announce"
: hasNoDeliver

View File

@@ -247,9 +247,9 @@ export function printCronList(jobs: CronJob[], runtime = defaultRuntime) {
})();
const coloredTarget =
job.sessionTarget === "isolated"
? colorize(rich, theme.accentBright, targetLabel)
: colorize(rich, theme.accent, targetLabel);
job.sessionTarget === "main"
? colorize(rich, theme.accent, targetLabel)
: colorize(rich, theme.accentBright, targetLabel);
const coloredAgent = job.agentId
? colorize(rich, theme.info, agentLabel)
: colorize(rich, theme.muted, agentLabel);