feat(desktop): TODO Agent に Crush CLI 対応を追加#386
Conversation
TODO Agent の自律実行エンジンを拡張し、Charmbracelet Crush CLI (`crush run`) を バックエンドとして選択可能にした。Claude Code / Codex CLI に続く3つ目のエージェント対応。 Crush は stdout に構造化ストリーミングを出力しないため、プロジェクトローカルの SQLite データベース (`<project>/.crush/crush.db`) をポーリングしてメッセージを 取得し、既存の TodoStreamEvent 形式に変換する方式を採用。 Claude PTY runner の JSONL ポーリングと同じパターン。 モデル選択は `crush models` の出力(276+)を動的フェッチし、 provider ごとにグループ化して UI に表示。ハードコードなし。 ## 変更点 ### コアロジック - `types.ts` — `AgentKind` enum に `"crush"` を追加。`crushModel` フィールドを `todoCreateInputSchema` / `todoSettingsSchema` に追加 - `crush-turn-runner.ts` (新規) — `crush run --yolo` を spawn → `.crush/crush.db` を 250ms 間隔でポーリング → messages テーブルの parts JSON をパースして TodoStreamEvent に変換。system_init / assistant_text / tool_use / tool_result / result / error の全 kind をマッピング - `supervisor-engine.ts` — `runAgentTurn()` に `agentKind === "crush"` ルートを追加 ### データ層 - DB schema — `todo_sessions` / `todo_schedules` に `crush_model` カラムを追加 - マイグレーション — `0069_add_crush_model.sql` 自動生成 - session-store / tRPC router — 作成・再実行時に crushModel を永続化 - tRPC — `crushModels` クエリ追加(`crush models` の出力をキャッシュ付きで返す) ### UI - `AgentRuntimePicker.tsx` — Agent セレクタに "Crush" を追加。Crush 選択時は モデルのみのセクションを表示(Effort は Crush CLI に概念がないため省略)。 モデルリストは provider ごとにグループ化 - `claudeRuntimeOptions.ts` — `CrushModelPick` 型 + 永続化ヘルパー - `TodoModal.tsx` — `crushModel` state + `crushModels` フェッチ + 送信 ## 設計上の決定 - **後方互換性**: `agent_kind` のデフォルトは引き続き `"claude"`。 既存セッションへの影響なし - **SQLite ポーリング**: Crush は NDJSON 出力をサポートしないため、DB ポーリング を選択。Claude PTY runner の JSONL ポーリングと同じアプローチ - **動的モデルリスト**: 276+ のモデルをハードコードせず `crush models` から動的取得 - **Effort なし**: Crush CLI に effort/reasoning の概念がないため省略 💘 Generated with Crush Assisted-by: GLM-5 via Crush <crush@charm.land>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 18 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughこのプルリクエストは、新しい「Crush」CLIエージェント型を導入し、セッション・スケジュール・設定に Changes
Sequence Diagram(s)sequenceDiagram
participant User as TodoModal
participant Supervisor as SupervisorEngine
participant CrushRunner as runCrushTurn
participant CrushBinary as crush binary
participant SQLite as SQLite DB<br/>(.crush/crush.db)
User->>Supervisor: runAgentTurn(crushModel, ...)
Supervisor->>CrushRunner: runCrushTurn(sessionId, prompt, crushModel, ...)
CrushRunner->>CrushBinary: spawn crush run --yolo<br/>(with model, session resume)
CrushBinary-->>CrushRunner: emit stderr (session_id)
CrushRunner->>CrushRunner: capture sessionId
par Database Polling
CrushRunner->>SQLite: poll messages table<br/>(where session_id, created_at)
SQLite-->>CrushRunner: new message rows
CrushRunner->>CrushRunner: parse parts JSON<br/>classifyPart()
CrushRunner-->>User: emit CrushStreamEvent<br/>(text, tool_call, etc.)
end
CrushBinary->>CrushBinary: process iteration
CrushBinary-->>CrushRunner: exit
CrushRunner->>SQLite: final DB sweep<br/>(fetch cost from sessions)
SQLite-->>CrushRunner: cost data
CrushRunner-->>Supervisor: return CrushTurnResult<br/>(result, sessionId, cost, etc.)
Supervisor-->>User: append stream events
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 572562b76e
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/desktop/src/main/todo-daemon/supervisor-engine.ts (1)
278-279:⚠️ Potential issue | 🟡 Minor
agentKindの型注釈が"crush"を含んでいません。行 450-451 の
runAgentTurn呼び出し側では"claude" | "codex" | "crush" | nullにキャストしているのに対し、ここでは"claude" | "codex" | nullのままです。DB 上に"crush"が保存された場合、この変数は型としては正しくないキャストになります(ランタイムは動きますが、以降agentKind === "codex"等の分岐で"crush"が常に else に落ちることを型システムで追跡できません)。両者を揃えることをお勧めします。🔧 差分案
- const agentKind = - (session0.agentKind as "claude" | "codex" | null) ?? "claude"; + const agentKind = + (session0.agentKind as "claude" | "codex" | "crush" | null) ?? "claude";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/todo-daemon/supervisor-engine.ts` around lines 278 - 279, The local variable agentKind is annotated as (session0.agentKind as "claude" | "codex" | null) but elsewhere (see runAgentTurn call around lines 450-451) the code expects "claude" | "codex" | "crush" | null; update the annotation here to include "crush" so the union matches the caller: change the cast on session0.agentKind to "claude" | "codex" | "crush" | null and keep the same default value logic (e.g., ?? "claude") so the type system correctly tracks the "crush" branch.apps/desktop/src/main/todo-agent/types.ts (1)
379-424:⚠️ Potential issue | 🟡 Minorスケジュール入力スキーマが
crushModel(およびcodexModel/agentKind) を受け付けません。
todoScheduleCreateInputSchema/todoScheduleBaseSchemaはclaudeModel/claudeEffortのみを受け付けており、agentKindやcodexModel/crushModelが存在しません。PR の説明ではtodo_schedulesにもcrush_modelカラムが追加されるとのことなので、DB カラムを活かすためにはスケジュール経由で作成されるセッションで Crush を選択できるよう、将来的にこのスキーマにもagentKind/codexModel/crushModelを追加する想定と思われます。本 PR のスコープ外であれば問題ありませんが、Crush カラムが書き込まれるパスが現状「セッション作成経由のみ」で、スケジュール経由では常にnull固定になる点は留意してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/todo-agent/types.ts` around lines 379 - 424, The schedule input schemas (todoScheduleCreateInputSchema and todoScheduleBaseSchema) currently only accept claudeModel/claudeEffort and must be extended to accept agentKind, codexModel and crushModel so schedules can create sessions with Crush; add fields agentKind (use the same enum/schema used elsewhere for session agent kind), codexModel and crushModel to both todoScheduleBaseSchema and todoScheduleCreateInputSchema (use the same types/nullish/defaults as used for session inputs), and update any .refine or validation logic to account for these new fields (e.g., ensure model presence when agentKind equals the corresponding value) so schedule-created sessions can persist crush_model in the DB.
🧹 Nitpick comments (6)
packages/local-db/src/schema/todo-sessions.ts (1)
100-114:agentKindのコメントにもcrushを反映してください。Line 100 の説明が
"claude"/"codex"のままなので、新しいcrushModelコメントと食い違っています。修正例
- // Which agent CLI to use: "claude" (Claude Code) or "codex" (Codex CLI). + // Which agent CLI to use: "claude" (Claude Code), "codex" (Codex CLI), + // or "crush" (Crush CLI).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/local-db/src/schema/todo-sessions.ts` around lines 100 - 114, Update the comment for the agentKind field to include the new "crush" option so it no longer contradicts crushModel; specifically edit the comment above agentKind (symbol: agentKind) to list the valid values "claude", "codex", and "crush", keep the default as "claude" for backward compatibility, and mention that this determines which turn runner the supervisor engine dispatches to (so it aligns with the crushModel comment and the crushModel symbol).apps/desktop/src/main/todo-agent/trpc-router.ts (1)
551-551: 不要なanycast を外してください。
SelectTodoSessionにcrushModelが追加されているため、ここは型安全に参照できます。修正例
- crushModel: (source as any).crushModel ?? null, + crushModel: source.crushModel ?? null,As per coding guidelines,
**/*.{ts,tsx}: Avoidanytype unless necessary.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/todo-agent/trpc-router.ts` at line 551, Remove the unnecessary any cast and access crushModel in a type-safe way: replace (source as any).crushModel ?? null with a typed reference (e.g. (source as SelectTodoSession).crushModel ?? null) or, better, ensure the surrounding code/parameter is typed as SelectTodoSession so you can simply use source.crushModel ?? null; target the crushModel property on SelectTodoSession to remove the any cast.packages/local-db/src/schema/todo-schedules.ts (1)
65-75: schedule 側のagentKindコメントも更新してください。Line 65 が
"claude"/"codex"のみを列挙しており、追加されたcrushModelと整合していません。修正例
- // Which agent CLI to use: "claude" or "codex". Defaults to "claude". + // Which agent CLI to use: "claude", "codex", or "crush". + // Defaults to "claude".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/local-db/src/schema/todo-schedules.ts` around lines 65 - 75, Update the comment for the agentKind column in packages/local-db/src/schema/todo-schedules.ts to reflect the newly supported agent types: include "crush" alongside "claude" and "codex" so it matches the added crushModel field; locate the agentKind declaration (agentKind: text("agent_kind").notNull().default("claude")) and expand the inline comment to enumerate "claude", "codex", and "crush" and optionally note which model overrides (codexModel, codexEffort, crushModel) apply to each agentKind.apps/desktop/src/main/todo-daemon/supervisor-engine.ts (1)
265-308: Crush 用のセットアップバナーがないため、選択モデルが UI 上で可視化されません。Claude / Codex はそれぞれ
session0.claudeModel/session0.codexModelを読んでappendSetupEvent("Claude 設定", ...)/appendSetupEvent("Codex 設定", ...)を出力していますが、Crush には同等の表示がありません。agentKind === "crush"の場合に「Crush」エンジンを起動する旨のバナーと、選択モデルの表示を追加すると、既存エージェントとの一貫性が保たれユーザーが設定ミスを発見しやすくなります。♻️ 差分案(概略)
if (session0.codexModel || session0.codexEffort) { const parts: string[] = []; if (session0.codexModel) parts.push(`model: ${session0.codexModel}`); if (session0.codexEffort) parts.push(`effort: ${session0.codexEffort}`); appendSetupEvent(sessionId, "Codex 設定", parts.join(" / ")); } + const crushModel = (session0 as SelectTodoSession & { + crushModel?: string | null; + }).crushModel; + if (crushModel) { + appendSetupEvent(sessionId, "Crush 設定", `model: ${crushModel}`); + } @@ - if (agentKind === "codex") { + if (agentKind === "codex") { appendSetupEvent( sessionId, "Codex", "codex exec --json --full-auto を起動します", ); ... + } else if (agentKind === "crush") { + appendSetupEvent( + sessionId, + "Crush", + "crush run --yolo を起動します", + ); } else {加えて
const agentKindの型注釈(行 279)も"claude" | "codex" | "crush" | nullに拡張する必要があります。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/todo-daemon/supervisor-engine.ts` around lines 265 - 308, The code never emits a setup banner for the "crush" agent so selected Crush model/config isn't shown in the UI; update the agentKind type annotation on the const agentKind (currently "claude" | "codex" | null) to include "crush", then add a branch alongside the existing agentKind === "codex" / else handling that calls appendSetupEvent(sessionId, "Crush", "<Crush startup message>") and also emits a Crush 設定 setup event when session0.crushModel or session0.crushEffort are present (mirroring the pattern used for session0.claudeModel / session0.codexModel and appendSetupEvent("Claude 設定"/"Codex 設定")). Ensure the willUsePty / remoteControlEnabled logic remains unchanged and reuse the same parts.join(" / ") formatting for Crush setup events.apps/desktop/src/renderer/features/todo-agent/ClaudeRuntimePicker/AgentRuntimePicker.tsx (1)
260-280:SelectGroup/SelectLabelでプロバイダーごとにグルーピングし視覚的な区切りを追加する
groupByProviderで分類したのに、UI 上はフラットなSelectItemの連続になっており、プロバイダーごとの区切りが見出しとして表示されていません。200+ モデルを扱う場合、SelectGroupとSelectLabelを使うと探しやすくなります。♻️ 差分案
-import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@superset/ui/select"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@superset/ui/select"; @@ - <SelectContent className="max-h-64"> - <SelectItem value={DEFAULT_SENTINEL}> - <span className="text-xs font-medium">デフォルト</span> - </SelectItem> - {Object.entries(grouped).map(([provider, models]) => - models.map((model) => ( - <SelectItem key={model} value={model}> - <span className="text-xs font-medium"> - {model.replace(`${provider}/`, "")} - </span> - <span className="text-[10px] text-muted-foreground ml-1"> - {provider} - </span> - </SelectItem> - )), - )} - </SelectContent> + <SelectContent className="max-h-64"> + <SelectItem value={DEFAULT_SENTINEL}> + <span className="text-xs font-medium">デフォルト</span> + </SelectItem> + {Object.entries(grouped).map(([provider, models]) => ( + <SelectGroup key={provider}> + <SelectLabel className="text-[10px] uppercase tracking-wide text-muted-foreground"> + {provider} + </SelectLabel> + {models.map((model) => ( + <SelectItem key={model} value={model}> + <span className="text-xs font-medium"> + {model.slice(provider.length + 1) || model} + </span> + </SelectItem> + ))} + </SelectGroup> + ))} + </SelectContent>合わせて
model.replaceからsliceに切り替えると、意図の明確化につながります。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/features/todo-agent/ClaudeRuntimePicker/AgentRuntimePicker.tsx` around lines 260 - 280, The UI currently renders a flat list of SelectItem from grouped but should use SelectGroup and SelectLabel to visually separate providers; update the render inside SelectContent to iterate Object.entries(grouped) and for each provider render a SelectGroup with a SelectLabel showing the provider and map its models to SelectItem; also change model.replace(`${provider}/`, "") to use model.slice(provider.length + 1) (or equivalent) to make the trimming intent clearer; keep DEFAULT_SENTINEL handling as a top-level SelectItem.apps/desktop/src/main/todo-daemon/crush-turn-runner.ts (1)
99-118: stderr からsession_idを抽出するパース方法は脆弱です。Crush の公式ドキュメントでは、非対話型実行時に stderr へ
session_idを出力することが明記されていません。このため、ログフォーマットの更新があると正規表現が一致しなくなり、crushSessionIdが永遠にnullのままとなります。その場合、pollDbForEventsはgetSessionId()が null でcontinueし続け、DB へのクエリが一度も発行されずイベントが UI に届きません。フォールバックとして、起動後一定時間
session_idが取れなければ DB 側でsessionsテーブルをcreated_atの大きい順に最新 1 件 look-up する、または少なくとも一定時間経過後に警告イベントを emit してユーザーに気付かせる仕組みがあると、障害対応しやすくなります。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/todo-daemon/crush-turn-runner.ts` around lines 99 - 118, stderr からの正規表現抽出だけに頼る現在の crushSessionId ロジックは壊れやすいので、一定時間(例: 起動後 5–10 秒)以内に crushSessionId が取得できなければフォールバックを行うように修正してください: pollDbForEvents / getSessionId の呼び出し経路にタイムアウト判定を追加して(crushSessionId が null のまま一定時間経過したら)① DB の sessions テーブルを created_at DESC で最新 1 件を look-up して session_id を取得して使う処理を実装するか、② DB lookup ができない/失敗した場合は params.emit を使って system_warn(例: "Crush セッションID未取得のため DB を参照しました" や "セッションID未取得")を送出するようにし、crushSessionId 変数と pollDbForEvents のループが永久に続かないようにしてください。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/todo-agent/trpc-router.ts`:
- Around line 1039-1057: The crushModels query currently spawns `crush models`
on every request; add a module-level cache (use an interface like
CrushModelsCache with fields values, expiresAt, inflight and constants like
CRUSH_MODELS_CACHE_TTL_MS and a crushModelsCache instance) and modify the
crushModels publicProcedure.query to: return cached values if not expired, if
expired but crushModelsCache.inflight exists await it, otherwise set
crushModelsCache.inflight to a Promise that runs the execFileAsync call, updates
crushModelsCache.values and expiresAt on success, clears inflight and returns
the values; on error clear inflight and return an empty array (or existing
values) so parallel requests are deduped and results are TTL-cached.
In `@apps/desktop/src/main/todo-daemon/crush-turn-runner.ts`:
- Around line 276-289: The block that checks for p.type === "finish" with reason
"end_turn" or "stop" is a no-op and should either be removed or mark the session
as finished; update it to set the existing settled flag and exit the poll so
polling stops. Locate the code in pollDbForEvents (crush-turn-runner.ts) where
safeParseJson(lastRow.parts) is iterated, and inside the branch for p.type ===
"finish" && (p.data?.reason === "end_turn" || p.data?.reason === "stop") assign
settled = true and then break/return so the polling loop exits (matching the
earlier critical fix that uses settled). Ensure you use the same settled
identifier used elsewhere in this file so state remains consistent.
- Around line 405-415: In killProcess, avoid calling taskkill with "undefined"
by checking child.pid before converting it to a string and before spawning; if
child.pid is undefined, return early (or fall back to child.kill) instead of
calling spawn("taskkill", [String(child.pid), ...]); similarly ensure the
non-Windows branch only calls process.kill when child.pid is present; keep the
try/catch so the existing child.kill("SIGKILL") fallback remains on errors.
- Around line 256-294: The code assumes safeParseJson(...) returns an array but
may return non-array values, causing silent TypeErrors and endless retries;
update the logic around safeParseJson(row.parts) (used in the parts loop where
classifyPart(...) is called) and safeParseJson(lastRow.parts) (used where
lastParts is iterated) to explicitly check Array.isArray(parts) /
Array.isArray(lastParts) before using for...of, and when the value is not an
array skip processing (and optionally emit a warning via params.emit or a
logger) so non-array JSON will not throw inside the polling try/catch.
- Around line 127-155: The poll loop in pollDbForEvents never stops because the
local `const settled = false` is never updated; modify pollDbForEvents to accept
an external "done" flag or callback (e.g., a boolean reference or onDone
function) and break the loop when that notifier is set, and at the call site
where you create `pollPromise = pollDbForEvents(params, () => crushSessionId)`
update the call to pass a notifier you can flip from the child handlers (use the
existing `child` variable and its "close" handler to set interrupted/childExited
and call the notifier or set the flag); ensure the "error" and "abort" paths
also trigger the same notifier so pollDbForEvents always exits after the child
closes.
- Around line 161-175: The SELECT result from SQLite may contain NULL for the
cost field; update the block that reads from Database (use symbols
crushSessionId, findCrushDb, Database, sessions, costUsd, and row.cost) to
defensively validate the value before assigning: after fetching row (which may
be undefined), check that row.cost is neither null nor undefined and that typeof
row.cost === "number" (or coerce/parse safely) and only then set costUsd =
row.cost; otherwise leave costUsd unchanged (or handle fallback). Ensure this
check sits inside the existing try block where the Database is opened and the
row is retrieved.
In `@apps/desktop/src/main/todo-daemon/supervisor-engine.ts`:
- Line 456: Remove the unnecessary "as any" cast and access the crushModel
property directly: replace "(currentSession as any).crushModel ?? null" with
"currentSession.crushModel ?? null" and ensure the variable currentSession is
typed (or inferred) as SelectTodoSession so TypeScript knows crushModel exists
(or add an explicit type annotation of SelectTodoSession to currentSession). If
currentSession can be undefined, guard or narrow its type before accessing
crushModel.
---
Outside diff comments:
In `@apps/desktop/src/main/todo-agent/types.ts`:
- Around line 379-424: The schedule input schemas (todoScheduleCreateInputSchema
and todoScheduleBaseSchema) currently only accept claudeModel/claudeEffort and
must be extended to accept agentKind, codexModel and crushModel so schedules can
create sessions with Crush; add fields agentKind (use the same enum/schema used
elsewhere for session agent kind), codexModel and crushModel to both
todoScheduleBaseSchema and todoScheduleCreateInputSchema (use the same
types/nullish/defaults as used for session inputs), and update any .refine or
validation logic to account for these new fields (e.g., ensure model presence
when agentKind equals the corresponding value) so schedule-created sessions can
persist crush_model in the DB.
In `@apps/desktop/src/main/todo-daemon/supervisor-engine.ts`:
- Around line 278-279: The local variable agentKind is annotated as
(session0.agentKind as "claude" | "codex" | null) but elsewhere (see
runAgentTurn call around lines 450-451) the code expects "claude" | "codex" |
"crush" | null; update the annotation here to include "crush" so the union
matches the caller: change the cast on session0.agentKind to "claude" | "codex"
| "crush" | null and keep the same default value logic (e.g., ?? "claude") so
the type system correctly tracks the "crush" branch.
---
Nitpick comments:
In `@apps/desktop/src/main/todo-agent/trpc-router.ts`:
- Line 551: Remove the unnecessary any cast and access crushModel in a type-safe
way: replace (source as any).crushModel ?? null with a typed reference (e.g.
(source as SelectTodoSession).crushModel ?? null) or, better, ensure the
surrounding code/parameter is typed as SelectTodoSession so you can simply use
source.crushModel ?? null; target the crushModel property on SelectTodoSession
to remove the any cast.
In `@apps/desktop/src/main/todo-daemon/crush-turn-runner.ts`:
- Around line 99-118: stderr からの正規表現抽出だけに頼る現在の crushSessionId
ロジックは壊れやすいので、一定時間(例: 起動後 5–10 秒)以内に crushSessionId
が取得できなければフォールバックを行うように修正してください: pollDbForEvents / getSessionId
の呼び出し経路にタイムアウト判定を追加して(crushSessionId が null のまま一定時間経過したら)① DB の sessions テーブルを
created_at DESC で最新 1 件を look-up して session_id を取得して使う処理を実装するか、② DB lookup
ができない/失敗した場合は params.emit を使って system_warn(例: "Crush セッションID未取得のため DB を参照しました" や
"セッションID未取得")を送出するようにし、crushSessionId 変数と pollDbForEvents のループが永久に続かないようにしてください。
In `@apps/desktop/src/main/todo-daemon/supervisor-engine.ts`:
- Around line 265-308: The code never emits a setup banner for the "crush" agent
so selected Crush model/config isn't shown in the UI; update the agentKind type
annotation on the const agentKind (currently "claude" | "codex" | null) to
include "crush", then add a branch alongside the existing agentKind === "codex"
/ else handling that calls appendSetupEvent(sessionId, "Crush", "<Crush startup
message>") and also emits a Crush 設定 setup event when session0.crushModel or
session0.crushEffort are present (mirroring the pattern used for
session0.claudeModel / session0.codexModel and appendSetupEvent("Claude
設定"/"Codex 設定")). Ensure the willUsePty / remoteControlEnabled logic remains
unchanged and reuse the same parts.join(" / ") formatting for Crush setup
events.
In
`@apps/desktop/src/renderer/features/todo-agent/ClaudeRuntimePicker/AgentRuntimePicker.tsx`:
- Around line 260-280: The UI currently renders a flat list of SelectItem from
grouped but should use SelectGroup and SelectLabel to visually separate
providers; update the render inside SelectContent to iterate
Object.entries(grouped) and for each provider render a SelectGroup with a
SelectLabel showing the provider and map its models to SelectItem; also change
model.replace(`${provider}/`, "") to use model.slice(provider.length + 1) (or
equivalent) to make the trimming intent clearer; keep DEFAULT_SENTINEL handling
as a top-level SelectItem.
In `@packages/local-db/src/schema/todo-schedules.ts`:
- Around line 65-75: Update the comment for the agentKind column in
packages/local-db/src/schema/todo-schedules.ts to reflect the newly supported
agent types: include "crush" alongside "claude" and "codex" so it matches the
added crushModel field; locate the agentKind declaration (agentKind:
text("agent_kind").notNull().default("claude")) and expand the inline comment to
enumerate "claude", "codex", and "crush" and optionally note which model
overrides (codexModel, codexEffort, crushModel) apply to each agentKind.
In `@packages/local-db/src/schema/todo-sessions.ts`:
- Around line 100-114: Update the comment for the agentKind field to include the
new "crush" option so it no longer contradicts crushModel; specifically edit the
comment above agentKind (symbol: agentKind) to list the valid values "claude",
"codex", and "crush", keep the default as "claude" for backward compatibility,
and mention that this determines which turn runner the supervisor engine
dispatches to (so it aligns with the crushModel comment and the crushModel
symbol).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5d1173ec-f7bb-4f4b-b6d4-13e15edff246
📒 Files selected for processing (15)
apps/desktop/src/main/todo-agent/session-store.tsapps/desktop/src/main/todo-agent/settings.tsapps/desktop/src/main/todo-agent/trpc-router.tsapps/desktop/src/main/todo-agent/types.tsapps/desktop/src/main/todo-daemon/crush-turn-runner.tsapps/desktop/src/main/todo-daemon/supervisor-engine.tsapps/desktop/src/renderer/features/todo-agent/ClaudeRuntimePicker/AgentRuntimePicker.tsxapps/desktop/src/renderer/features/todo-agent/ClaudeRuntimePicker/claudeRuntimeOptions.tsapps/desktop/src/renderer/features/todo-agent/ClaudeRuntimePicker/index.tsapps/desktop/src/renderer/features/todo-agent/TodoModal/TodoModal.tsxpackages/local-db/drizzle/0069_add_crush_model.sqlpackages/local-db/drizzle/meta/0069_snapshot.jsonpackages/local-db/drizzle/meta/_journal.jsonpackages/local-db/src/schema/todo-schedules.tspackages/local-db/src/schema/todo-sessions.ts
`pollDbForEvents` の `settled` が `const false` だったため、正常終了時に `await pollPromise` が永久にブロックしセッションが running でスタックしていた。 修正: - `settled` を `let` に変更し、DB で `finish` part (end_turn/stop) を 検知したら `settled = true` にする - `isChildExited` コールバックを追加し、子プロセス終了後もポーリングが 停止するようにした(DB に finish が書かれないエラー/シグナルケース対応) Codex review P1 指摘への対応。 💘 Generated with Crush Assisted-by: GLM-5 via Crush <crush@charm.land>
- crush-turn-runner: `safeParseJson` で `Array.isArray` チェックを追加し、 非 JSON 配列が渡った際の TypeError を防止 - crush-turn-runner: `killProcess` で `child.pid` の undefined ガードを追加 - crush-turn-runner: `cost` 取得時の NULL チェックを追加 (`typeof === "number" && Number.isFinite`) - supervisor-engine: `(currentSession as any).crushModel` の `as any` を削除 - trpc-router: `(source as any).crushModel` の `as any` を削除 - trpc-router: `crushModels` クエリに TTL キャッシュ (5分) と inflight 重複排除を追加 💘 Generated with Crush Assisted-by: GLM-5 via Crush <crush@charm.land>
PresetsDialog (設定タブ) と TodoManager (インラインコンポーザー) に Crush CLI 向けモデル選択 UI を追加。PR #387 で Codex に対応した 両コンポーネントで Crush の抜けがあったのを補完。 💘 Generated with Crush Assisted-by: GLM-5 via Crush <crush@charm.land>
This reverts commit 4973a9d.
Summary
crush run) に対応.crush/crush.db) を 250ms ポーリング してメッセージを取得し、既存のTodoStreamEvent形式に変換する方式を採用crush models(276+ モデル) を動的フェッチし、provider ごとにグループ化して UI 表示。ハードコードなし。Crush には effort の概念がないため、モデル選択のみ変更点
コアロジック
types.ts—AgentKindenum に"crush"を追加、crushModelフィールドをスキーマに追加crush-turn-runner.ts(新規) —crush run --yoloを spawn →.crush/crush.dbをポーリング → parts JSON をTodoStreamEventに変換text→assistant_texttool_call→tool_usetool_result→tool_resultfinish(reason による) →result/errorreasoning/binary→ スキップsupervisor-engine.ts—runAgentTurn()にagentKind === "crush"ルートを追加データ層
todo_sessions/todo_schedulesにcrush_modelカラム追加0069_add_crush_model.sqlcrushModelsクエリ追加 (crush modelsの出力をキャッシュ付きで返す)UI
AgentRuntimePicker.tsx— Agent セレクタに "Crush" 追加。モデルリストは provider ごとにグループ化claudeRuntimeOptions.ts—CrushModelPick型 + 永続化ヘルパーTodoModal.tsx—crushModelstate + フェッチ + 送信設計上の決定
agent_kindのデフォルトは引き続き"claude"。既存セッションへの影響なしcrush modelsからランタイムで取得。276+ モデルをハードコードしないTest plan
bun run typecheckが通ること(27/27 tasks)bun run lintが通ること(warnings only, errors 0)agent_kind = "claude"で正常動作することcrush run --yoloが spawn され、DB ポーリングでストリーミング表示されることcrush run --sessionで正しく動作すること💘 Generated with Crush
Assisted-by: GLM-5 via Crush crush@charm.land
Summary by CodeRabbit
リリースノート