feat(tui): add Crons panel for scheduled cron jobs#119
Merged
yishuiliunian merged 1 commit intomainfrom Apr 17, 2026
Merged
Conversation
Adds a fourth Panel (Crons) to the TUI panel zone alongside Agents/Tasks/BgTasks, surfacing the root agent's CronScheduler state through the existing PanelProvider architecture so users can observe scheduled prompts without invoking CronList. Data flow: CronScheduler → cron_bridge (2s poll with HashSet<identity> diff-skip) → CronsChanged event → SessionState.cron_snapshots → App cache → CronsPanelProvider. The bridge ignores next_fire and lets the TUI recompute the countdown from Utc::now() each frame, avoiding redundant IPC traffic. Architecture improvements shipped together: - CronScheduler switched from Mutex<Vec> to RwLock<Vec>; tick_loop uses two-phase locking (read-only survey → write-lock mutate only when tasks fire/expire) so bridge list() and tool add/remove can proceed in parallel with idle ticks. - CronJobSnapshot carries cron_expr + created_at_unix_ms with #[serde(default)], maintaining forward-compatible IPC while leaving room for future UI expansion. - New views/text_width.rs gives all panels a shared UnicodeWidthStr column-width helper, fixing CJK truncation bugs in tasks_panel and bg_tasks_panel at the same time. - SessionResumed now clears cron/task/bg panel caches so resumed sessions don't display stale data before the bridges re-emit. - agent_handler and agent_setup each split helpers into a sibling module to stay within the 200-line file budget. Tests: 120+ new/updated unit and integration tests covering serde round-trips, diff-skip invariants, bridge error paths, CJK rendering, scheduler concurrency, and the ID-collision retry loop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CronScheduler状态(ID + prompt + 倒计时next Xm Ys+ recurring 标识[R])HashSet<(id, prompt, recurring, cron_expr-hash)>做 diff-skip,TUI 每帧用Utc::now()重算倒计时 —— 消除冗余 IPC 事件CronScheduler从Mutex<Vec>升级为RwLock<Vec>+ 两阶段锁;text_width.rs统一 CJK 宽度计算修复跨 panel 老 bug;agent_handler/agent_setup拆分 helper 以满足 200 行文件限制Changes
Protocol:
CronJobSnapshot(含cron_expr+created_at_unix_ms,#[serde(default)])、CronsChanged事件Session:
cron_state::apply处理CronsChanged+SessionResumed;task_state/bg_task_state同步加 resume 清理;agent_event_helpers用let-else unreachable!()显式契约Agent-server:
cron_bridge(poll + diff-skip + emit);agent_setup_helpers(sub_agent_forwarder / initial_messages / feature_tags);AgentSetupResult.schedulerScheduler:
RwLock<Vec<ScheduledTask>>;tick::survey_tasks + mutate_tasks两阶段;find_unique_id可测化 ID 冲突重试TUI:
PanelKind::Crons+CronsPanelProvider(通过 registry 注册)+views/crons_panel+cron_duration_format+text_width(替换 tasks/bg_tasks 的chars().count())ACP:
CronsChanged标为 non-translatable(与TasksChanged一致)Test plan
bazel test //..., Clippy, Rustfmt)bazel build //:loopal && ./bazel-bin/loopal→ 执行CronCreate→ 2 秒内 panel 显示该 cron,Tab 循环可定位 →CronDelete后 2 秒内消失Coverage
tick_loopsend-err + inner-cancel、find_unique_id冲突重试、Default::default激活.rs文件 ≤ 200 行(符合 CLAUDE.md)