Skip to content

Commit 254bb7c

Browse files
authored
ui(cron): add advanced controls for run-if-due and routing (#31244)
* ui(cron): add advanced run controls and routing fields * ui(cron): gate delivery account id to announce mode * ui(cron): allow clearing delivery account id in editor * cron: persist payload lightContext updates * tests(cron): fix payload lightContext assertion typing
1 parent 1272176 commit 254bb7c

10 files changed

Lines changed: 418 additions & 7 deletions

File tree

src/cron/service.jobs.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,53 @@ describe("applyJobPatch", () => {
137137
expect(job.delivery?.accountId).toBeUndefined();
138138
});
139139

140+
it("persists agentTurn payload.lightContext updates when editing existing jobs", () => {
141+
const job = createIsolatedAgentTurnJob("job-light-context", {
142+
mode: "announce",
143+
channel: "telegram",
144+
});
145+
job.payload = {
146+
kind: "agentTurn",
147+
message: "do it",
148+
lightContext: true,
149+
};
150+
151+
applyJobPatch(job, {
152+
payload: {
153+
kind: "agentTurn",
154+
message: "do it",
155+
lightContext: false,
156+
},
157+
});
158+
159+
expect(job.payload.kind).toBe("agentTurn");
160+
if (job.payload.kind === "agentTurn") {
161+
expect(job.payload.lightContext).toBe(false);
162+
}
163+
});
164+
165+
it("applies payload.lightContext when replacing payload kind via patch", () => {
166+
const job = createIsolatedAgentTurnJob("job-light-context-switch", {
167+
mode: "announce",
168+
channel: "telegram",
169+
});
170+
job.payload = { kind: "systemEvent", text: "ping" };
171+
172+
applyJobPatch(job, {
173+
payload: {
174+
kind: "agentTurn",
175+
message: "do it",
176+
lightContext: true,
177+
},
178+
});
179+
180+
const payload = job.payload as CronJob["payload"];
181+
expect(payload.kind).toBe("agentTurn");
182+
if (payload.kind === "agentTurn") {
183+
expect(payload.lightContext).toBe(true);
184+
}
185+
});
186+
140187
it("rejects webhook delivery without a valid http(s) target URL", () => {
141188
const expectedError = "cron webhook delivery requires delivery.to to be a valid http(s) URL";
142189
const cases = [

src/cron/service/jobs.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,9 @@ function mergeCronPayload(existing: CronPayload, patch: CronPayloadPatch): CronP
564564
if (typeof patch.timeoutSeconds === "number") {
565565
next.timeoutSeconds = patch.timeoutSeconds;
566566
}
567+
if (typeof patch.lightContext === "boolean") {
568+
next.lightContext = patch.lightContext;
569+
}
567570
if (typeof patch.allowUnsafeExternalContent === "boolean") {
568571
next.allowUnsafeExternalContent = patch.allowUnsafeExternalContent;
569572
}
@@ -641,6 +644,7 @@ function buildPayloadFromPatch(patch: CronPayloadPatch): CronPayload {
641644
model: patch.model,
642645
thinking: patch.thinking,
643646
timeoutSeconds: patch.timeoutSeconds,
647+
lightContext: patch.lightContext,
644648
allowUnsafeExternalContent: patch.allowUnsafeExternalContent,
645649
deliver: patch.deliver,
646650
channel: patch.channel,

ui/src/ui/app-defaults.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const DEFAULT_CRON_FORM: CronFormState = {
1414
name: "",
1515
description: "",
1616
agentId: "",
17+
sessionKey: "",
1718
clearAgent: false,
1819
enabled: true,
1920
deleteAfterRun: true,
@@ -32,9 +33,11 @@ export const DEFAULT_CRON_FORM: CronFormState = {
3233
payloadText: "",
3334
payloadModel: "",
3435
payloadThinking: "",
36+
payloadLightContext: false,
3537
deliveryMode: "announce",
3638
deliveryChannel: "last",
3739
deliveryTo: "",
40+
deliveryAccountId: "",
3841
deliveryBestEffort: false,
3942
failureAlertMode: "inherit",
4043
failureAlertAfter: "2",

ui/src/ui/app-render.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export function renderApp(state: AppViewState) {
214214
...jobToSuggestions,
215215
...accountToSuggestions,
216216
]);
217+
const accountSuggestions = uniquePreserveOrder(accountToSuggestions);
217218
const deliveryToSuggestions =
218219
state.cronForm.deliveryMode === "webhook"
219220
? rawDeliveryToSuggestions.filter((value) => isHttpUrl(value))
@@ -482,6 +483,7 @@ export function renderApp(state: AppViewState) {
482483
thinkingSuggestions: CRON_THINKING_SUGGESTIONS,
483484
timezoneSuggestions: CRON_TIMEZONE_SUGGESTIONS,
484485
deliveryToSuggestions,
486+
accountSuggestions,
485487
onFormChange: (patch) => {
486488
state.cronForm = normalizeCronFormState({ ...state.cronForm, ...patch });
487489
state.cronFieldErrors = validateCronForm(state.cronForm);
@@ -492,7 +494,7 @@ export function renderApp(state: AppViewState) {
492494
onClone: (job) => startCronClone(state, job),
493495
onCancelEdit: () => cancelCronEdit(state),
494496
onToggle: (job, enabled) => toggleCronJob(state, job, enabled),
495-
onRun: (job) => runCronJob(state, job),
497+
onRun: (job, mode) => runCronJob(state, job, mode ?? "force"),
496498
onRemove: (job) => removeCronJob(state, job),
497499
onLoadRuns: async (jobId) => {
498500
updateCronRunsFilter(state, { cronRunsScope: "job" });

0 commit comments

Comments
 (0)