# 機能設計書 60-Slack統合

## 概要

本ドキュメントは、OpenCodeのSlack統合機能について、その設計仕様を記述するものである。

### 本機能の処理概要

Slack統合機能は、SlackチャンネルからOpenCodeを利用するためのボット統合を提供する。Slackのメッセージイベントを受信し、OpenCodeのセッションを通じてAI応答を返す。スレッドごとにセッションを管理し、継続的な会話を可能にする。

**業務上の目的・背景**：チーム開発において、Slack上でコードレビューやAI支援を受けたいニーズがある。Slack統合により、チャットツールから離れることなくOpenCodeの機能を利用でき、チームコラボレーションが促進される。

**機能の利用シーン**：
- Slackチャンネルでのコードレビュー依頼
- チーム内でのAIアシスタントへの質問
- プログラミング関連の議論中のAI支援
- スレッド単位での継続的な会話

**主要な処理内容**：
1. Slack Bolt App初期化（Socket Mode）
2. OpenCodeサーバー起動（createOpencode）
3. メッセージイベント受信とセッション管理
4. OpenCode APIを通じたプロンプト送信
5. AI応答のSlackへの投稿
6. ツール実行状況のリアルタイム通知

**関連システム・外部連携**：Slack API、OpenCode SDK、WebSocket

**権限による制御**：Slack Bot Token、Signing Secret、App Tokenによる認証

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | Slackクライアント | 主機能 | メッセージ送受信 |

## 機能種別

外部連携 / チャットボット統合

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| SLACK_BOT_TOKEN | string | Yes | Slackボットトークン | 環境変数必須 |
| SLACK_SIGNING_SECRET | string | Yes | Slack署名シークレット | 環境変数必須 |
| SLACK_APP_TOKEN | string | Yes | Slackアプリトークン（Socket Mode用） | 環境変数必須 |

### 入力データソース

- Slackメッセージイベント（message.text）
- スレッド情報（thread_ts）
- チャンネル情報（channel）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| responseText | string | AI応答テキスト |
| toolMessage | string | ツール実行状況メッセージ |
| sessionUrl | string | 共有セッションURL |

### 出力先

- Slackスレッド（chat.postMessage）

## 処理フロー

### 処理シーケンス

```
1. 初期化
   ├─ Slack Bolt App初期化（Socket Mode）
   └─ OpenCodeサーバー起動（createOpencode）

2. イベント購読
   └─ opencode.client.event.subscribe()
      └─ message.part.updatedイベント監視

3. メッセージ受信
   ├─ subtype/text存在チェック
   └─ セッションキー生成（channel-thread）

4. セッション管理
   ├─ 既存セッションあり → セッション再利用
   └─ 新規 → session.create + session.share

5. プロンプト送信
   └─ session.prompt({ parts: [{ type: "text", text }] })

6. 応答送信
   └─ say({ text: responseText, thread_ts })

7. ツール更新通知
   └─ handleToolUpdate → chat.postMessage
```

### フローチャート

```mermaid
flowchart TD
    A[Slackメッセージ受信] --> B{subtype/text確認}
    B -->|無効| C[スキップ]
    B -->|有効| D[セッションキー生成]
    D --> E{既存セッション?}
    E -->|No| F[session.create]
    F --> G[session.share]
    G --> H[セッションURL投稿]
    E -->|Yes| I[セッション再利用]
    H --> I
    I --> J[session.prompt]
    J --> K{エラー?}
    K -->|Yes| L[エラーメッセージ投稿]
    K -->|No| M[レスポンス構築]
    M --> N[say投稿]

    O[イベントストリーム] --> P{message.part.updated?}
    P -->|Yes| Q{ツール完了?}
    Q -->|Yes| R[ツール状況投稿]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | セッションキー | channel-threadをキーとしてセッション管理 | メッセージ受信時 |
| BR-02 | スレッド継続 | 同一スレッドでは同一セッションを再利用 | スレッド内メッセージ時 |
| BR-03 | subtype除外 | subtypeを持つメッセージ（編集等）は無視 | メッセージ受信時 |
| BR-04 | ツール通知 | ツール完了時のみ状況を通知 | status === "completed"時 |
| BR-05 | セッション共有 | 新規セッション作成時にURLを共有 | セッション作成時 |

### 計算ロジック

- セッションキー生成: `${channel}-${thread}`
- スレッドTS: `(message as any).thread_ts || message.ts`

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

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| session.create | sessions | INSERT | OpenCodeセッション作成 |
| session.prompt | messages | INSERT | メッセージ送信 |
| session.share | shares | INSERT | セッション共有 |

### テーブル別操作詳細

OpenCode APIを通じた間接操作。Slack統合自体はインメモリでセッション管理（Map）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | セッション作成失敗 | session.create失敗 | エラーメッセージをSlackに投稿 |
| - | プロンプト送信失敗 | session.prompt失敗 | エラーメッセージをSlackに投稿 |
| - | ツール更新投稿失敗 | chat.postMessage失敗 | サイレント失敗（.catch(() => {})） |

### リトライ仕様

リトライは行わない。エラー時はユーザーに再試行を促すメッセージを返す。

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

該当なし（各API呼び出しは独立）

## パフォーマンス要件

- メッセージ応答: OpenCode API応答時間に依存
- イベントストリーム: WebSocket常時接続

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

- 環境変数によるトークン管理（SLACK_BOT_TOKEN等）
- Socket Modeによるセキュアな接続
- Signing Secretによるリクエスト検証

## 備考

- パッケージ名: @opencode-ai/slack
- 依存: @slack/bolt ^3.17.1、@opencode-ai/sdk
- 動作モード: Socket Mode（WebSocket接続）
- セッション管理: インメモリMap

---

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

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

### 推奨読解順序

#### Step 1: パッケージ構成を理解する

依存関係を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | package.json | `packages/slack/package.json` | 依存パッケージ（@slack/bolt、@opencode-ai/sdk） |

**読解のコツ**:
- @slack/bolt: Slack公式のBoltフレームワーク
- @opencode-ai/sdk: OpenCode SDK

#### Step 2: 初期化処理を理解する

アプリケーション初期化を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/slack/src/index.ts` | App初期化、createOpencode |

