Skip to content

feat(desktop): TODO Agent に cron 風スケジュール実行機能を追加#211

Merged
MocA-Love merged 9 commits intomainfrom
feat/todo-schedule
Apr 16, 2026
Merged

feat(desktop): TODO Agent に cron 風スケジュール実行機能を追加#211
MocA-Love merged 9 commits intomainfrom
feat/todo-schedule

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 16, 2026

概要

既存の TODO Agent にスケジュール機能を追加。毎日デプロイ / 毎時 lint / 毎週レポート生成のような定型タスクを UI ビルダーで登録でき、アプリ起動中に指定時刻が来ると TODO セッションが自動で作られて実行されます。

動作イメージ

  • TodoManager を開く → 左サイドバーのタブを「スケジュール」に切替
  • 「新規」で プロジェクト + 実行対象(プロジェクト本体 (main) or 特定 worktree workspace)+ 頻度 (毎時/毎日/毎週/毎月/cron) + TODO テンプレートを入力
  • 発火時はトースト通知 ( 📅 成功 / ⏭️ skip / ⚠️ 失敗 )

主要な決定事項 (ユーザー確認済み)

  • 頻度は UX 重視のビルダー。cron 直書きは custom モードでのみ可、cronstrue でヒューマン表示
  • プロジェクト主軸。ワークスペースは任意選択、省略時はプロジェクトの main repo path (branch workspace を lazy に作成) で実行
  • 前回未完了時の挙動は スケジュールごとに選択 (skip / queue)
  • 実行前に main ブランチを最新化する opt-in (git fetch → checkout default → pull --ff-only、未コミット変更時はスキップ)
  • セッション自動削除 (日) 設定を Agent Manager 設定に追加 (0=無効、終了済みセッションのみ対象)
  • TodoManager 内に統合 (独立モーダルにしていない)
  • スケジューラ起動失敗時はトースト通知(renderer 購読タイミングより先に emit されても replay される)

アーキテクチャ

  • DB: todo_schedules テーブル新規 (migration 0056_add_todo_schedules.sql)
    • projectId NOT NULL (cascade), workspaceId nullable (set null)
    • autoSyncBeforeFire, overlapMode (skip/queue)
  • Main:
    • TodoScheduler (setInterval 30s、before-quit で stop) が DB の nextRunAt を監視して発火 → 既存 TodoSupervisor に委譲
    • ensureProjectBranchWorkspaceId でプロジェクトの branch workspace を lazy 作成
    • autoSyncProjectMain で opt-in の git fetch/checkout/pull
    • cleanupOldSessions が起動時に COALESCE(completedAt, createdAt) < cutoff で終了済みセッション + artifact を掃除
  • Renderer: SchedulesSection (TodoManager 内), ScheduleFireToasts (layout に常駐)
  • tRPC: todoAgent.schedule.* (list/listAll/create/update/setEnabled/delete/previewNextRun/onFire)

非目的 (v1)

  • アプリを閉じている間の発火補完 → UI に「起動中のみ」を明記
  • タイムゾーン切替 (ローカル TZ 固定)
  • スケジュールのエクスポート/インポート
  • スケジュール発火の OS ネイティブ通知 (sonner トーストのみ)

セルフレビュー + Codex レビュー 対応済み

複数ラウンドで以下を自己 / Codex レビューで検出・対応:

  • overlapMode='queue' が既存 supervisor キューに正しく乗る仕様であることをコメントで明記
  • before-quit で scheduler.stop() + tick 内 isStopped ガードで closeLocalDb 後の race 防止
  • listDuelte(nextRunAt, now) を入れて composite index を実際に利用
  • monthly 31 日指定の Date overflow (Feb 31 → Mar 3) を月末クランプで修正
  • supervisor.start 失敗時に failed event を再 emit (triggered のまま残る UX 欺瞞を修正)
  • cronstrue/i18n (~200KB 全ロケール) → cronstrue + ja ロケール単体に変更
  • ScheduleFireToasts の useSubscription に onError ハンドラ
  • TodoSession 行の構築を insertQueuedFromTemplate に集約 (create / fire の重複排除)
  • ensureProjectBranchWorkspaceId 時に兄弟 workspace の tabOrder を +1 で詰める (既存 workspaces.create 経路と同じ挙動)
  • "__main__" sentinel を MAIN_REPO_SENTINEL 定数化
  • projectId を schedule update から omit + UI 編集時は Select を disabled (Codex P1)
  • scheduler.tick() 内で Date.now() を事前計算せず、各 fire 直前に取り直し (Codex P2)
  • cleanupOldSessionsCOALESCE(completedAt, createdAt) ベースに修正 (Codex P1)

