Skip to content

Commit 06bc90a

Browse files
authored
feat: エージェント切替でヘッダのプラン / モデル / 使用状況も切り替わるようにする (#130)
## 背景 `/agent` で新規セッションの既定エージェントを切り替えても、一覧画面のヘッダ(バナー)は - **プラン**: claude.ai のプラン名(`プラン: Claude Team`)を出しっぱなし - **モデル**: 起動時の `config.model` を prop で配っていたため、切替でモデル指定が CLI 既定へ戻っても前の値が居座る - **使用状況**: 「既定エージェント **or** archived でないセッションのどれかが `usage` を報告する」判定だったため、Claude セッションが残っている間はゲージも残る という状態で、**エージェント名だけが変わって残りが前の provider のまま**でした。ヘッダは「次に動くエージェント」の説明なので、項目ごとに読み方が食い違います。 ## 変更 - `showsAccountUsage` → **`showsAccountInfo`**(`core/agent-capabilities.ts`)。判定を**既定エージェントだけ**に狭め、稼働中セッションは見ない。プラン名と使用状況は同じ 1 回の probe(`utils/usage-probe.ts`)が運ぶので、判定も 1 本にまとめてプラン行とゲージを揃って出し分ける - ヘッダのモデル欄と既定エージェントを **manager の購読**で読む(`ui/hooks.ts` の `useDefaultModel` / `useDefaultAgent`)。`SessionManager.setModel` / `setDefaultAgent` が `store.notify()` するので、切替でモデルが CLI 既定へ戻ったことも即座に出る - 起動時の設定値を配っていた `App` / `SessionList` の `model` prop を削除(既定モデルの出所を manager 1 つにする) - 取得側(`bootstrap/usage-poller.ts` の `enabled`)も同じ純関数を通したままなので、**出さないゲージのために 5 分ごとの `claude` probe は立たない**(既定を Claude へ戻せば次の poll から再開) 結果、`/agent` で Codex / Grok を選ぶと `エージェント: Codex` / `モデル: CLI 既定` になり、プラン行と使用状況セクションは消えます。 ## トレードオフ Claude のセッションが走っている最中に既定を切り替えると、その消費(ゲージ)は見えなくなります。「ヘッダは既定エージェントの説明」という一貫性を、消費を覗ける便利さより優先しました(既定を戻せばまた出ます)。 ## テスト - `npm run lint` / `npm run typecheck` / `npm test`(2982 passed・カバレッジ閾値クリア)/ `npm run build` - 追加: `tests/app.test.tsx` … `/agent` で既定を切り替えるとヘッダのプラン / モデル / 使用状況も入れ替わる(実 CLI を起こさないフェイクアダプタで配線を検証) - 追加: `src/core/session-manager.spec.ts` … `setModel` / `setDefaultAgent` が購読者へ通知する(同じ値なら通知しない) - 更新: `src/core/agent-capabilities.spec.ts` … `showsAccountInfo` のテーブル ## ドキュメント `docs/ARCHITECTURE.md`(縮退の表 + 判定を狭めた理由)/ `docs/TASKS.md` / `CLAUDE.md` / `.claude/rules/sdk-integration.md` / `README.md`(ユーザー可視の挙動)を更新しました。 ## 手動確認のお願い 非対話セッションのため TUI の体感確認ができていません。`/agent` での切替時にヘッダの 4 項目(エージェント / プラン / モデル / 使用状況)が揃って入れ替わるか、実端末で見ていただけると助かります。
1 parent 11cee35 commit 06bc90a

14 files changed

Lines changed: 228 additions & 79 deletions

.claude/rules/sdk-integration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
- その provider に無い機能は `AgentCapabilities` で表明する(`permissions` / `interrupt` /
3333
`setModel` / `resume` / `modelCatalog` / `usage` / `cost` / `transcript`)。UI は capability を
3434
見て縮退する。**判定は純粋な `core/agent-capabilities.ts` を通す**`supportsCapability` /
35-
`capabilityLookup` / `agentSupports` / `showsAccountUsage`)。守ること 2 つ:
35+
`capabilityLookup` / `agentSupports` / `showsAccountInfo`)。守ること 2 つ:
3636
- **capability が分からないときは縮退しない**(未登録の provider・`agent` を持たない古い
3737
セッションで機能を隠すと、動くはずの操作が黙って消える)。
3838
- **「値が 0 だから自然に消える」に頼らない**。コスト・使用状況・トランスクリプト復元は

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ CI(`.github/workflows/ci.yml`)は `lint → typecheck → test → build`。
6161
| セッションの状態・遷移 | `core/types.ts`(union)/ `core/status-meta.ts`(性質の表)/ `core/status-reducer.ts`(純粋 reducer) |
6262
| 別のエージェントに対応させる | `core/agent-ports.ts`(`AgentAdapter` / `AgentCapabilities` / `PermissionDecision` = DI 境界)/ `core/agent-events.ts`(`AgentEvent` の語彙 + 全 provider 共通の畳み込み `applyAgentEvent`)/ `core/claude-adapter.ts`・`core/claude-parse.ts`・`core/claude-errors.ts`(Claude 実装の 3 点セット)/ `core/codex-adapter.ts`・`core/codex-parse.ts`・`core/codex-errors.ts` + `core/codex-events.ts`(JSONL の型)・`core/codex-models.ts`・`core/codex-rollout.ts`(rollout から解決済みモデル)・`utils/codex.ts`(`codex exec` の起動 = 唯一の I/O)/ `core/grok-adapter.ts`・`core/grok-parse.ts`・`core/grok-errors.ts` + `core/grok-events.ts`(ACP メッセージの型)・`core/grok-models.ts`・`utils/grok.ts`(`grok agent stdio` の起動 = 唯一の I/O)/ 行区切り JSON の枠切りは provider 非依存の `core/jsonl.ts`(Codex / Grok 共用)/ アダプタの登録は `bootstrap/build-manager.ts` の `buildAgents` |
6363
| SDK メッセージの解釈 | `core/claude-parse.ts` **のみ**`parseClaudeMessage`: SDKMessage → `AgentEvent[]`)+ `core/__fixtures__/*.jsonl`。Codex は `core/codex-parse.ts``parseCodexEvent`: `codex exec --json` の JSONL → `AgentEvent[]`)+ `core/__fixtures__/codex-*.jsonl`。Grok は `core/grok-parse.ts``createGrokParser`: ACP = JSON-RPC over stdio の通知 → `AgentEvent[]`)+ `core/__fixtures__/grok-*.jsonl` |
64-
| capability による UI 縮退(コスト・使用状況・確認モード・ログ復元) | `core/agent-capabilities.ts``supportsCapability` = **不明なら縮退しない** / `capabilityLookup` / `agentSupports` / `showsAccountUsage`・純粋)/ `core/cost.ts``totalCostUsd(states, reportsCost)` / `bootstrap/usage-poller.ts``enabled` / `bootstrap/restore-sessions.ts` / `ui/status-footer.tsx``confirmSupported` |
65-
| どのセッションが何で走っているかの表示 | `core/agent-display.ts``sessionAgentId` / `usesMultipleAgents`)/ `core/layout.ts``showsAgentColumn`(混在時だけ列を出す)/ `core/banner-lines.ts``agent`(ヘッダ = 既定)/ `core/scroll.ts``logLines(…, dividerFor)`(ログの切替区切り)/ `m.detail.followupPlaceholder(agent)`(詳細の入力欄) |
64+
| capability による UI 縮退(コスト・プラン/使用状況・確認モード・ログ復元) | `core/agent-capabilities.ts``supportsCapability` = **不明なら縮退しない** / `capabilityLookup` / `agentSupports` / `showsAccountInfo` = プラン + 使用状況は**既定エージェント**で出し分け・純粋)/ `core/cost.ts``totalCostUsd(states, reportsCost)` / `bootstrap/usage-poller.ts``enabled` / `bootstrap/restore-sessions.ts` / `ui/status-footer.tsx``confirmSupported` |
65+
| どのセッションが何で走っているかの表示 | `core/agent-display.ts``sessionAgentId` / `usesMultipleAgents`)/ `core/layout.ts``showsAgentColumn`(混在時だけ列を出す)/ `core/banner-lines.ts``agent`(ヘッダ = 既定。エージェント名・プラン・モデル・使用状況は `ui/hooks.ts``useDefaultAgent` / `useDefaultModel` を購読して**揃って**切り替わる)/ `core/scroll.ts``logLines(…, dividerFor)`(ログの切替区切り)/ `m.detail.followupPlaceholder(agent)`(詳細の入力欄) |
6666
| エージェント切替時の引き継ぎ | `core/agent-handoff.ts``handoffInstruction` / `lastUserInstruction`・英語固定 = AI 向け文字列)/ `core/system-prompt.ts``handoff` 節 / `core/session.ts``setAgent`**使い捨て**で次の `open()` が消費) |
6767
| エージェントの切替(`/agent`| `core/session-manager.ts`(一覧=既定: `getDefaultAgentId` / `setDefaultAgent`・詳細=切替: `listAgents` / `getSessionAgent` / `setSessionAgent`)/ `ui/agent-select.tsx``mode:'default'`=一覧 / `'session'`=詳細)/ `core/status-reducer.ts``agent_switched` |
6868
| エージェントの導入・ログイン検出 | `core/agent-ports.ts``AgentAdapter.checkAvailability` / `AgentAvailability` / `core/agent-availability.ts``resolveDefaultAgentId` / `noAgentInstalled`・純粋)/ `utils/claude.ts``detectClaudeAvailability``utils/codex.ts``detectCodexAvailability``utils/grok.ts``detectGrokAvailability`(実 I/O)/ `SessionManager.checkAgents`(集約・キャッシュ)/ `ui/hooks.ts``useAgentAvailability` |

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ Codiva v0.3.1 3 セッション
282282
- 使用率(`%`)は Claude が返さないプランもあります。その場合はゲージを出さず残り時間だけを表示します
283283
(0% と誤読させないため)。
284284
- API キー / Bedrock / Vertex 利用時はサブスク制限が無いため、この表示は出ません。
285+
- **ヘッダは「次に動くエージェント」の説明**です。`/agent` で既定を Codex / Grok に切り替えると、
286+
エージェント名・プラン・モデル・使用状況が揃って切り替わります(どちらもプランや使用状況を
287+
報告しないので、その間はプラン行とゲージが消え、モデルは `モデル: CLI 既定` に戻ります)。
288+
claude.ai への 5 分ごとの自動取得もその間は止まり、既定を Claude へ戻すと再開します。
285289
- 画面下部のステータスバーはモード表示(`⏵⏵ 自動モード`)と操作ヒントだけです。プラン / 使用状況は
286290
ヘッダに集約しています(詳細ビューで見たいときは Esc で一覧へ戻ります)。
287291

