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
15 changes: 14 additions & 1 deletion .claude/rules/sdk-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,20 @@ provider のメッセージ ──[アダプタの parse]──▶ AgentEvent[]
載る。**`'project'` は必ず含める**)、
`includePartialMessages: true`(ストリーミングプレビュー用)。**この既定を組み立てるのはアダプタ**で、
`Session` は provider 非依存の `AgentRunOptions`(model / effort / permissionMode / maxBudgetUsd /
systemPrompt)しか渡さない。各項目をどう解釈するか(無視も可)はアダプタの裁量。
systemPrompt / handoff)しか渡さない。各項目をどう解釈するか(無視も可)はアダプタの裁量。
- **`handoff`(`/agent` の引き継ぎ)だけは「無視も可」ではない。** これは `core/agent-handoff.ts`
が組み立てた**1 回きり**の文字列で、アダプタが `attachHandoff(text, handoff)` で
**切替後の最初のユーザープロンプトに前置する**のが契約。`systemPrompt` に混ぜてはいけない
(`codex exec resume` のように再開時に systemPrompt を読み直さない provider があり、
往復切替でだけ引き継ぎが消える)。守ること 2 つ:
- **落とすのは「provider へ実際に渡った」と確認できたときだけ**。立ち上げ中に中断された
ターン(Grok の `runTurn` が捨てる経路)や `thread.started` の前に落ちたターン(Codex)で
無条件に落とすと、1 回きりの引き継ぎを空振りで使い切って**切替の文脈が黙って消える**
(`Session` 側の使い捨ては `open()` の時点で済んでいるので二度と来ない)。
- **大きさは UTF-8 バイトで見積もる**。Codex は指示文を argv で渡すので Linux の
`MAX_ARG_STRLEN`(131,072 バイト)に当たると起動そのものが `E2BIG` で落ちる
(日本語は 1 文字 3 バイト = 文字数の 3 倍。macOS では再現しない)。
予算は `MAX_HANDOFF_TRANSCRIPT_BYTES`。
- `systemPrompt` は**純粋な `core/system-prompt.ts` の `composeSystemPrompt()` で組み立てる**
(`session.ts` に文言や結合順を書かない)。要素は「worktree の環境説明(`ignoredFiles: 'symlink'`
のときだけ載る共有 symlink の注意書き)」→「`<repo>/.codiva/prompt.md` の内容」の順で、
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ CI(`.github/workflows/ci.yml`)は `lint → typecheck → test → build`。
| 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` / `showsAccountInfo` = プラン + 使用状況は**既定エージェント**で出し分け・純粋)/ `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`(ヘッダ = 既定。エージェント名・プラン・モデル・使用状況は `ui/hooks.ts` の `useDefaultAgent` / `useDefaultModel` を購読して**揃って**切り替わる)/ `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()` が消費) |
| エージェント切替時の引き継ぎ | `core/agent-handoff.ts`(`handoffInstruction` / `handoffTranscript` = 会話ログの写し・`attachHandoff` / `lastUserInstruction`・英語固定 = AI 向け文字列)/ `core/agent-ports.ts` の `AgentRunOptions.handoff`(**systemPrompt ではない**。各アダプタが切替後の最初のユーザープロンプトに `attachHandoff` で前置する)/ `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
4 changes: 2 additions & 2 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,11 @@ Codiva v0.3.1 3 セッション

| 引き継がれる | 引き継がれない |
|---|---|
| worktree・ブランチ・作業ツリーの内容、codiva 上のログ・タイトル・PR | **会話の文脈**(各 CLI がそれぞれ自分の記録を持つため) |
| worktree・ブランチ・作業ツリーの内容、codiva 上のユーザー/アシスタント双方の会話ログ・タイトル・PR | provider 固有のセッションそのもの(各 CLI がそれぞれ自分の記録を持つため) |

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