実装ファイル

新規:

  • packages/local-db/src/schema/todo-schedules.ts + migration
  • apps/desktop/src/main/todo-agent/{schedule-store,scheduler,schedule-sync,sessions-cleanup}.ts
  • apps/desktop/src/renderer/features/todo-agent/{TodoManager/SchedulesSection,ScheduleFireToasts}/...
  • apps/desktop/plans/20260416-todo-schedule.md

既存への小さい追記:

  • TodoManager.tsx: サイドバーにタブ追加 (既存タスク一覧はそのまま保持)
  • layout.tsx: ScheduleFireToasts を 1 行追加
  • main/index.ts: 起動時に scheduler.start() + 失敗時の replay 通知 + cleanupOldSessions() 呼出
  • session-store.ts: insertQueuedFromTemplateensureProjectBranchWorkspaceId を追加
  • trpc-router.ts: schedule サブルーター
  • PresetsDialog.tsx: Settings タブに「セッション自動削除 (日)」入力

新規依存: cron-parser (main), cronstrue (renderer)

Test plan

  • bun run typecheck / bun run lint がグリーン
  • 毎時スケジュール作成 → 次回時刻に発火してトースト表示
  • 毎日スケジュール作成 → 指定時刻に TODO が自動生成
  • custom cron (0 9 * * 1-5) → cronstrue で「平日の 9:00」と表示
  • monthly=31 → 2月は月末に発火 (オーバーフローしない)
  • overlapMode=skip で前回未完了なら新規セッション作らずにトースト skip
  • overlapMode=queue で前回未完了でも新規セッションが queued 状態で積まれる
  • workspaceId=null で発火 → branch workspace が lazy に作成され、mainRepoPath で実行
  • autoSyncBeforeFire=true で fetch/checkout/pull が走る
  • 未コミット変更がある状態で autoSyncBeforeFire → skip トースト
  • sessionRetentionDays=30 で 31日前の done セッションが起動時に消える
  • sessionRetentionDays=0 で何も消えない
  • 数週間前作成→今日完了のセッションが誤って消えない (completedAt ベース確認)
  • 編集時にプロジェクトを変更できない
  • 有効トグル OFF で nextRunAt がクリアされ発火しない
  • アプリ再起動後もスケジュールが保持され nextRunAt が再計算される
  • scheduler 起動失敗時にトーストが表示される

TodoManager のサイドバーに「スケジュール」タブを追加し、
毎時 / 毎日 / 毎週 / 毎月 / cron 式 の UI ビルダーで作った定型 TODO を
アプリ起動中に自動発火できるようにする。発火成功・スキップ・失敗は
トーストで通知し、TodoManager が閉じていても届く。

- DB: todo_schedules テーブル + drizzle migration 0056
- main: scheduler (30秒間隔 tick) + schedule-store + trpc router
- renderer: SchedulesSection (編集ダイアログ / 頻度ビルダー / 一覧)
- 重複時は skip / queue をスケジュールごとに選択可能
- アプリ閉じている間の発火は v1 では対象外 (plan.md 参照)
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

TODO エージェント向けのスケジュール実行機能を実装しました。定期的なタスク実行(時間ごと、日ごと、週ごと、月ごと、cron式)をスケジュール登録し、指定時刻に自動的にセッションを作成・実行する仕組みを追加しました。30秒ごとのポーリング、cron パース、スケジュール永続化、イベント通知を含みます。

Changes