@@ -317,15 +321,15 @@ Codiva v0.3.1 3 セッション
317321
**Codex セッションの制約**(Claude セッションとの違い):
318322

319323
- **ツール使用の許可を尋ねません。** `codex exec` の JSON 出力モードは承認要求を CLI 内部で自動的に拒否してしまい、codiva 側へ上げる手段がありません。そこで codiva は「それらしい許可ダイアログ」を出さず、**サンドボックスを唯一の安全弁**にしています(設定 `codexSandbox`。既定の `workspace-write` では書き込みがセッションの worktree 内に限定されます)。`質問あり` の状態にもなりません。
320-
- **コストを表示しません。** Codex はターン終了時にトークン数しか返さず、金額もアカウント全体の使用状況も運びません。ヘッダの合計コストには Codex のセッションを数えません(Claude ぶんだけの金額を「全体」として出さないため)。使用状況ゲージは Claude のアカウントの枠なので**Codex / Grok だけで作業している間はヘッダに出ません**(取得もしません)。
324+
- **コストを表示しません。** Codex はターン終了時にトークン数しか返さず、金額もアカウント全体の使用状況も運びません。ヘッダの合計コストには Codex のセッションを数えません(Claude ぶんだけの金額を「全体」として出さないため)。プラン表示と使用状況ゲージは Claude のアカウントの話なので**`/agent` で既定を Codex にしている間はヘッダに出ません**(取得もしません)。
321325
- **フッタのモード表示が `確認モード (非対応)` になります。** 許可を尋ねられないので、`確認モード` のままだと「待っていれば聞かれる」と読めてしまうためです(`shift+tab` の切替そのものは効きます)。
322326
- **再起動後にログが復元されません**(セッションの続きを再開すること自体はできます)。ログの再構築は Claude CLI の記録ファイルを読む仕組みで、Codex の記録は形式が異なるためです。
323327
- `/model` の選択肢は Codex 側のモデル一覧(`codex debug models`)になります。一覧を取得できない環境では「デフォルト」だけになります(推測でモデル名を並べません)。`/agent` で provider を切り替えると、互換性のない切替前のモデル指定は CLI 既定へ戻ります。Codex は実行イベントにモデル名を含めないため、`/model` で明示したモデル名をセッション一覧に表示します。
324328

