source dump of claude code
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 135 lines 7.5 kB view raw
1import { feature } from 'bun:bundle' 2import { getFeatureValue_CACHED_WITH_REFRESH } from '../../services/analytics/growthbook.js' 3import { DEFAULT_CRON_JITTER_CONFIG } from '../../utils/cronTasks.js' 4import { isEnvTruthy } from '../../utils/envUtils.js' 5 6const KAIROS_CRON_REFRESH_MS = 5 * 60 * 1000 7 8export const DEFAULT_MAX_AGE_DAYS = 9 DEFAULT_CRON_JITTER_CONFIG.recurringMaxAgeMs / (24 * 60 * 60 * 1000) 10 11/** 12 * Unified gate for the cron scheduling system. Combines the build-time 13 * `feature('AGENT_TRIGGERS')` flag (dead code elimination) with the runtime 14 * `tengu_kairos_cron` GrowthBook gate on a 5-minute refresh window. 15 * 16 * AGENT_TRIGGERS is independently shippable from KAIROS — the cron module 17 * graph (cronScheduler/cronTasks/cronTasksLock/cron.ts + the three tools + 18 * /loop skill) has zero imports into src/assistant/ and no feature('KAIROS') 19 * calls. The REPL.tsx kairosEnabled read is safe: 20 * kairosEnabled is unconditionally in AppStateStore with default false, so 21 * when KAIROS is off the scheduler just gets assistantMode: false. 22 * 23 * Called from Tool.isEnabled() (lazy, post-init) and inside useEffect / 24 * imperative setup, never at module scope — so the disk cache has had a 25 * chance to populate. 26 * 27 * The default is `true` — /loop is GA (announced in changelog). GrowthBook 28 * is disabled for Bedrock/Vertex/Foundry and when DISABLE_TELEMETRY / 29 * CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC are set; a `false` default would 30 * break /loop for those users (GH #31759). The GB gate now serves purely as 31 * a fleet-wide kill switch — flipping it to `false` stops already-running 32 * schedulers on their next isKilled poll tick, not just new ones. 33 * 34 * `CLAUDE_CODE_DISABLE_CRON` is a local override that wins over GB. 35 */ 36export function isKairosCronEnabled(): boolean { 37 return feature('AGENT_TRIGGERS') 38 ? !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CRON) && 39 getFeatureValue_CACHED_WITH_REFRESH( 40 'tengu_kairos_cron', 41 true, 42 KAIROS_CRON_REFRESH_MS, 43 ) 44 : false 45} 46 47/** 48 * Kill switch for disk-persistent (durable) cron tasks. Narrower than 49 * {@link isKairosCronEnabled} — flipping this off forces `durable: false` at 50 * the call() site, leaving session-only cron (in-memory, GA) untouched. 51 * 52 * Defaults to `true` so Bedrock/Vertex/Foundry and DISABLE_TELEMETRY users get 53 * durable cron. Does NOT consult CLAUDE_CODE_DISABLE_CRON (that kills the whole 54 * scheduler via isKairosCronEnabled). 55 */ 56export function isDurableCronEnabled(): boolean { 57 return getFeatureValue_CACHED_WITH_REFRESH( 58 'tengu_kairos_cron_durable', 59 true, 60 KAIROS_CRON_REFRESH_MS, 61 ) 62} 63 64export const CRON_CREATE_TOOL_NAME = 'CronCreate' 65export const CRON_DELETE_TOOL_NAME = 'CronDelete' 66export const CRON_LIST_TOOL_NAME = 'CronList' 67 68export function buildCronCreateDescription(durableEnabled: boolean): string { 69 return durableEnabled 70 ? 'Schedule a prompt to run at a future time — either recurring on a cron schedule, or once at a specific time. Pass durable: true to persist to .claude/scheduled_tasks.json; otherwise session-only.' 71 : 'Schedule a prompt to run at a future time within this Claude session — either recurring on a cron schedule, or once at a specific time.' 72} 73 74export function buildCronCreatePrompt(durableEnabled: boolean): string { 75 const durabilitySection = durableEnabled 76 ? `## Durability 77 78By default (durable: false) the job lives only in this Claude session — nothing is written to disk, and the job is gone when Claude exits. Pass durable: true to write to .claude/scheduled_tasks.json so the job survives restarts. Only use durable: true when the user explicitly asks for the task to persist ("keep doing this every day", "set this up permanently"). Most "remind me in 5 minutes" / "check back in an hour" requests should stay session-only.` 79 : `## Session-only 80 81Jobs live only in this Claude session — nothing is written to disk, and the job is gone when Claude exits.` 82 83 const durableRuntimeNote = durableEnabled 84 ? 'Durable jobs persist to .claude/scheduled_tasks.json and survive session restarts — on next launch they resume automatically. One-shot durable tasks that were missed while the REPL was closed are surfaced for catch-up. Session-only jobs die with the process. ' 85 : '' 86 87 return `Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders. 88 89Uses standard 5-field cron in the user's local timezone: minute hour day-of-month month day-of-week. "0 9 * * *" means 9am local — no timezone conversion needed. 90 91## One-shot tasks (recurring: false) 92 93For "remind me at X" or "at <time>, do Y" requests — fire once then auto-delete. 94Pin minute/hour/day-of-month/month to specific values: 95 "remind me at 2:30pm today to check the deploy" → cron: "30 14 <today_dom> <today_month> *", recurring: false 96 "tomorrow morning, run the smoke test" → cron: "57 8 <tomorrow_dom> <tomorrow_month> *", recurring: false 97 98## Recurring jobs (recurring: true, the default) 99 100For "every N minutes" / "every hour" / "weekdays at 9am" requests: 101 "*/5 * * * *" (every 5 min), "0 * * * *" (hourly), "0 9 * * 1-5" (weekdays at 9am local) 102 103## Avoid the :00 and :30 minute marks when the task allows it 104 105Every user who asks for "9am" gets \`0 9\`, and every user who asks for "hourly" gets \`0 *\` — which means requests from across the planet land on the API at the same instant. When the user's request is approximate, pick a minute that is NOT 0 or 30: 106 "every morning around 9" → "57 8 * * *" or "3 9 * * *" (not "0 9 * * *") 107 "hourly" → "7 * * * *" (not "0 * * * *") 108 "in an hour or so, remind me to..." → pick whatever minute you land on, don't round 109 110Only use minute 0 or 30 when the user names that exact time and clearly means it ("at 9:00 sharp", "at half past", coordinating with a meeting). When in doubt, nudge a few minutes early or late — the user will not notice, and the fleet will. 111 112${durabilitySection} 113 114## Runtime behavior 115 116Jobs only fire while the REPL is idle (not mid-query). ${durableRuntimeNote}The scheduler adds a small deterministic jitter on top of whatever you pick: recurring tasks fire up to 10% of their period late (max 15 min); one-shot tasks landing on :00 or :30 fire up to 90 s early. Picking an off-minute is still the bigger lever. 117 118Recurring tasks auto-expire after ${DEFAULT_MAX_AGE_DAYS} days — they fire one final time, then are deleted. This bounds session lifetime. Tell the user about the ${DEFAULT_MAX_AGE_DAYS}-day limit when scheduling recurring jobs. 119 120Returns a job ID you can pass to ${CRON_DELETE_TOOL_NAME}.` 121} 122 123export const CRON_DELETE_DESCRIPTION = 'Cancel a scheduled cron job by ID' 124export function buildCronDeletePrompt(durableEnabled: boolean): string { 125 return durableEnabled 126 ? `Cancel a cron job previously scheduled with ${CRON_CREATE_TOOL_NAME}. Removes it from .claude/scheduled_tasks.json (durable jobs) or the in-memory session store (session-only jobs).` 127 : `Cancel a cron job previously scheduled with ${CRON_CREATE_TOOL_NAME}. Removes it from the in-memory session store.` 128} 129 130export const CRON_LIST_DESCRIPTION = 'List scheduled cron jobs' 131export function buildCronListPrompt(durableEnabled: boolean): string { 132 return durableEnabled 133 ? `List all cron jobs scheduled via ${CRON_CREATE_TOOL_NAME}, both durable (.claude/scheduled_tasks.json) and session-only.` 134 : `List all cron jobs scheduled via ${CRON_CREATE_TOOL_NAME} in this session.` 135}