import { useCustomerQuerySession } from "../runtime/customer-query-provider"; import { LoadingState } from "../components/loading-state"; import { ClockIcon } from "@octanejs/phosphor-icons"; import { Link, useParams } from "@octanejs/tanstack-router"; import { useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "octane"; import { Button } from "octane-kumo/components/button"; import { Empty } from "octane-kumo/components/empty"; import { Loader } from "octane-kumo/components/loader"; import { LayerCard } from "octane-kumo/components/layer-card"; import { Dialog } from "octane-kumo/components/dialog"; import { Input, InputArea } from "octane-kumo/components/input"; import { Select } from "octane-kumo/components/select"; import { Switch } from "octane-kumo/components/switch"; import { MAX_TASKS, MAX_TASK_NAME, MAX_TASK_INSTRUCTIONS, type TaskRunStatus, type TaskSummary, } from "../../shared/tasks"; import { TaskSession } from "../runtime/task-session"; import { activeRun, emptyTaskHistory } from "../runtime/task-history"; import { useTaskListQuery } from "../runtime/queries/use-task-list-query"; import { useTaskRunsQuery } from "../runtime/queries/use-task-runs-query"; import { useShellSession } from "../runtime/shell-session"; const kinds = [ { value: "cron", label: "Recurring" }, { value: "once", label: "One time" }, ]; const statusLabels: Record = { dispatching: "Submitting", dispatch_error: "Submission failed", pending: "Queued", running: "Running", completed: "Completed", error: "Failed", aborted: "Cancelled", skipped: "Skipped", }; const failures = { dispatch_failed: "Could not submit this run.", turn_failed: "The agent turn failed.", turn_aborted: "The agent turn was cancelled.", turn_skipped: "The agent turn was skipped.", }; export function taskTime(instant: string | null) { return instant ? `${instant.slice(0, 10)} ${instant.slice(11, 19)} UTC` : "—"; } export function taskScheduleLabel(task: TaskSummary) { return task.schedule.kind === "once" ? `Once · ${taskTime(task.schedule.at)}` : `${task.schedule.expression} · UTC`; } function NextRun({ task }: { task: TaskSummary }) { return task.schedulingError ? ( Schedule unavailable. Refresh to retry. ) : ( {task.nextRunAt ? taskTime(task.nextRunAt) : task.enabled ? "No upcoming run" : "None while paused"} ); } export function Tasks() { const params = useParams({ strict: false }); const selected = "id" in params ? (params.id ?? null) : null; const { conversations, session: shell, creating } = useShellSession(); const cache = useCustomerQuerySession(); const [session] = useState(() => new TaskSession(cache)); const view = useSyncExternalStore( session.subscribe, session.getSnapshot, session.getServerSnapshot, ); const { connected, busy, error: commandError, notice, editor, deleting, } = view; const tasksQuery = useTaskListQuery(view.connection, busy, selected); const tasks = tasksQuery.data ?? []; const runsQuery = useTaskRunsQuery( view.connection, tasks.some((task) => task.id === selected) ? selected! : "", busy, ); const history = runsQuery.data ?? emptyTaskHistory; const reading = tasksQuery.isFetching || runsQuery.isFetching; const loading = !tasksQuery.data && !commandError && tasksQuery.isPending; const error = commandError || (tasksQuery.error || runsQuery.error ? "Could not refresh tasks. Saved details may be out of date. Your edits are still here." : ""); const portal = useRef(null); const conversationItems = useMemo( () => conversations.map((conversation) => ({ value: conversation.id, label: conversation.name, })), [conversations], ); useEffect(() => session.start(), [session]); useEffect(() => session.select(selected), [session, selected]); const task = tasks.find((item) => item.id === selected); const locked = busy || !connected; const fieldsLocked = locked || !!editor?.pending; return (
{selected && All scheduled tasks}

{task?.name ?? (selected ? "Scheduled task" : "Scheduled tasks")}

Work Flarebot carries out in your conversations, even when you’re away.

{editor && !view.editorOpen && (

A task creation request is unconfirmed. Resume the draft to retry the same request.

)} {loading && } {error && !deleting &&

{error}

} {notice &&

{notice}

} {!loading && !error && !selected && !tasks.length && ( } title={ connected ? "No scheduled tasks yet" : "Connect to view scheduled tasks" } description={ connected ? "Create a task with instructions, a schedule and a conversation for its results." : "Check your connection and sign-in to see your schedules." } /> )} {!selected && !!tasks.length && (
    {tasks.map((item) => (
  • {item.name}

    {item.enabled ? "Enabled" : "Paused"}

    {taskScheduleLabel(item)}

    Previous run
    {item.previousRun ? `${statusLabels[item.previousRun.status]} · ${taskTime(item.previousRun.createdAt)}` : "No runs yet"}
    Next run
    View task and history
  • ))}
)} {selected && !loading && !reading && !error && !task && ( } title={connected ? "Task not found" : "Connect to view this task"} description="It may have been deleted with its conversation. Your other tasks are available from the task list." /> )} {task && (

Schedule

void session.toggle(task)} />

{taskScheduleLabel(task)}

Previous run
{task.previousRun ? `${statusLabels[task.previousRun.status]} · ${taskTime(task.previousRun.createdAt)}` : "No runs yet"}
Next run

Schedules use UTC. Runs can start later when Flarebot is busy. Run now leaves the schedule unchanged.

Instructions

{task.instructions}

Open result conversation

Run history

{reading && }

Running includes time waiting for this conversation. Results appear in its messages.

{reading && !history.runs.length && ( )} {!reading && !loading && !error && !history.runs.length && (

No runs yet.

)}
    {history.runs.map((run) => (
  1. {statusLabels[run.status]} {run.source === "manual" ? "Manual" : "Scheduled"}

    {taskTime(run.scheduledFor)}

    {run.completedAt && (

    Finished {taskTime(run.completedAt)}

    )} {run.failureCode &&

    {failures[run.failureCode]}

    } View conversation
  2. ))}
{history.nextCursor && ( )}
)}
{ if (!open) session.closeEditor(); }} > {editor?.base ? "Edit task" : "Create task"} Set instructions and an explicit UTC schedule. Results go to the selected conversation. {editor && (
{ event.preventDefault(); void session.save(); }} > session.draft({ name })} maxLength={MAX_TASK_NAME} required disabled={fieldsLocked} /> session.draft({ instructions }) } rows={4} maxLength={MAX_TASK_INSTRUCTIONS} description="Tell Flarebot what to do. Up to 8,000 characters." required disabled={fieldsLocked} /> {editor.base ? (

Results stay in{" "} this conversation .

) : ( <> { if (kind === "once" || kind === "cron") session.draft({ kind }); }} disabled={fieldsLocked} container={portal} /> {editor.draft.kind === "once" ? ( session.draft({ at })} required disabled={fieldsLocked} description="Use UTC, not your device’s local time. New one-off schedules must be in the future." /> ) : ( <> session.draft({ expression }) } maxLength={120} required disabled={fieldsLocked} description="Five numeric fields: minute, hour, day of month, month, weekday (0–6, Sunday–Saturday). Supports *, ranges, lists and steps." />
)} session.draft({ enabled })} /> {editor.error &&

{editor.error}

} {editor.conflict && ( )}
)}
{ if (!open) session.confirmDelete(null); }} > Delete task? Delete “{deleting?.name}” and its run history. Future runs stop and active task work is cancelled. Messages in its conversation are kept. {error &&

{error}

}
); }