325329
**Grok セッションの制約**(Claude セッションとの違い):
326330

327331
- **ツール使用の許可と質問はそのまま届きます。** Codex と違い、Grok は許可要求(`許可待ち`)と質問(`質問あり`)を codiva の双方向のやり取りで上げてくるので、いつもどおりダイアログで応答できます。
328-
- **コストを表示しません。** Grok はターンの終わりにトークン数しか返さず、金額もアカウント全体の使用状況も運びません。ヘッダの合計コストには Grok のセッションを数えず、使用状況ゲージ(Claude のアカウントの枠)も Grok だけで作業している間は出ません
332+
- **コストを表示しません。** Grok はターンの終わりにトークン数しか返さず、金額もアカウント全体の使用状況も運びません。ヘッダの合計コストには Grok のセッションを数えず、プラン表示と使用状況ゲージ(Claude のアカウントの話)も既定を Grok にしている間は出ません
329333
- **再起動後にログが復元されません**(セッションの続きを再開すること自体はできます)。ログの再構築は Claude CLI の記録ファイルを読む仕組みで、Grok の記録は形式が異なるためです。
330334
- `/model` の選択肢は Grok 側のモデル一覧になります。一覧を取得できない環境では「デフォルト」だけになります(推測でモデル名を並べません)。`/agent` で provider を切り替えると、互換性のない切替前のモデル指定は CLI 既定へ戻ります。Codex と違い Grok は**実際に動いているモデル名を自分で教えてくれる**ので、`/model` で明示していなくてもセッション一覧にモデル名が出ます。
331335

