feat: implement /loop command with fixed and dynamic scheduling (#621)

* feat: implement /loop command with fixed and dynamic scheduling modes

Enable cron tools and /loop skill without the AGENT_TRIGGERS build flag
by removing feature guards from tools.ts, REPL.tsx, and skill registration.
The isKairosCronEnabled() runtime gate now enables cron unconditionally for
open builds while preserving the GrowthBook kill switch for ant builds.

The /loop skill supports four modes: fixed-interval with prompt, fixed-interval
maintenance, dynamic-prompt (self-pacing), and dynamic maintenance (bare /loop).

* chore: remove unused DEFAULT_INTERVAL constant from loop skill

* revert: drop infra changes, scope PR to /loop skill rewrite only

The cron activation layer (AGENT_TRIGGERS guard removal, isKairosCronEnabled
hardcode) is covered by an in-flight stack (#633, #639). Scope this PR to
just the loop.ts rewrite and its tests so it can land cleanly on top.

* fix: restore infra changes needed for /loop in open build

Bun's constant folder evaluates feature('AGENT_TRIGGERS') at bundle time
through the bun:bundle shim — even when the flag is flipped to true in
build.ts, the folded value is cached from the previous build and stays false.
This means the feature-gated require() blocks for cron tools, useScheduledTasks,
and loop skill registration all compile to dead code regardless of the flag.

Fix by removing the AGENT_TRIGGERS guards from the specific paths /loop needs:
- tools.ts: cron tools always registered (isEnabled gates visibility)
- REPL.tsx: useScheduledTasks always mounted
- index.ts: registerLoopSkill via static import, called unconditionally
- prompt.ts: isKairosCronEnabled() bypasses feature flag for non-ant builds

* fix: replace backslash line continuations with explicit delimiters in loop prompts

The backslash-newline sequences inside template literals were acting as
line continuations, collapsing newlines and merging prompt content with
surrounding instruction text. Replace with --- BEGIN/END --- markers
for unambiguous delimiting.

Also add tests for trailing "every" clause parsing, human-readable unit
normalization, and the non-interval "check every PR" case.

* fix: remove remaining AGENT_TRIGGERS guards from print.ts and constants/tools.ts

Completes the cron guard removal started in the previous commit.
The cron scheduler in non-interactive (-p) mode was dead because
print.ts still gated cronSchedulerModule/cronGate requires behind
feature('AGENT_TRIGGERS'), which Bun constant-folds to false in open
builds. Similarly, cron tool names were absent from
IN_PROCESS_TEAMMATE_ALLOWED_TOOLS.

Remove all three guards so the scheduler initialises (gated at runtime
by isKairosCronEnabled) and cron tools are allowed for in-process
teammates in all builds.
This commit is contained in:
Khaled Moayad
2026-04-13 12:28:42 +02:00
committed by GitHub
parent 30c866d31a
commit 64298a663f
8 changed files with 356 additions and 128 deletions

View File

@@ -9,39 +9,35 @@ export const DEFAULT_MAX_AGE_DAYS =
DEFAULT_CRON_JITTER_CONFIG.recurringMaxAgeMs / (24 * 60 * 60 * 1000)
/**
* Unified gate for the cron scheduling system. Combines the build-time
* `feature('AGENT_TRIGGERS')` flag (dead code elimination) with the runtime
* `tengu_kairos_cron` GrowthBook gate on a 5-minute refresh window.
* Unified gate for the cron scheduling system.
*
* AGENT_TRIGGERS is independently shippable from KAIROS — the cron module
* graph (cronScheduler/cronTasks/cronTasksLock/cron.ts + the three tools +
* /loop skill) has zero imports into src/assistant/ and no feature('KAIROS')
* calls. The REPL.tsx kairosEnabled read is safe:
* kairosEnabled is unconditionally in AppStateStore with default false, so
* when KAIROS is off the scheduler just gets assistantMode: false.
* Open builds (USER_TYPE !== 'ant') enable cron unconditionally — the
* cron tools and /loop skill are registered without the AGENT_TRIGGERS
* build flag, so this gate is the sole runtime switch. Set the env var
* `CLAUDE_CODE_DISABLE_CRON=1` to turn it off locally.
*
* Anthropic-internal (ant) builds additionally consult the
* `tengu_kairos_cron` GrowthBook gate on a 5-minute refresh window,
* serving as a fleet-wide kill switch.
*
* Called from Tool.isEnabled() (lazy, post-init) and inside useEffect /
* imperative setup, never at module scope — so the disk cache has had a
* chance to populate.
*
* The default is `true` — /loop is GA (announced in changelog). GrowthBook
* is disabled for Bedrock/Vertex/Foundry and when DISABLE_TELEMETRY /
* CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC are set; a `false` default would
* break /loop for those users (GH #31759). The GB gate now serves purely as
* a fleet-wide kill switch — flipping it to `false` stops already-running
* schedulers on their next isKilled poll tick, not just new ones.
*
* `CLAUDE_CODE_DISABLE_CRON` is a local override that wins over GB.
*/
export function isKairosCronEnabled(): boolean {
return feature('AGENT_TRIGGERS')
? !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CRON) &&
getFeatureValue_CACHED_WITH_REFRESH(
'tengu_kairos_cron',
true,
KAIROS_CRON_REFRESH_MS,
)
: false
if (isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CRON)) return false
// OpenClaude open builds do not rely on Anthropic's internal runtime gates.
// Expose cron support by default unless explicitly disabled.
if (process.env.USER_TYPE !== 'ant') return true
return getFeatureValue_CACHED_WITH_REFRESH(
'tengu_kairos_cron',
true,
KAIROS_CRON_REFRESH_MS,
)
}
/**