/ tools / ScheduleCronTool / CronListTool.ts
CronListTool.ts
 1  import { z } from 'zod/v4'
 2  import { buildTool, type ToolDef } from '../../Tool.js'
 3  import { cronToHuman } from '../../utils/cron.js'
 4  import { listAllCronTasks } from '../../utils/cronTasks.js'
 5  import { truncate } from '../../utils/format.js'
 6  import { lazySchema } from '../../utils/lazySchema.js'
 7  import { getTeammateContext } from '../../utils/teammateContext.js'
 8  import {
 9    buildCronListPrompt,
10    CRON_LIST_DESCRIPTION,
11    CRON_LIST_TOOL_NAME,
12    isDurableCronEnabled,
13    isKairosCronEnabled,
14  } from './prompt.js'
15  import { renderListResultMessage, renderListToolUseMessage } from './UI.js'
16  
17  const inputSchema = lazySchema(() => z.strictObject({}))
18  type InputSchema = ReturnType<typeof inputSchema>
19  
20  const outputSchema = lazySchema(() =>
21    z.object({
22      jobs: z.array(
23        z.object({
24          id: z.string(),
25          cron: z.string(),
26          humanSchedule: z.string(),
27          prompt: z.string(),
28          recurring: z.boolean().optional(),
29          durable: z.boolean().optional(),
30        }),
31      ),
32    }),
33  )
34  type OutputSchema = ReturnType<typeof outputSchema>
35  export type ListOutput = z.infer<OutputSchema>
36  
37  export const CronListTool = buildTool({
38    name: CRON_LIST_TOOL_NAME,
39    searchHint: 'list active cron jobs',
40    maxResultSizeChars: 100_000,
41    shouldDefer: true,
42    get inputSchema(): InputSchema {
43      return inputSchema()
44    },
45    get outputSchema(): OutputSchema {
46      return outputSchema()
47    },
48    isEnabled() {
49      return isKairosCronEnabled()
50    },
51    isConcurrencySafe() {
52      return true
53    },
54    isReadOnly() {
55      return true
56    },
57    async description() {
58      return CRON_LIST_DESCRIPTION
59    },
60    async prompt() {
61      return buildCronListPrompt(isDurableCronEnabled())
62    },
63    async call() {
64      const allTasks = await listAllCronTasks()
65      // Teammates only see their own crons; team lead (no ctx) sees all.
66      const ctx = getTeammateContext()
67      const tasks = ctx
68        ? allTasks.filter(t => t.agentId === ctx.agentId)
69        : allTasks
70      const jobs = tasks.map(t => ({
71        id: t.id,
72        cron: t.cron,
73        humanSchedule: cronToHuman(t.cron),
74        prompt: t.prompt,
75        ...(t.recurring ? { recurring: true } : {}),
76        ...(t.durable === false ? { durable: false } : {}),
77      }))
78      return { data: { jobs } }
79    },
80    mapToolResultToToolResultBlockParam(output, toolUseID) {
81      return {
82        tool_use_id: toolUseID,
83        type: 'tool_result',
84        content:
85          output.jobs.length > 0
86            ? output.jobs
87                .map(
88                  j =>
89                    `${j.id} — ${j.humanSchedule}${j.recurring ? ' (recurring)' : ' (one-shot)'}${j.durable === false ? ' [session-only]' : ''}: ${truncate(j.prompt, 80, true)}`,
90                )
91                .join('\n')
92            : 'No scheduled jobs.',
93      }
94    },
95    renderToolUseMessage: renderListToolUseMessage,
96    renderToolResultMessage: renderListResultMessage,
97  } satisfies ToolDef<InputSchema, ListOutput>)