docs/ARCHITECTURE.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ codiva/
6363
│ │ ├── status-reducer.ts # reduce(state, CodivaEvent): SessionState(codiva 起点のイベント・純関数)
6464
│ │ ├── agent-ports.ts # エージェントの DI 境界(AgentAdapter/AgentRun/AgentCapabilities/PermissionDecision・leaf)
6565
│ │ ├── agent-events.ts # AgentEvent の語彙 + applyAgentEvent()(全 provider 共通の畳み込み・純粋)
66-
│ │ ├── agent-capabilities.ts # capability による UI 縮退の判定(不明なら縮退しない・showsAccountUsage
66+
│ │ ├── agent-capabilities.ts # capability による UI 縮退の判定(不明なら縮退しない・showsAccountInfo
6767
│ │ ├── agent-display.ts # 「どのセッションが何で走っているか」の判定(sessionAgentId / usesMultipleAgents)
6868
│ │ ├── agent-handoff.ts # 切替先へ渡す状況説明(英語固定・systemPrompt に 1 回だけ載る)
6969
│ │ ├── claude-adapter.ts # Claude 用 AgentAdapter(query() の組み立て・canUseTool の写像)
@@ -319,7 +319,7 @@ provider が増えてもビュー側の分岐は増えない(未登録のエ
319319
エージェント名は固有名詞なので翻訳しない(モデル名と同じ i18n の例外)。
320320

321321
縮退の判定は**純粋な `core/agent-capabilities.ts`** に寄せてある(`supportsCapability` /
322-
`capabilityLookup` / `agentSupports` / `showsAccountUsage`)。要点は 2 つ:
322+
`capabilityLookup` / `agentSupports` / `showsAccountInfo`)。要点は 2 つ:
323323

