File size: 3,671 Bytes
7c5ed63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import type { CronTask } from '../../types';
import { formatAbsoluteTime, formatRelativeTime } from '../../util/time';
import { useCron } from '../../hooks/useTasks';
import { CopyButton } from '../shared/CopyButton';
import { Pill } from '../shared/Pill';

interface CronTabProps {
  sessionId: string;
}

/** Cron tab — scheduled prompts persisted under the session's `cron/`
 *  directory. Like background tasks, none of this is in the wire, so it is
 *  the only place to see what a session has scheduled. */
export function CronTab({ sessionId }: CronTabProps) {
  const { data, isLoading, error } = useCron(sessionId);

  if (isLoading) {
    return <div className="p-6 font-mono text-[12px] text-fg-3">loading cron…</div>;
  }
  if (error) {
    return (
      <div className="p-6 font-mono text-[12px] text-[var(--color-sev-error)]">
        {error.message}
      </div>
    );
  }
  const cron = data?.cron ?? [];
  return (
    <div className="min-h-0 flex-1 overflow-y-auto p-4">
      <div className="font-mono text-[11px] uppercase tracking-[0.12em] text-fg-3">
        cron jobs{cron.length > 0 ? ` · ${cron.length}` : ''}
      </div>
      {cron.length === 0 ? (
        <div className="mt-3 border border-border bg-surface-0 px-3 py-6 text-center font-mono text-[12px] text-fg-3">
          no cron jobs were scheduled in this session
        </div>
      ) : (
        <div className="mt-3 flex flex-col gap-2">
          {cron.map((job) => (
            <CronCard key={job.id} job={job} />
          ))}
        </div>
      )}
    </div>
  );
}

function CronCard({ job }: { job: CronTask }) {
  // `recurring` is undefined/true → recurring by convention; false → one-shot.
  const oneShot = job.recurring === false;
  return (
    <div className="border border-border bg-surface-0">
      <div className="flex flex-wrap items-center gap-2 border-b border-border px-3 py-2">
        <Pill tone={oneShot ? 'ephemeral' : 'lifecycle'} variant="outline">
          {oneShot ? 'one-shot' : 'recurring'}
        </Pill>
        <code className="font-mono text-[12px] text-fg-0">{job.cron}</code>
        <span className="font-mono text-[11px] text-fg-3">{job.id}</span>
        <CopyButton value={job.id} />
        <span
          className="ml-auto font-mono text-[11px] text-fg-3 tabular"
          title={formatAbsoluteTime(job.createdAt)}
        >
          created {formatRelativeTime(job.createdAt)}
        </span>
      </div>
      <div className="px-3 py-2">
        <div className="text-[10px] uppercase tracking-[0.1em] text-fg-3">prompt</div>
        <div className="mt-1 whitespace-pre-wrap break-words font-mono text-[12px] text-fg-1">
          {job.prompt}
        </div>
      </div>
      <div className="grid grid-cols-1 gap-x-6 gap-y-1 border-t border-border px-3 py-2 md:grid-cols-2">
        <Field label="lastFiredAt">
          {job.lastFiredAt === undefined ? (
            <span className="text-fg-3">(never fired)</span>
          ) : (
            <span title={formatAbsoluteTime(job.lastFiredAt)}>
              {formatAbsoluteTime(job.lastFiredAt)} ({formatRelativeTime(job.lastFiredAt)})
            </span>
          )}
        </Field>
        <Field label="createdAt">{formatAbsoluteTime(job.createdAt)}</Field>
      </div>
    </div>
  );
}

function Field({ label, children }: { label: string; children: import('react').ReactNode }) {
  return (
    <div className="flex items-baseline gap-2 font-mono text-[12px]">
      <span className="w-28 shrink-0 text-[10px] uppercase tracking-[0.1em] text-fg-3">{label}</span>
      <span className="min-w-0 break-words text-fg-1">{children}</span>
    </div>
  );
}