会話の文脈は渡せませんが、**切替先には「引き継ぎの覚書」を 1 回だけ渡します** — ブランチ名・そのセッションの最初の指示・直前の指示と、「続ける前に `git status` / `git diff` で作業ツリーの状態を自分で確かめること」を伝えるので、済んだ作業をやり直したり直前の指示を無視したりしにくくなります(切替直後に余分なターンは走りません。次にあなたが指示を送ったときに一緒に渡ります)。
provider 固有の会話文脈を直接移すことはできないため、codiva が保持している**ユーザーとアシスタント双方の会話ログを、切替先への 1 回限りの引き継ぎ情報としてコピーします**。ブランチ名・そのセッションの最初と直前の指示・「続ける前に `git status` / `git diff` で作業ツリーを確認すること」も一緒に渡します。ツールの実行ログは含めません(量が大きく、作業ツリーを見れば分かるため)。引き継ぎが安全上限に達した場合は新しい会話を優先し、省略したことを明記します(切替直後に余分なターンは走らず、次に入力した指示と一緒に渡ります)。

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

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,11 @@ Here's what does and doesn't carry over when you switch with `/agent`:

| Carries over | Does not carry over |
|---|---|
| The worktree, branch and working tree contents; codiva's log, title and PRs | **The conversation context** (each CLI keeps its own transcript) |
| The worktree, branch and working tree contents; codiva's user/assistant conversation log, title and PRs | The provider's native session itself (each CLI keeps its own transcript) |

The conversation id of each agent you've used is stored per session, so going Claude → Codex → Claude resumes **the original conversation** where it left off (this survives restarting codiva too).

The context can't be transferred, but **the incoming agent gets a one-time handoff note** the branch name, the session's first instruction, the most recent instruction, and a reminder to "verify the state of the working tree yourself with `git status` / `git diff` before continuing" — which makes it much less likely to redo finished work or ignore your last instruction. (No extra turn runs at switch time; the note rides along with the next instruction you send.)
The provider-native context can't be transferred directly, so **codiva copies the retained user and assistant conversation into a one-time handoff** for the incoming agent. It also includes the branch name, the first and most recent instructions, and a reminder to verify the working tree with `git status` / `git diff`. Tool-execution logs are left out (they're bulky, and the working tree tells the same story). The newest conversation is prioritized, and an omission marker is shown if the handoff reaches its safety limit. No extra turn runs at switch time; the handoff rides along with the next instruction you send.

**You can always see what each session is running on.**

Expand Down
43 changes: 32 additions & 11 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ codiva/
│ │ ├── agent-events.ts # AgentEvent の語彙 + applyAgentEvent()(全 provider 共通の畳み込み・純粋)
│ │ ├── agent-capabilities.ts # capability による UI 縮退の判定(不明なら縮退しない・showsAccountInfo)
│ │ ├── agent-display.ts # 「どのセッションが何で走っているか」の判定(sessionAgentId / usesMultipleAgents)
│ │ ├── agent-handoff.ts # 切替先へ渡す状況説明(英語固定・systemPrompt に 1 回だけ載る)
│ │ ├── agent-handoff.ts # 切替先へ渡す状況説明 + 会話ログの写し(英語固定・切替後の最初の指示に 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 @@ -243,9 +243,10 @@ Claude のログインは env / 資格情報ファイルで分かるときだけ

| 引き継がれるもの | 引き継がれないもの |
|---|---|
| worktree・ブランチ・作業ツリーの内容 | モデル側の会話文脈(provider ごとに別のトランスクリプト) |
| worktree・ブランチ・作業ツリーの内容 | provider 固有のセッションそのもの(各 CLI が別のトランスクリプトを持つ) |
| codiva 側のログ(`messages`)・タイトル・PR・稼働時間 | `sdkSessionId`(切替先の `agentSessions` に無ければ undefined =新しい会話) |
| `agentSessions`(provider ごとの resume id) | `streamingText`(前のエージェントの途中表示) |
| 会話の中身(`messages` の user / assistant_text を写して渡す ⇒ 下記の引き継ぎ) | `streamingText`(前のエージェントの途中表示) |
| `agentSessions`(provider ごとの resume id) | ツール実行・system / error のログ行(量が大きく、作業ツリーを見れば足りる) |
| セッションの状態(`SessionStatus`)| `model`(解決済みモデルは provider ごとに別物。次のターンが埋める) |

