# 機能設計書 45-WebSocket サポート

## 概要

本ドキュメントは、OpenCodeにおけるWebSocketサポート機能の設計を記述する。双方向リアルタイム通信により、主にPTY（疑似ターミナル）セッションとの対話的な接続を提供する。

### 本機能の処理概要

WebSocketサポート機能は、HTTP APIサーバーにおいてWebSocketプロトコルを通じた双方向通信を実現する。主な用途はPTYセッションへのリアルタイム接続であり、ターミナル入力の送信とターミナル出力の受信を同時に行う。Hono の upgradeWebSocket 機能とBunのWebSocket実装を組み合わせて提供する。

**業務上の目的・背景**：AIエージェントがBashコマンドを実行する際、その出力をリアルタイムで確認したり、対話的なコマンド（viエディタ、REPLなど）を実行する必要がある。WebSocketにより、ターミナルセッションとの完全な双方向通信が可能になり、ローカルターミナルと同等の操作性を提供できる。また、Web UIやデスクトップアプリでのターミナルパネル実装に必要不可欠な機能である。

**機能の利用シーン**：
- ターミナルパネルでのコマンド実行とリアルタイム出力表示
- 対話的なシェルセッション（vi、less、top等）
- 長時間実行コマンドの出力監視
- 複数クライアントからの同一PTYセッションへの接続

**主要な処理内容**：
1. WebSocket接続のアップグレードリクエスト処理
2. PTYセッションとの接続確立
3. クライアントからの入力をPTYに転送
4. PTYからの出力をクライアントに転送
5. バッファリングと再接続時のデータ復元
6. 切断処理とリソースクリーンアップ

**関連システム・外部連携**：
- Hono upgradeWebSocket: WebSocketアップグレード
- bun-pty: 疑似ターミナル実装
- Bun WebSocket: WebSocket実装

**権限による制御**：HTTP APIサーバーと同じ認証・CORS制限が適用される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | セッション画面 | 補助機能 | TerminalPanelコンポーネントによるターミナル表示 |

## 機能種別

リアルタイム通信 / 双方向通信

## 入力仕様

### 接続パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| ptyID | string | Yes | 接続対象のPTYセッションID | pty_ プレフィックス形式 |
| cursor | number | No | 再接続時のバッファ位置（-1で末尾から） | 整数値または-1 |

### WebSocketメッセージ

| 方向 | 型 | 説明 |
|-----|-----|------|
| クライアント→サーバー | string | ターミナル入力文字列 |
| サーバー→クライアント | string/Uint8Array | ターミナル出力 |

### 入力データソース

- HTTPリクエスト（GET /pty/:ptyID/connect）
- WebSocketメッセージ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| data | string/Uint8Array | ターミナル出力データ |
| meta | Uint8Array | 制御フレーム（カーソル位置情報等） |

### メタデータ形式

```typescript
// 制御フレーム: 0x00 + UTF-8 JSON
meta = [0x00, ...JSON.stringify({cursor: number})]
```

### 出力先

- WebSocketフレーム

## 処理フロー

### 処理シーケンス

```
1. 接続要求
   ├─ GET /pty/:ptyID/connect 受信
   ├─ PTYセッション存在確認
   └─ WebSocket接続にアップグレード

2. 接続確立（onOpen）
   ├─ ソケットID付与
   ├─ 前回のセッション登録解除（同一ソケットの場合）
   ├─ 新しいセッションに登録
   └─ バッファデータ送信（再接続時）

3. メッセージ受信（onMessage）
   └─ PTYプロセスにデータ書き込み

4. データ配信
   ├─ PTY onData コールバック
   ├─ 全購読者にデータ送信
   └─ バッファに追加（2MB制限）

5. 切断（onClose/onError）
   └─ 購読者リストから削除
```

### フローチャート

```mermaid
flowchart TD
    A[GET /pty/:ptyID/connect] --> B{PTYセッション存在?}
    B -->|No| C[エラー: Session not found]
    B -->|Yes| D[WebSocketアップグレード]
    D --> E[onOpen]
    E --> F[ソケットID付与]
    F --> G[セッションに購読者登録]
    G --> H{cursor指定あり?}
    H -->|Yes| I[バッファから履歴送信]
    H -->|No| J[全バッファ送信]
    I --> K[カーソルメタデータ送信]
    J --> K
    K --> L{メッセージ受信?}
    L -->|Yes| M[PTYに書き込み]
    M --> L
    L -->|切断| N[購読者リストから削除]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-45-01 | セッション存在確認 | 存在しないPTYセッションへの接続は拒否 | 接続要求時 |
| BR-45-02 | バッファ制限 | バッファサイズは2MB (BUFFER_LIMIT) を超えない | データ受信時 |
| BR-45-03 | チャンク分割 | 送信時は64KB (BUFFER_CHUNK) 単位で分割 | データ送信時 |
| BR-45-04 | カーソル復元 | cursor=-1 で末尾から、cursor指定で指定位置から | 再接続時 |
| BR-45-05 | 重複購読防止 | 同一ソケットの二重購読を防止 | 接続確立時 |

### 計算ロジック

バッファ管理：
```typescript
BUFFER_LIMIT = 1024 * 1024 * 2  // 2MB
BUFFER_CHUNK = 64 * 1024        // 64KB

