Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .claude/rules/sdk-integration.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# エージェント連携規約(Claude Agent SDK)

コーディングエージェントとの境界と、`@anthropic-ai/claude-agent-sdk` を触るときの不変条件。
**`core/agent-ports.ts` / `core/agent-events.ts` / `core/claude-adapter.ts` / `core/claude-parse.ts` /
**`core/agent-ports.ts` / `core/agent-events.ts` / `core/agent-capabilities.ts` /
`core/agent-handoff.ts` / `core/claude-adapter.ts` / `core/claude-parse.ts` /
`core/claude-errors.ts` / `core/codex-adapter.ts` / `core/codex-parse.ts` / `core/codex-errors.ts` /
`core/grok-adapter.ts` / `core/grok-parse.ts` / `core/grok-errors.ts` / `core/jsonl.ts` /
`core/session.ts` / `utils/model-catalog.ts` / `utils/codex.ts` / `utils/grok.ts` /
Expand Down Expand Up @@ -30,7 +31,15 @@
provider 形への写像はアダプタが行う(Claude は `claude-adapter.ts` の `canUseTool`)。
- その provider に無い機能は `AgentCapabilities` で表明する(`permissions` / `interrupt` /
`setModel` / `resume` / `modelCatalog` / `usage` / `cost` / `transcript`)。UI は capability を
見て縮退する(今つながっているのは `/model` と `Ctrl+C`。残りは Phase D)。
見て縮退する。**判定は純粋な `core/agent-capabilities.ts` を通す**(`supportsCapability` /
`capabilityLookup` / `agentSupports` / `showsAccountUsage`)。守ること 2 つ:
- **capability が分からないときは縮退しない**(未登録の provider・`agent` を持たない古い
セッションで機能を隠すと、動くはずの操作が黙って消える)。
- **「値が 0 だから自然に消える」に頼らない**。コスト・使用状況・トランスクリプト復元は
Claude 由来の仕組みで、他 provider は何も供給しないので今は勝手に消えるが、それは偶然。
混在時に「Claude ぶんの合計」を全体として出す余地が残るので明示的な分岐にする
(どこで何を縮退させているかの表は docs/ARCHITECTURE.md)。表示を縮退させたら
**取得も止める**(出さないゲージのために `claude` の probe を立てない)。
`AgentRun.interrupt` / `setModel` は
optional。新しいアダプタは `NO_CAPABILITIES` から始めて、実装できたものだけ true にする。

Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ CI(`.github/workflows/ci.yml`)は `lint → typecheck → test → build`。
| セッションの状態・遷移 | `core/types.ts`(union)/ `core/status-meta.ts`(性質の表)/ `core/status-reducer.ts`(純粋 reducer) |
| 別のエージェントに対応させる | `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` |
| 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` |
| 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` |
| どのセッションが何で走っているかの表示 | `core/agent-display.ts`(`sessionAgentId` / `usesMultipleAgents`)/ `core/layout.ts` の `showsAgentColumn`(混在時だけ列を出す)/ `core/banner-lines.ts` の `agent`(ヘッダ = 既定)/ `core/scroll.ts` の `logLines(…, dividerFor)`(ログの切替区切り)/ `m.detail.followupPlaceholder(agent)`(詳細の入力欄) |
| エージェント切替時の引き継ぎ | `core/agent-handoff.ts`(`handoffInstruction` / `lastUserInstruction`・英語固定 = AI 向け文字列)/ `core/system-prompt.ts` の `handoff` 節 / `core/session.ts` の `setAgent`(**使い捨て**で次の `open()` が消費) |
| エージェントの切替(`/agent`)| `core/session-manager.ts`(一覧=既定: `getDefaultAgentId` / `setDefaultAgent`・詳細=切替: `listAgents` / `getSessionAgent` / `setSessionAgent`)/ `ui/agent-select.tsx`(`mode:'default'`=一覧 / `'session'`=詳細)/ `core/status-reducer.ts` の `agent_switched` |
| エージェントの導入・ログイン検出 | `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` |
| エージェントに codiva 内でサインイン(`/login` / `/agent` の `l`)| `core/agent-login.ts`(URL/コード抽出・ANSI 除去・純粋)/ `utils/agent-login.ts`(`spawnLogin` = プロセス起動)/ `ui/login-dialog.tsx` / `core/agent-ports.ts` の `AgentAdapter.login` + `AgentLoginProcess` / `SessionManager.startLogin` / `canLogin` / `refreshAgents` |
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,17 +303,27 @@ Codiva v0.3.1 3 セッション