**主要処理フロー**:
- **1-2行目**: import（@slack/bolt、@opencode-ai/sdk）
- **4-9行目**: Slack App初期化（Socket Mode）
- **17-19行目**: OpenCodeサーバー起動（port: 0で自動割当）

#### Step 3: セッション管理を理解する

セッション管理とイベント購読を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/slack/src/index.ts` | sessions Map、event.subscribe |

**主要処理フロー**:
- **22行目**: `sessions` Map定義
- **23-39行目**: イベント購読ループ
- **26-36行目**: message.part.updatedイベント処理

#### Step 4: メッセージハンドラを理解する

メッセージ受信処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.ts | `packages/slack/src/index.ts` | app.message handler |

**主要処理フロー**:
- **58-136行目**: `app.message`ハンドラ
- **61-64行目**: subtype/textバリデーション
- **68-70行目**: セッションキー生成
- **74-102行目**: セッション作成/再利用
- **105-108行目**: session.promptでプロンプト送信
- **124-130行目**: レスポンステキスト構築
- **135行目**: Slackへの応答投稿

#### Step 5: ツール更新通知を理解する

ツール実行状況の通知処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | index.ts | `packages/slack/src/index.ts` | handleToolUpdate関数 |

**主要処理フロー**:
- **41-51行目**: `handleToolUpdate`関数
- **42行目**: completed状態チェック
- **43行目**: ツールメッセージ構築
- **44-50行目**: Slackへの投稿

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

```
起動時
    │
    ├─ new App({ socketMode: true }) (index.ts: 4行目)
    │      └─ 環境変数からトークン取得
    │
    ├─ createOpencode({ port: 0 }) (index.ts: 17行目)
    │      └─ OpenCodeサーバー起動
    │
    ├─ opencode.client.event.subscribe() (index.ts: 24行目)
    │      └─ イベントストリーム購読
    │
    └─ app.start() (index.ts: 144行目)

メッセージ受信時
    │
    ├─ app.message(handler) (index.ts: 58行目)
    │      │
    │      ├─ セッションキー生成: `${channel}-${thread}`
    │      │
    │      ├─ sessions.get(sessionKey)
    │      │      │
    │      │      └─ (なければ) client.session.create()
    │      │             │
    │      │             └─ client.session.share()
    │      │
    │      ├─ session.client.session.prompt()
    │      │
    │      └─ say({ text, thread_ts })
    │
    └─ イベントストリームループ
           │
           └─ handleToolUpdate() (index.ts: 41行目)
                  │
                  └─ app.client.chat.postMessage()
```

### データフロー図

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

Slackメッセージ
  message.text ─────────▶ app.message(handler) ────────▶ セッションキー
     │                          │
     │                          ▼
スレッド情報               sessions.get/set
  thread_ts ──────────────▶ Map<key, session>
     │                          │
     │                          ▼
                          session.prompt()
                                │
                                ▼
OpenCode応答             レスポンス構築
  result.data ───────────▶ response.parts.filter ─────▶ responseText
     │                          │
     │                          ▼
                          say({ text, thread_ts })
                                │
                                ▼
                          Slackスレッド投稿

イベントストリーム
  message.part.updated ──▶ handleToolUpdate() ─────────▶ ツール状況投稿
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/slack/src/index.ts` | ソース | Slack統合エントリーポイント |
| package.json | `packages/slack/package.json` | 設定 | パッケージ定義 |
| index.ts | `packages/sdk/js/src/index.ts` | ソース | SDK（createOpencode） |