Cohort / File(s) Summary
ドキュメント・設定
README.md, apps/desktop/package.json, apps/desktop/plans/20260416-todo-schedule.md
スケジュール機能の changelog エントリ追加、依存パッケージ(cron-parser, cronstrue)の追加、実装計画ドキュメント作成。
メインプロセス起動・終了
apps/desktop/src/main/index.ts
アプリ起動時にセッションのクリーンアップとスケジューラーの開始、シャットダウン時にスケジューラーの停止を追加。
TODO エージェント モジュール出力
apps/desktop/src/main/todo-agent/index.ts
getTodoScheduleStore, getTodoScheduler, cleanupOldSessions の新規エクスポート。
スケジュール永続化・イベント
apps/desktop/src/main/todo-agent/schedule-store.ts
スケジュールの CRUD と fireEvent イベントバスを管理する TodoScheduleStore、遅延購読者向けの初期化失敗キャッシュ。
スケジューラー実行エンジン
apps/desktop/src/main/todo-agent/scheduler.ts
30秒ごとのポーリング、computeNextRunAt による次回実行時刻計算(cron パース対応)、重複実行ガード、オプショナルな自動同期、セッション作成・イベント発火。
リポジトリ同期ユーティリティ
apps/desktop/src/main/todo-agent/schedule-sync.ts
スケジュール実行前にメインブランチを fetch・checkout・pull する autoSyncProjectMain
セッション管理拡張
apps/desktop/src/main/todo-agent/session-store.ts
テンプレートからキューイング状態でセッションを作成する insertQueuedFromTemplate、プロジェクト用ブランチワークスペースの自動確保 ensureProjectBranchWorkspaceId
セッション自動削除
apps/desktop/src/main/todo-agent/sessions-cleanup.ts
起動時に期限切れセッション(完了日が保持日数超過)を削除する cleanupOldSessions
設定・スキーマ・API
apps/desktop/src/main/todo-agent/settings.ts, apps/desktop/src/main/todo-agent/types.ts, apps/desktop/src/main/todo-agent/trpc-router.ts
sessionRetentionDays 設定追加、スケジュール関連 Zod スキーマ・fire イベント型定義、todoAgent.schedule.* tRPC ルーター (list/create/update/delete/onFire)。
フロントエンド スケジュール UI
apps/desktop/src/renderer/features/todo-agent/TodoManager/SchedulesSection/*, apps/desktop/src/renderer/features/todo-agent/TodoManager/PresetsDialog/PresetsDialog.tsx, apps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsx
スケジュール一覧・エディター・行コンポーネント、周波数ピッカー、セッション保持日数設定、サイドバータブ切り替え。
フロントエンド ユーティリティ・統合
apps/desktop/src/renderer/features/todo-agent/TodoManager/SchedulesSection/utils/*, apps/desktop/src/renderer/features/todo-agent/ScheduleFireToasts/ScheduleFireToasts.tsx, apps/desktop/src/renderer/routes/_authenticated/layout.tsx
cron・次回実行時刻の日本語表記、fire イベントへの toast 通知、レイアウト統合。
データベース スキーマ
packages/local-db/drizzle/0056_add_todo_schedules.sql, packages/local-db/drizzle/meta/0056_snapshot.json, packages/local-db/drizzle/meta/_journal.json, packages/local-db/src/schema/todo-schedules.ts, packages/local-db/src/schema/index.ts, packages/local-db/src/schema/schema.ts
todo_schedules テーブル定義(周波数、cron 式、実行制御、実行履歴追跡)、Drizzle スキーマ・マイグレーション。

Sequence Diagram(s)

sequenceDiagram
    participant Main as Main Process
    participant Scheduler as TodoScheduler
    participant Store as TodoScheduleStore
    participant Sync as ScheduleSync
    participant Supervisor as TodoSupervisor
    participant Renderer as Renderer (Toast)

    Main->>Scheduler: getTodoScheduler().start()
    Scheduler->>Scheduler: setInterval (30s tick)
    
    loop Every 30 seconds
        Scheduler->>Store: listDue(now)
        Store-->>Scheduler: due schedules[]
        
        alt For each due schedule
            Scheduler->>Scheduler: computeNextRunAt()
            Scheduler->>Store: check lastRunSessionId (skip guard)
            
            alt overlapMode === "skip" && session active
                Scheduler->>Store: emitFire(skipped event)
            else overlapMode === "queue" or no active session
                opt autoSyncBeforeFire enabled
                    Scheduler->>Sync: autoSyncProjectMain(cwd)
                    Sync-->>Scheduler: ok | dirty | error
                end
                
                Scheduler->>Supervisor: createFromSchedule(template)
                Supervisor->>Supervisor: start session async
                Scheduler->>Store: recordRun(sessionId)
                Scheduler->>Store: emitFire(triggered event)
                Store->>Renderer: onFire subscription
                Renderer->>Renderer: show success toast
            end
        end
    end
    
    Main->>Scheduler: before-quit → stop()
    Scheduler-->>Main: cleanup complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs


🐰 スケジュールの兎(ウサギ)

ポーリング tick、30 秒ごとに
Cron を解きて、次を計る
予約の時刻、来たるとき
セッション生まれ、火を灯す
定期の業務、自動化の道 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed プルリクエストのタイトルは、TODO Agent に cron 風スケジュール実行機能を追加する主要な変更を明確に要約しており、簡潔で理解しやすい。
Description check ✅ Passed PR説明が必須セクション(説明、関連issue、変更種別、テスト、追加ノート)の大部分を満たしており、実装内容が詳細に記載されている。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/todo-schedule

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

自己レビューで検出した 5 件の修正必須 + 2 件の修正推奨。

- listDue: composite index を実際に使う (enabled + lte(nextRunAt, now))
- stop() を before-quit で呼び、tick 内に isStopped ガードを追加。
  closeLocalDb 後にセッション挿入する race を防ぐ
- monthly の monthday=31 を月末クランプ (Feb 31 → Feb 28 等)。
  以前は JS Date overflow で翌月にずれていた
- supervisor.start 失敗時に failed event を再 emit。triggered のまま
  残って UX 欺瞞になる問題を修正
- overlapMode='queue' は既存 TodoSupervisor の内部キューがそのまま
  機能することをコード内コメントで明示
- cronstrue を `cronstrue/i18n` から `cronstrue` + ja ロケール単体に
  変更し、renderer バンドルから他ロケールを除外
- ScheduleFireToasts の useSubscription に onError を追加
scheduler.fire() と trpc-router.create() の両方で TodoSession の約30
フィールドをコピペしていたのを、session-store の
insertQueuedFromTemplate() に集約した。todo_sessions に列を足すときに
両方を書き換える必要が無くなる。
ユーザー要望により、スケジュールの一次キーを workspaceId から projectId
に移行。ワークスペースは任意選択、省略時はプロジェクト本体 (main repo path)
で実行する。

- todo_schedules: projectId を NOT NULL、workspaceId を NULL 許容に
- UI: ScheduleEditorDialog に「プロジェクト」 dropdown を追加し
  「実行対象」 dropdown に「プロジェクト本体 (main)」オプションを用意
- scheduler.fire(): workspaceId が null のときは
  ensureProjectBranchWorkspaceId() でプロジェクトの branch workspace
  を用意 (なければ lazy に作成) してセッションを attach
- trpc schedule.list のキーを workspaceId → projectId に変更
自己レビューで指摘された副作用・整合性・読みやすさの改善。

- ensureProjectBranchWorkspaceId で branch workspace を materialize
  した後、既存 workspaces/procedures/create.ts と同じく兄弟の tabOrder
  を +1 で詰める。これまで tabOrder=0 の既存 worktree workspace と
  衝突して サイドバー並び順が非決定的になっていた
- ScheduleEditorDialog の "__main__" マジック文字列を
  MAIN_REPO_SENTINEL 定数に抽出
- schedule-store から未使用の listForWorkspace を削除
  (現在は listForProject のみ使用)
- schedule update input から projectId を omit。プロジェクト移動を
  ブロックして lastRunSessionId と projectId の不整合を予防
@MocA-Love
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac3946b7bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/todo-agent/scheduler.ts
3 つの追加改善。

1. スケジュールに 「実行前に main ブランチを最新化する」 opt-in を追加
   - todo_schedules.autoSyncBeforeFire 列を追加 (新 migration)
   - workspaceId が null (プロジェクト本体実行) のときのみ UI に表示
   - 発火時に git fetch → checkout default → pull --ff-only
   - 未コミット変更があるときはスキップ + 通知 (stash はしない)

2. Agent Manager 設定に セッション自動削除 (日) を追加
   - todo-agent-settings.json に sessionRetentionDays (0-365, 0=無効) を追加
   - 起動時に cleanupOldSessions() が終了済み (done/failed/aborted/escalated) かつ
     N 日より古い todo_sessions 行 + artifact ディレクトリを削除
   - 実行中・キュー中のセッションは絶対に対象外

3. スケジューラ起動失敗時のトースト通知 (提案 B 案)
   - main/index.ts の起動 try/catch で失敗したら
     schedule-store.emitFire({kind:"failed", scheduleId:"__scheduler_init__"}) を発行
   - schedule-store に pendingInitFailure バッファを追加し、
     renderer 購読が間に合わなくてもマウント直後に replay
- ScheduleEditorDialog 編集時にプロジェクト select を disabled。
  ユーザーがプロジェクト変更を保存しても裏でサイレント無視される
  P1 issue を塞ぐ。update 送信時に projectId も payload から omit
- scheduler.tick() が同じ firedAt を複数 fire に渡していたのを、
  各 fire 直前に Date.now() を取り直すよう修正。遅い fire の後に
  続く fire で nextRunAt が過去時刻にセットされ次 tick で重複発火
  する P2 issue を塞ぐ
@MocA-Love
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0e76054dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/todo-agent/sessions-cleanup.ts Outdated
…ベースに

cleanupOldSessions が createdAt < cutoff で判定していたが、
ドキュメントでは「完了時刻 (completedAt) を優先、null のみ createdAt に
フォールバック」と約束していた。数週間前に作成されたが今日完了した
ばかりの長命セッションが起動時に消えてしまう P1 バグを修正。

Drizzle の sql テンプレートで COALESCE(completed_at, created_at) < cutoff
とし、完了時刻ベースで判定するようにした。
@MocA-Love MocA-Love merged commit d07bda9 into main Apr 16, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant