

























@@ -163,6 +163,48 @@ describe("cron controller", () => {
163163});
164164});
165165166+it('omits delivery.channel when the form still uses the "last" sentinel', async () => {
167+const request = vi.fn(async (method: string, _payload?: unknown) => {
168+if (method === "cron.add") {
169+return { id: "job-last-add" };
170+}
171+if (method === "cron.list") {
172+return { jobs: [] };
173+}
174+if (method === "cron.status") {
175+return { enabled: true, jobs: 0, nextWakeAtMs: null };
176+}
177+return {};
178+});
179+180+const state = createState({
181+client: { request } as unknown as CronState["client"],
182+cronForm: {
183+ ...DEFAULT_CRON_FORM,
184+name: "implicit channel",
185+scheduleKind: "cron",
186+cronExpr: "0 * * * *",
187+sessionTarget: "isolated",
188+wakeMode: "next-heartbeat",
189+payloadKind: "agentTurn",
190+payloadText: "run this",
191+deliveryMode: "announce",
192+deliveryChannel: "last",
193+},
194+});
195+196+await addCronJob(state);
197+198+const addCall = request.mock.calls.find(([method]) => method === "cron.add");
199+expect(addCall).toBeDefined();
200+expect(addCall?.[1]).toMatchObject({
201+delivery: { mode: "announce" },
202+});
203+expect(
204+(addCall?.[1] as { delivery?: { channel?: string } } | undefined)?.delivery?.channel,
205+).toBeUndefined();
206+});
207+166208it("forwards lightContext in cron payload", async () => {
167209const request = vi.fn(async (method: string, _payload?: unknown) => {
168210if (method === "cron.add") {
@@ -485,6 +527,97 @@ describe("cron controller", () => {
485527expect(state.cronForm.deliveryAccountId).toBe("bot-2");
486528});
487529530+it('keeps implicit announce delivery implicit when editing a job that shows "last" in the form', async () => {
531+const request = vi.fn(async (method: string, _payload?: unknown) => {
532+if (method === "cron.update") {
533+return { id: "job-implicit-delivery" };
534+}
535+if (method === "cron.list") {
536+return { jobs: [{ id: "job-implicit-delivery" }] };
537+}
538+if (method === "cron.status") {
539+return { enabled: true, jobs: 1, nextWakeAtMs: null };
540+}
541+return {};
542+});
543+const job = {
544+id: "job-implicit-delivery",
545+name: "Implicit delivery",
546+enabled: true,
547+createdAtMs: 0,
548+updatedAtMs: 0,
549+schedule: { kind: "cron" as const, expr: "0 * * * *" },
550+sessionTarget: "isolated" as const,
551+wakeMode: "next-heartbeat" as const,
552+payload: { kind: "agentTurn" as const, message: "run" },
553+delivery: { mode: "announce" as const, to: "123" },
554+state: {},
555+};
556+const state = createState({
557+client: { request } as unknown as CronState["client"],
558+cronJobs: [job],
559+});
560+561+startCronEdit(state, job);
562+await addCronJob(state);
563+564+const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
565+expect(updateCall).toBeDefined();
566+expect(updateCall?.[1]).toMatchObject({
567+id: "job-implicit-delivery",
568+patch: {
569+delivery: { mode: "announce", to: "123" },
570+},
571+});
572+expect(
573+(updateCall?.[1] as { patch?: { delivery?: { channel?: string } } } | undefined)?.patch
574+?.delivery?.channel,
575+).toBeUndefined();
576+});
577+578+it('sends delivery.channel="last" when editing clears an explicit channel back to implicit-last', async () => {
579+const request = vi.fn(async (method: string, _payload?: unknown) => {
580+if (method === "cron.update") {
581+return { id: "job-clear-delivery-channel" };
582+}
583+if (method === "cron.list") {
584+return { jobs: [{ id: "job-clear-delivery-channel" }] };
585+}
586+if (method === "cron.status") {
587+return { enabled: true, jobs: 1, nextWakeAtMs: null };
588+}
589+return {};
590+});
591+const job = {
592+id: "job-clear-delivery-channel",
593+name: "Clear delivery channel",
594+enabled: true,
595+createdAtMs: 0,
596+updatedAtMs: 0,
597+schedule: { kind: "cron" as const, expr: "0 * * * *" },
598+sessionTarget: "isolated" as const,
599+wakeMode: "next-heartbeat" as const,
600+payload: { kind: "agentTurn" as const, message: "run" },
601+delivery: { mode: "announce" as const, channel: "telegram", to: "123" },
602+state: {},
603+};
604+const state = createState({
605+client: { request } as unknown as CronState["client"],
606+cronJobs: [job],
607+});
608+609+startCronEdit(state, job);
610+state.cronForm.deliveryChannel = "last";
611+await addCronJob(state);
612+613+const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
614+expect(updateCall).toBeDefined();
615+expect(
616+(updateCall?.[1] as { patch?: { delivery?: { channel?: string } } } | undefined)?.patch
617+?.delivery?.channel,
618+).toBe("last");
619+});
620+488621it("includes model/thinking/stagger/bestEffort in cron.update patch", async () => {
489622const request = vi.fn(async (method: string, _payload?: unknown) => {
490623if (method === "cron.update") {
@@ -684,6 +817,103 @@ describe("cron controller", () => {
684817});
685818});
686819820+it('keeps implicit failure alert delivery implicit when editing a job that shows "last" in the form', async () => {
821+const request = vi.fn(async (method: string, _payload?: unknown) => {
822+if (method === "cron.update") {
823+return { id: "job-alert-implicit-channel" };
824+}
825+if (method === "cron.list") {
826+return { jobs: [{ id: "job-alert-implicit-channel" }] };
827+}
828+if (method === "cron.status") {
829+return { enabled: true, jobs: 1, nextWakeAtMs: null };
830+}
831+return {};
832+});
833+const job = {
834+id: "job-alert-implicit-channel",
835+name: "Implicit failure alert",
836+enabled: true,
837+createdAtMs: 0,
838+updatedAtMs: 0,
839+schedule: { kind: "cron" as const, expr: "0 * * * *" },
840+sessionTarget: "isolated" as const,
841+wakeMode: "next-heartbeat" as const,
842+payload: { kind: "agentTurn" as const, message: "run" },
843+delivery: { mode: "announce" as const, channel: "telegram", to: "123" },
844+failureAlert: { after: 2, to: "123" },
845+state: {},
846+};
847+const state = createState({
848+client: { request } as unknown as CronState["client"],
849+cronJobs: [job],
850+});
851+852+startCronEdit(state, job);
853+await addCronJob(state);
854+855+const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
856+expect(updateCall).toBeDefined();
857+expect(updateCall?.[1]).toMatchObject({
858+id: "job-alert-implicit-channel",
859+patch: {
860+failureAlert: {
861+after: 2,
862+to: "123",
863+mode: "announce",
864+},
865+},
866+});
867+expect(
868+(updateCall?.[1] as { patch?: { failureAlert?: { channel?: string } } } | undefined)?.patch
869+?.failureAlert?.channel,
870+).toBeUndefined();
871+});
872+873+it('sends failureAlert.channel="last" when editing clears an explicit failure channel back to implicit-last', async () => {
874+const request = vi.fn(async (method: string, _payload?: unknown) => {
875+if (method === "cron.update") {
876+return { id: "job-clear-failure-channel" };
877+}
878+if (method === "cron.list") {
879+return { jobs: [{ id: "job-clear-failure-channel" }] };
880+}
881+if (method === "cron.status") {
882+return { enabled: true, jobs: 1, nextWakeAtMs: null };
883+}
884+return {};
885+});
886+const job = {
887+id: "job-clear-failure-channel",
888+name: "Clear failure channel",
889+enabled: true,
890+createdAtMs: 0,
891+updatedAtMs: 0,
892+schedule: { kind: "cron" as const, expr: "0 * * * *" },
893+sessionTarget: "isolated" as const,
894+wakeMode: "next-heartbeat" as const,
895+payload: { kind: "agentTurn" as const, message: "run" },
896+delivery: { mode: "announce" as const, channel: "telegram", to: "123" },
897+failureAlert: { after: 2, channel: "telegram", to: "123" },
898+state: {},
899+};
900+const state = createState({
901+client: { request } as unknown as CronState["client"],
902+cronJobs: [job],
903+});
904+905+startCronEdit(state, job);
906+state.cronForm.failureAlertChannel = "last";
907+await addCronJob(state);
908+909+const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
910+expect(updateCall).toBeDefined();
911+expect(
912+(updateCall?.[1] as { patch?: { failureAlert?: { channel?: string } } } | undefined)?.patch
913+?.failureAlert?.channel,
914+).toBe("last");
915+});
916+687917it("omits failureAlert.cooldownMs when custom cooldown is left blank", async () => {
688918const request = vi.fn(async (method: string, _payload?: unknown) => {
689919if (method === "cron.update") {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。