324324
- **capability が分からないときは縮退しない**`supportsCapability(undefined, …) === true`)。
325325
未登録の provider・`agent` を持たない古いセッションで機能を隠すと、動くはずの操作が黙って
@@ -333,7 +333,7 @@ provider が増えてもビュー側の分岐は増えない(未登録のエ
333333
| `/model` のダイアログ | `setModel` / `modelCatalog` | `ui/session-detail.tsx` | 開かずに理由を出す・選択肢を provider 別に出し分け |
334334
| `Ctrl+C` のヒント | `interrupt` | `ui/session-detail.tsx` | ヒント行を出さない |
335335
| 合計コスト(ヘッダ) | `cost` | `core/cost.ts``totalCostUsd(states, reportsCost)` | 報告しない provider のセッションを合計に数えない |
336-
| 使用状況ゲージ(ヘッダ) | `usage` | `showsAccountUsage`(一覧の表示 + `bootstrap/usage-poller.ts``enabled`| 使っていなければ**出さないし取りにも行かない**(5 分ごとの probe を立てない) |
336+
| プラン名 + 使用状況ゲージ(ヘッダ) | `usage` | `showsAccountInfo`(一覧の表示 + `bootstrap/usage-poller.ts``enabled`| **既定エージェント**が報告しなければ**出さないし取りにも行かない**(5 分ごとの probe を立てない) |
337337
| 確認モードのフッタ表示 | `permissions` | `ui/status-footer.tsx``confirmSupported` | `確認モード (非対応)` に差し替える(下記) |
338338
| トランスクリプト復元 | `transcript` | `bootstrap/restore-sessions.ts` | その provider のセッションでは読みにも行かない |
339339
| 認証切れの文言 | —(`AgentLabel`| 一覧・詳細・通知 | 駆動中の provider のコマンド名を出す |
@@ -343,12 +343,21 @@ provider が増えてもビュー側の分岐は増えない(未登録のエ
343343
「待っていれば聞かれる」と読めてしまっていた(ツールは確認なしに実行される)。ダイアログを
344344
偽装しないのと同じ理由で、**モード表示の側を正直にする**
345345

346-
**使用状況ゲージを消す判定**`showsAccountUsage`)は「新規セッションの既定エージェント、または
347-
`archived` でないセッションのどれかが `usage` を報告する」。ゲージが表しているのは
348-
その provider のアカウントの消費で、Codex / Grok だけで作業している人には読みようがない
349-
`archived` を数えないのは、乗り換えた人のヘッダにマージ済みのセッション 1 件で残り続けるのを
350-
避けるため)。**表示と取得は同じ純関数を通す**ので、出していないゲージのために
351-
`claude` のサブプロセスが立つことはない。
346+
**ヘッダは「次に動くエージェント」の説明**にする。エージェント名・プラン名・モデル・使用状況は
347+
1 つのアカウントの話として同じ場所に並んでいるので、`/agent` で既定を切り替えたら**4 つ揃って**
348+
入れ替わる。判定は `showsAccountInfo`(= 既定エージェントが `usage` を報告するか)1 本で、
349+
プラン名も枠も**同じ 1 回の probe**`utils/usage-probe.ts`)が運ぶので分けていない。
350+
**表示と取得は同じ純関数を通す**ので、出していないゲージのために `claude` のサブプロセスが
351+
立つことはない(既定を Claude へ戻せば次の poll から再開する)。
352+
353+
- かつては「既定エージェント **or** `archived` でないセッションのどれかが報告する」で判定して
354+
いたため、Codex / Grok に切り替えても Claude のプランとゲージが残っていた。稼働中の Claude
355+
セッションの消費が覗けるという利点はあったが、ヘッダの読み方(= 既定エージェントの説明)が
356+
項目ごとに食い違うほうが害が大きいと判断して、**既定エージェントだけ**を見る形に狭めた。
357+
- モデル欄と既定エージェントは **manager の購読**で読む(`ui/hooks.ts``useDefaultModel` /
358+
`useDefaultAgent``setModel` / `setDefaultAgent``store.notify()` する)。起動時の設定値を
359+
prop で配っていたときは、`/model` やエージェント切替(既定モデルを CLI 既定へ戻す)のあとも
360+
前の値が居座って、実際に使われるモデルと食い違って見えていた。
352361

353362
### 6. Codex アダプタ: 1 ターン = 1 プロセス
354363

docs/TASKS.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,13 +1313,15 @@ zsh: abort codiva
13131313
`agent.noneInstalled` の 1 行を出す(`noAgentInstalled` が全件未導入で確定したときだけ)
13141314
- [x] **残りの capability 縮退**: 判定を純粋な `core/agent-capabilities.ts` に集約
13151315
`supportsCapability` = **不明なら縮退しない** / `capabilityLookup` / `agentSupports` /
1316-
`showsAccountUsage`)。「数字が 0 だから自然に消える」偶然に頼るのをやめ、明示的な分岐にした:
1316+
`showsAccountInfo`)。「数字が 0 だから自然に消える」偶然に頼るのをやめ、明示的な分岐にした:
13171317
- `cost`: `totalCostUsd(states, reportsCost)` が報告しない provider を合計から外す
13181318
(混在時に「Claude ぶんの合計」を全体として出さない)
1319-
- `usage`: 一覧が `showsAccountUsage`(既定エージェント or `archived` でないセッションの
1320-
どれかが `usage` を報告するか)でゲージを出し分け、**同じ純関数**
1321-
`bootstrap/usage-poller.ts``enabled` にも渡す = 出さないゲージのために 5 分ごとの
1322-
`claude` probe を立てない
1319+
- `usage`: 一覧が `showsAccountInfo`**既定エージェント**`usage` を報告するか)で
1320+
プラン名とゲージを出し分け、**同じ純関数**`bootstrap/usage-poller.ts``enabled`
1321+
にも渡す = 出さないゲージのために 5 分ごとの `claude` probe を立てない
1322+
(当初は「既定 or `archived` でないセッションのどれか」で判定していたが、それだと
1323+
`/agent` で Codex へ切り替えても Claude のプランと枠が残り、ヘッダの読み方が項目ごとに
1324+
食い違ったので**既定エージェントだけ**に狭めた)
13231325
- `permissions`: フッタの確認モード表示を `confirmSupported``確認モード (非対応)`
13241326
差し替える(Codex では原理的に聞かれないのに「確認モード」と言い切っていた =
13251327
「待っていれば聞かれる」と読める嘘)。ダイアログ自体は偽装も抑止もしない

0 commit comments

Comments
 (0)