戻ってきたときに続きから再開できるよう、`agentSessions: Partial<Record<AgentId, string>>` に
Expand All @@ -267,18 +268,38 @@ provider ごとの resume id を控え、**これは永続化する**(`state.j
セッションのログ行の形を変えないため(切替を使っていないユーザーには何も増えない)。
詳細ビューはこの帰属が変わる境界に区切り行(`── ここから Codex ──`)を 1 本挿む
(行の挿入は `core/scroll.ts` の `logLines(…, dividerFor)`、文言はカタログ + アダプタの表示名)。
- **引き継ぎの状況説明を 1 回だけ渡す**(`core/agent-handoff.ts` の `handoffInstruction`)。
切替先は前の会話を持たないので、何も渡さないと「途中まで作業された作業ツリー」を白紙から
見ることになり、済んだ作業をやり直したり直前の指示を無視したりする。ブランチ・最初の指示・
直前の指示を並べ、**続ける前に自分で `git status` / `git diff` を読む**よう促す文を
`AgentRunOptions.systemPrompt`(`composeSystemPrompt` の最後の節)に載せる。
- **引き継ぎを 1 回だけ渡す**(`core/agent-handoff.ts` の `handoffInstruction`)。切替先は
provider 固有の会話を持てないので、何も渡さないと「途中まで作業された作業ツリー」を白紙から
見ることになり、済んだ作業をやり直したり直前の指示を無視したりする。渡すのは
ブランチ・最初の指示・直前の指示に加えて、**codiva 側のログから写した会話そのもの**
(`handoffTranscript` = `user` / `assistant_text` だけ。ツール実行・system 行は落とす)と、
**続ける前に自分で `git status` / `git diff` を読む**よう促す文。
- **`AgentRunOptions.handoff` で渡し、アダプタが切替後の最初のユーザープロンプトに前置する**
(`attachHandoff`)。**systemPrompt には載せない** — `codex exec resume` のように再開時に
systemPrompt を読み直さない provider があり、往復切替でだけ引き継ぎが消える。
アダプタを増やすときは `request.options.handoff` の扱いを必ず実装する(番人は 3 つの
`*-adapter.spec.ts`)。
- **使い捨て**にする(`Session` が次の `open()` で消費する)。常設にすると、引き継ぎが済んだ
あとのターンや通信断からの再起動でも「前任者から引き継いだ」と言い続けることになる。
ただし**アダプタ側では「実際に provider へ渡るまで」持つ** — 立ち上げ前に中断された
ターン(Grok)や `thread.started` 前に落ちたターン(Codex)で捨てると、1 回きりの
引き継ぎを空振りで使い切ってしまう。
- **キューへ指示として積まない**。積むと切替直後に「状況を読むだけのターン」が 1 本走り、
provider のプロセスを無駄に立てる(ユーザーが次の指示を出すまで何も起こらないのが正しい)。
- 各項目は 1 行に畳んで `MAX_HANDOFF_FIELD_CHARS` で切る(指示文はファイルを丸ごと貼った
ものになりうるので、systemPrompt が本文より大きくなるのを防ぐ)。AI 向けの文字列なので
i18n カタログには置かない(英語固定。`SHARED_IGNORED_FILES_NOTICE` と同じ扱い)。
- 各項目は 1 行に畳んで `MAX_HANDOFF_FIELD_CHARS` で切り、会話は
**`MAX_HANDOFF_TRANSCRIPT_BYTES` = UTF-8 バイトの予算**で新しい方から詰める(切ったことは
1 行で明示する)。**文字数ではなくバイト数**なのは、Codex が指示文を argv で渡すため
(Linux の `MAX_ARG_STRLEN` = 131,072 バイト。日本語なら文字数の 3 倍になる。
docs/TECH_NOTES.md 参照)。
- **往復切替では重複を許す**。切替先が自分のスレッドを resume できるときはそのぶん文脈が
重なるが、resume が失敗した・圧縮で落ちた場合に「足りない」方が害が大きいので全部渡し、
重複が新しい指示ではないことは引き継ぎ文の中で断る。
- 引き継ぎは provider には**ユーザーメッセージ**として届くので CLI のトランスクリプトにも
そう残る。ログ復元(`core/transcript.ts`)は `stripHandoff` を通し、ユーザーが実際に
打った指示だけを積む(通さないと詳細ビューに巨大な引き継ぎが「ユーザー発言」として並び、
`lastUserInstruction` もそれを拾って次の引き継ぎが入れ子になる)。
- AI 向けの文字列なので i18n カタログには置かない(英語固定。`SHARED_IGNORED_FILES_NOTICE`
と同じ扱い)。

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

Expand Down
Loading
Loading