// バッファオーバーフロー時の処理
if (buffer.length > BUFFER_LIMIT) {
  const excess = buffer.length - BUFFER_LIMIT
  buffer = buffer.slice(excess)
  bufferCursor += excess
}
```

## データベース操作仕様

本機能はデータベースを直接操作しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Session not found | PTYセッションが存在しない | WebSocket接続を拒否 |
| - | 書き込み失敗 | クライアントへの送信失敗 | 購読者リストから削除 |
| - | 接続切断 | クライアントが切断 | onClose でクリーンアップ |

### リトライ仕様

サーバー側でのリトライは実装されていない。クライアント側でcursor指定の再接続を実装。

## トランザクション仕様

WebSocketメッセージはトランザクション管理なし。各メッセージは独立して処理される。

## パフォーマンス要件

- 低遅延のリアルタイム通信
- バッファサイズ2MBで履歴保持
- 複数クライアントへの同時配信

## セキュリティ考慮事項

- HTTP APIサーバーと同じ認証が適用
- ソケットIDによる接続追跡
- トークンベースの購読者検証

## 備考

- readyState === 1 で接続状態確認
- 制御フレーム（0x00プレフィックス）でメタデータ送信
- PTY終了時は全購読者のWebSocket接続を自動クローズ

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

PTYセッションとWebSocket接続の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/opencode/src/pty/index.ts` | Info型定義（95-106行目）、ActiveSession型（138-145行目） |

**読解のコツ**: ActiveSession はPTYプロセス、バッファ、購読者マップを保持。Subscriber はソケットIDとトークンで一意識別。

#### Step 2: エントリーポイントを理解する

WebSocketエンドポイントの実装を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | pty.ts | `packages/opencode/src/server/routes/pty.ts` | /pty/:ptyID/connect エンドポイント（133-199行目） |

**主要処理フロー**:
1. **133-150行目**: エンドポイント定義
2. **151-152行目**: upgradeWebSocket でアップグレード
3. **153-161行目**: cursor パラメータのパース
4. **178-185行目**: onOpen - Pty.connect 呼び出し
5. **186-189行目**: onMessage - ハンドラーに転送
6. **190-196行目**: onClose/onError - クリーンアップ

#### Step 3: PTY接続管理を理解する

PTYモジュールでの接続管理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/opencode/src/pty/index.ts` | connect関数（320-389行目） |
| 3-2 | index.ts | `packages/opencode/src/pty/index.ts` | tagSocket, token関数（35-78行目） |

**主要処理フロー**:
- **320-333行目**: セッション存在確認とソケットID付与
- **334-345行目**: 前回セッションの購読解除と新規登録
- **347-359行目**: バッファからの履歴データ取得
- **361-371行目**: 履歴データのチャンク送信
- **373-379行目**: メタデータ（カーソル位置）送信
- **380-389行目**: onMessage/onClose ハンドラー返却

#### Step 4: データ配信を理解する

PTYからのデータ配信メカニズムを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.ts | `packages/opencode/src/pty/index.ts` | create関数内のonData（224-255行目） |

**主要処理フロー**:
- **224行目**: ptyProcess.onData コールバック設定
- **225行目**: cursor位置更新
- **227-247行目**: 全購読者への配信（状態確認付き）
- **250-255行目**: バッファ追加と制限処理

### プログラム呼び出し階層図

```
GET /pty/:ptyID/connect
    │
    └─ upgradeWebSocket()
           │
           ├─ onOpen
           │      └─ Pty.connect(id, ws, cursor)
           │             ├─ tagSocket(ws)
           │             ├─ session.subscribers.set(ws, {...})
           │             ├─ 履歴データ送信（チャンク分割）
           │             └─ メタデータ送信
           │
           ├─ onMessage
           │      └─ handler.onMessage(data)
           │             └─ session.process.write(data)
           │
           └─ onClose
                  └─ handler.onClose()
                         └─ session.subscribers.delete(ws)
```

### データフロー図

```
[入力]               [処理]                    [出力]

クライアント入力
       │
       ▼
WebSocket.onMessage
       │
       ▼
session.process.write() ───▶ PTYプロセス
                                  │
                                  ▼
                            ptyProcess.onData()
                                  │
                                  ▼
                            subscribers.forEach() ───▶ WebSocket送信
                                  │
                                  ▼
                            バッファ追加
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| pty.ts | `packages/opencode/src/server/routes/pty.ts` | ソース | WebSocketエンドポイント |
| index.ts | `packages/opencode/src/pty/index.ts` | ソース | PTY管理とWebSocket接続 |
| server.ts | `packages/opencode/src/server/server.ts` | ソース | WebSocket設定（36, 589行目） |
