diff --git a/apps/server/src/scheduledTasks/ScheduledTaskService.schedule.test.ts b/apps/server/src/scheduledTasks/ScheduledTaskService.schedule.test.ts index 91d58d53b684..bcb110a5da71 100644 --- a/apps/server/src/scheduledTasks/ScheduledTaskService.schedule.test.ts +++ b/apps/server/src/scheduledTasks/ScheduledTaskService.schedule.test.ts @@ -6,7 +6,6 @@ import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; -import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts"; @@ -59,52 +58,6 @@ it.effect("rejects a stale form save after deletion while preserving explicit-id }).pipe(Effect.provide(SqlitePersistence.layerMemory)), ); -it.effect("includes the scheduler zone in empty lists and subscription updates", () => - Effect.gen(function* () { - const layerDependencies = Layer.mergeAll( - NodeCrypto.layer, - Scheduler.layer, - Layer.mock(ThreadLaunchService.ThreadLaunchService)({}), - Layer.mock(ManagedProjectFolders.ManagedProjectFolders)({ - namedProjectsRoot: "/projects", - isScratchProject: () => Effect.succeed(false), - }), - Layer.mock(ThreadManagementService.ThreadManagementService)({}), - Layer.mock(SecretRequests.SecretRequests)({}), - ); - yield* Effect.gen(function* () { - const service = yield* ScheduledTaskService.ScheduledTaskService; - const timeZone = DateTime.zoneToString(DateTime.zoneMakeLocal()); - expect(yield* service.list()).toEqual({ tasks: [], timeZone }); - - const input = yield* decodeUpsertInput({ - title: "Morning review", - prompt: "Review the open pull requests.", - enabled: false, - schedule: { type: "fixed_time", timeOfDay: "09:00" }, - projectId: "project-schedule-zone", - workspaceStrategy: { type: "root" }, - modelSelection: { instanceId: "codex", model: "gpt-5.4" }, - runtimeMode: "full-access", - interactionMode: "default", - }); - const snapshots = yield* service.subscribeList().pipe( - Stream.tap((snapshot) => - snapshot.tasks.length === 0 ? service.upsert(input) : Effect.void, - ), - Stream.take(2), - Stream.runCollect, - ); - const populated = yield* service.list(); - expect(populated).toMatchObject({ - timeZone, - tasks: [{ title: "Morning review", schedule: input.schedule }], - }); - expect(snapshots).toEqual([{ tasks: [], timeZone }, populated]); - }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(layerDependencies)))); - }).pipe(Effect.provide(SqlitePersistence.layerMemory)), -); - it.effect("preserves a due run when a save only pads the scheduled hour", () => Effect.gen(function* () { const dueAt = DateTime.makeZonedUnsafe( diff --git a/apps/server/src/scheduledTasks/ScheduledTaskService.ts b/apps/server/src/scheduledTasks/ScheduledTaskService.ts index d3d2ef19c6ac..d9a962994040 100644 --- a/apps/server/src/scheduledTasks/ScheduledTaskService.ts +++ b/apps/server/src/scheduledTasks/ScheduledTaskService.ts @@ -936,10 +936,7 @@ export const layer = Layer.effect( const list: ScheduledTaskService["Service"]["list"] = () => listRows().pipe( - Effect.map((tasks) => ({ - tasks, - timeZone: DateTime.zoneToString(DateTime.zoneMakeLocal()), - })), + Effect.map((tasks) => ({ tasks })), Effect.mapError((cause) => taskError("Could not list schedule tasks.", { cause })), ); diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 7b88c268e7c3..8c3744ed776f 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -1321,7 +1321,6 @@ export const layerWsRpc = ( currentSession.scopes.includes(AuthOrchestrationOperateScope) ? result : { - ...result, tasks: result.tasks.map(({ webhook: _webhook, ...task }) => task), }; const authorizeEffect = ( diff --git a/apps/web/src/components/chat/ThreadAutomationsPanel.permissions.browser.test.tsx b/apps/web/src/components/chat/ThreadAutomationsPanel.permissions.browser.test.tsx index 0d38057c6545..e780a6350756 100644 --- a/apps/web/src/components/chat/ThreadAutomationsPanel.permissions.browser.test.tsx +++ b/apps/web/src/components/chat/ThreadAutomationsPanel.permissions.browser.test.tsx @@ -1,10 +1,4 @@ -import { - EnvironmentId, - ThreadId, - ScheduledTaskId, - type ScheduledTask, - type ScheduledTaskListResult, -} from "@t3tools/contracts"; +import { EnvironmentId, ThreadId, ScheduledTaskId, type ScheduledTask } from "@t3tools/contracts"; import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test"; @@ -34,7 +28,7 @@ vi.mock(import("../../state/use-atom-command"), async (importOriginal) => { }); vi.mock(import("../../state/query"), async (importOriginal) => ({ ...(await importOriginal()), - useEnvironmentQuery: (() => ({ data: listResult, error: null })) as never, + useEnvironmentQuery: (() => ({ data: { tasks: [task] }, error: null })) as never, })); import { ThreadAutomationsPanel } from "./ThreadAutomationsPanel"; @@ -47,15 +41,12 @@ const task = { enabled: true, schedule: { type: "interval", everyMs: 60000 }, lastRunStatus: "never", - nextRunAt: null, } as ScheduledTask; -let listResult: ScheduledTaskListResult & { timeZone?: string }; let root: Root; let container: HTMLDivElement; beforeEach(() => { vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); state.allowed = true; - listResult = { tasks: [task] }; state.toggle.mockReset().mockResolvedValue({ _tag: "Success" }); state.run.mockReset().mockResolvedValue({ _tag: "Success" }); container = document.createElement("div"); @@ -93,33 +84,3 @@ it("rejects a retained toggle after task permission is revoked", async () => { await act(async () => byLabel("Pause Daily review").click()); expect(state.toggle).toHaveBeenCalledOnce(); }); - -it("labels fixed times with the server zone and preserves labels from older servers", async () => { - const tasks: ScheduledTask[] = [ - { ...task, schedule: { type: "fixed_time", timeOfDay: "09:00" } }, - { ...task, id: ScheduledTaskId.make("interval"), title: "Interval review" }, - { - ...task, - id: ScheduledTaskId.make("webhook"), - title: "Webhook review", - schedule: { type: "webhook", signature: null }, - }, - ]; - const labels = () => Array.from(container.querySelectorAll("li p"), (row) => row.textContent); - - listResult = { tasks, timeZone: "Asia/Kolkata" }; - await renderPanel(); - expect(labels()).toEqual(["Daily at 09:00 Asia/Kolkata", "Every 1 min", "On webhook"]); - - listResult = { tasks, timeZone: "Etc/UTC" }; - await renderPanel(); - expect(labels()).toEqual(["Daily at 09:00 UTC", "Every 1 min", "On webhook"]); - - listResult = { tasks, timeZone: "UTC" }; - await renderPanel(); - expect(labels()).toEqual(["Daily at 09:00 UTC", "Every 1 min", "On webhook"]); - - listResult = { tasks }; - await renderPanel(); - expect(labels()).toEqual(["Daily at 09:00", "Every 1 min", "On webhook"]); -}); diff --git a/apps/web/src/components/chat/ThreadAutomationsPanel.tsx b/apps/web/src/components/chat/ThreadAutomationsPanel.tsx index 670ea8db3066..28fb297b3526 100644 --- a/apps/web/src/components/chat/ThreadAutomationsPanel.tsx +++ b/apps/web/src/components/chat/ThreadAutomationsPanel.tsx @@ -37,10 +37,10 @@ const STATUS_DOT_CLASS: Record = { failed: "bg-destructive", }; -function taskScheduleLine(task: ScheduledTask, timeZone: string | undefined) { +function taskScheduleLine(task: ScheduledTask) { return ( <> - {scheduleLabel(task.schedule, timeZone)} + {scheduleLabel(task.schedule)} {task.enabled && task.nextRunAt !== null ? ( <> {" · next "} @@ -185,15 +185,11 @@ export function ThreadAutomationsPanel(props: { {task.title} -

- {taskScheduleLine(task, tasksQuery.data?.timeZone)} -

+

{taskScheduleLine(task)}

{task.title}

-

- {taskScheduleLine(task, tasksQuery.data?.timeZone)} -

+

{taskScheduleLine(task)}

diff --git a/apps/web/src/components/settings/ScheduledTasksSettings.tsx b/apps/web/src/components/settings/ScheduledTasksSettings.tsx index 98988c3d278c..c22bdc188dbd 100644 --- a/apps/web/src/components/settings/ScheduledTasksSettings.tsx +++ b/apps/web/src/components/settings/ScheduledTasksSettings.tsx @@ -179,11 +179,7 @@ function splitModelKey(value: string): ModelSelection | null { }; } -function scheduleTimeZoneLabel(timeZone: string | undefined) { - return timeZone === "Etc/UTC" ? "UTC" : timeZone; -} - -export function scheduleLabel(schedule: ScheduledTaskSchedule, timeZone?: string): string { +export function scheduleLabel(schedule: ScheduledTaskSchedule): string { if (schedule.type === "webhook") return "On webhook"; if (schedule.type === "interval") { const minutes = schedule.everyMs / 60_000; @@ -198,8 +194,7 @@ export function scheduleLabel(schedule: ScheduledTaskSchedule, timeZone?: string : weekdays.length === 5 && weekdays.every((day) => day >= 1 && day <= 5) ? "Weekdays" : weekdays.map((day) => WEEKDAY_LABELS[day]).join(", "); - const zone = scheduleTimeZoneLabel(timeZone); - return `${days} at ${schedule.timeOfDay}${zone === undefined ? "" : ` ${zone}`}`; + return `${days} at ${schedule.timeOfDay}`; } /** @@ -369,7 +364,6 @@ function ScheduledTaskEnvironmentList({ return { environment, connected: index >= 0, - timeZone: data?.timeZone, tasks: data?.tasks.filter((task) => matchesScheduledTaskScope(scope, environment.environmentId, task.projectId), ), @@ -411,7 +405,7 @@ function ScheduledTaskEnvironmentList({ return ( - {populated.map(({ environment, tasks, timeZone }) => { + {populated.map(({ environment, tasks }) => { const { primary, secondary } = settingsScopeEnvironmentLabels(environment, environments); const transport = environmentTransportLabel(environment); return ( @@ -441,7 +435,6 @@ function ScheduledTaskEnvironmentList({ key={task.id} environmentId={environment.environmentId} task={task} - timeZone={timeZone} onEdit={() => onEdit(environment.environmentId, task)} /> ))} @@ -557,7 +550,6 @@ function ScheduledTaskEnvironmentSection({ key={task.id} environmentId={environment.environmentId} task={task} - timeZone={tasksQuery.data?.timeZone} onEdit={() => onEdit(environment.environmentId, task)} /> )) @@ -571,12 +563,10 @@ function ScheduledTaskEnvironmentSection({ function ScheduledTaskRow({ environmentId, task, - timeZone, onEdit, }: { readonly environmentId: EnvironmentId; readonly task: ScheduledTask; - readonly timeZone: string | undefined; readonly onEdit: () => void; }) { const canOperate = useAtomValue( @@ -621,7 +611,7 @@ function ScheduledTaskRow({ status={
- {scheduleLabel(task.schedule, timeZone)} ·{" "} + {scheduleLabel(task.schedule)} ·{" "} {!task.enabled ? ( "Paused" ) : isWebhook ? ( @@ -1008,7 +998,6 @@ function ScheduledTaskEditorForm({ draft.editingId !== null && tasksQuery.data !== null && !tasksQuery.data.tasks.some((entry) => entry.id === draft.editingId); - const runAtZone = scheduleTimeZoneLabel(tasksQuery.data?.timeZone); const liveTask = tasksQuery.data ? (tasksQuery.data.tasks.find((entry) => entry.id === draft.editingId) ?? null) : task; @@ -1512,9 +1501,6 @@ function ScheduledTaskEditorForm({ setDraft((current) => ({ ...current, timeOfDay: event.target.value })) } /> - {runAtZone === undefined ? null : ( - {runAtZone} - )} on