一度使ったエージェントの会話 id はセッションごとに保存されるので、Claude → Codex → Claude と戻したときは**元の会話の続き**から再開します(codiva を再起動しても同じです)。

会話の文脈は渡せませんが、**切替先には「引き継ぎの覚書」を 1 回だけ渡します** — ブランチ名・そのセッションの最初の指示・直前の指示と、「続ける前に `git status` / `git diff` で作業ツリーの状態を自分で確かめること」を伝えるので、済んだ作業をやり直したり直前の指示を無視したりしにくくなります(切替直後に余分なターンは走りません。次にあなたが指示を送ったときに一緒に渡ります)。

**どのセッションが何で走っているかは画面で分かります。**

- ヘッダに `エージェント: Claude` として**新規セッションの既定**が出ます。
- 一覧の行には、**複数のエージェントが混ざっているときだけ**エージェント名の列が出ます(全部同じならヘッダと重複するだけなので、その幅はタイトルやブランチ名に回します)。
- 詳細ビューの入力欄には `Claude に追加の指示を入力…` のように相手の名前が出ます。
- 途中で切り替えたセッションの会話ログには `── ここから Codex ──` の区切りが入り、どこからが別のエージェントの発言か分かります。

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

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

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

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

Expand Down
52 changes: 48 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ codiva/
│ │ ├── status-reducer.ts # reduce(state, CodivaEvent): SessionState(codiva 起点のイベント・純関数)
│ │ ├── agent-ports.ts # エージェントの DI 境界(AgentAdapter/AgentRun/AgentCapabilities/PermissionDecision・leaf)
│ │ ├── agent-events.ts # AgentEvent の語彙 + applyAgentEvent()(全 provider 共通の畳み込み・純粋)
│ │ ├── agent-capabilities.ts # capability による UI 縮退の判定(不明なら縮退しない・showsAccountUsage)
│ │ ├── agent-display.ts # 「どのセッションが何で走っているか」の判定(sessionAgentId / usesMultipleAgents)
│ │ ├── agent-handoff.ts # 切替先へ渡す状況説明(英語固定・systemPrompt に 1 回だけ載る)
│ │ ├── claude-adapter.ts # Claude 用 AgentAdapter(query() の組み立て・canUseTool の写像)
│ │ ├── claude-parse.ts # parseClaudeMessage()(SDK メッセージ形状の解釈を集約・純粋)
│ │ ├── claude-errors.ts # Claude CLI の失敗分類(文言/typed kind/HTTP status → AgentStopCause)
Expand Down Expand Up @@ -262,6 +265,20 @@ provider ごとの resume id を控え、**これは永続化する**(`state.j
会話を resume しようとして壊れる)。
- **ログ行の帰属**(`LogEntry.agent`)は**切替が起きたあとだけ**刻む。単一エージェントで完結する
セッションのログ行の形を変えないため(切替を使っていないユーザーには何も増えない)。
詳細ビューはこの帰属が変わる境界に区切り行(`── ここから Codex ──`)を 1 本挿む
(行の挿入は `core/scroll.ts` の `logLines(…, dividerFor)`、文言はカタログ + アダプタの表示名)。
- **引き継ぎの状況説明を 1 回だけ渡す**(`core/agent-handoff.ts` の `handoffInstruction`)。
切替先は前の会話を持たないので、何も渡さないと「途中まで作業された作業ツリー」を白紙から
見ることになり、済んだ作業をやり直したり直前の指示を無視したりする。ブランチ・最初の指示・
直前の指示を並べ、**続ける前に自分で `git status` / `git diff` を読む**よう促す文を
`AgentRunOptions.systemPrompt`(`composeSystemPrompt` の最後の節)に載せる。
- **使い捨て**にする(`Session` が次の `open()` で消費する)。常設にすると、引き継ぎが済んだ
あとのターンや通信断からの再起動でも「前任者から引き継いだ」と言い続けることになる。
- **キューへ指示として積まない**。積むと切替直後に「状況を読むだけのターン」が 1 本走り、
provider のプロセスを無駄に立てる(ユーザーが次の指示を出すまで何も起こらないのが正しい)。
- 各項目は 1 行に畳んで `MAX_HANDOFF_FIELD_CHARS` で切る(指示文はファイルを丸ごと貼った
ものになりうるので、systemPrompt が本文より大きくなるのを防ぐ)。AI 向けの文字列なので
i18n カタログには置かない(英語固定。`SHARED_IGNORED_FILES_NOTICE` と同じ扱い)。

### 5. Claude 専用機能は capability で optional 化する

Expand Down Expand Up @@ -301,10 +318,37 @@ provider が増えてもビュー側の分岐は増えない(未登録のエ
「`claude` でログインし直して」と言わないための配線で、一覧・詳細・デスクトップ通知の 3 経路で効く。
エージェント名は固有名詞なので翻訳しない(モデル名と同じ i18n の例外)。

> 縮退の配線は Phase D で段階的に入れている。現状効いているのは `/model`(`setModel` /
> `modelCatalog`)・`Ctrl+C`(`interrupt`)・認証文言(`AgentLabel`)で、使用状況ゲージ・
> コスト・許可ダイアログ・トランスクリプト復元はまだ capability を見ていない(現状は
> 実害が出ていないだけ。[TASKS.md](./TASKS.md) の Phase D)。
縮退の判定は**純粋な `core/agent-capabilities.ts`** に寄せてある(`supportsCapability` /
`capabilityLookup` / `agentSupports` / `showsAccountUsage`)。要点は 2 つ:

- **capability が分からないときは縮退しない**(`supportsCapability(undefined, …) === true`)。
未登録の provider・`agent` を持たない古いセッションで機能を隠すと、動くはずの操作が黙って
消える。既存の `caps && !caps.setModel` と同じ規約。
- **「数字が 0 だから自然に消える」に頼らない**。Codex / Grok は USD を運ばないのでヘッダの
合計コストは今のところ勝手に消えるが、それは偶然であって、混在時に「Claude ぶんの合計」を
全体のコストとして出す余地が残る。`AgentCapabilities` を見た明示的な分岐に置き換える。

| 縮退する対象 | capability | 見る場所 | 縮退の形 |
|---|---|---|---|
| `/model` のダイアログ | `setModel` / `modelCatalog` | `ui/session-detail.tsx` | 開かずに理由を出す・選択肢を provider 別に出し分け |
| `Ctrl+C` のヒント | `interrupt` | `ui/session-detail.tsx` | ヒント行を出さない |
| 合計コスト(ヘッダ) | `cost` | `core/cost.ts` の `totalCostUsd(states, reportsCost)` | 報告しない provider のセッションを合計に数えない |
| 使用状況ゲージ(ヘッダ) | `usage` | `showsAccountUsage`(一覧の表示 + `bootstrap/usage-poller.ts` の `enabled`) | 使っていなければ**出さないし取りにも行かない**(5 分ごとの probe を立てない) |
| 確認モードのフッタ表示 | `permissions` | `ui/status-footer.tsx` の `confirmSupported` | `確認モード (非対応)` に差し替える(下記) |
| トランスクリプト復元 | `transcript` | `bootstrap/restore-sessions.ts` | その provider のセッションでは読みにも行かない |
| 認証切れの文言 | —(`AgentLabel`) | 一覧・詳細・通知 | 駆動中の provider のコマンド名を出す |

**確認モードの表示を capability で変える理由**: `permissions: false` の provider(Codex)では
許可ダイアログが原理的に出ない。それでもフッタが `確認モード` と言い切っていたので、
「待っていれば聞かれる」と読めてしまっていた(ツールは確認なしに実行される)。ダイアログを
偽装しないのと同じ理由で、**モード表示の側を正直にする**。

**使用状況ゲージを消す判定**(`showsAccountUsage`)は「新規セッションの既定エージェント、または
`archived` でないセッションのどれかが `usage` を報告する」。ゲージが表しているのは
その provider のアカウントの消費で、Codex / Grok だけで作業している人には読みようがない
(`archived` を数えないのは、乗り換えた人のヘッダにマージ済みのセッション 1 件で残り続けるのを
避けるため)。**表示と取得は同じ純関数を通す**ので、出していないゲージのために
`claude` のサブプロセスが立つことはない。

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

Expand Down
Loading
Loading