# 通知設計書 4-Question

## 概要

本ドキュメントは、OpenCodeアプリケーションにおける「Question（質問）」システム通知の設計を記述します。セッションでエージェントからユーザーへの質問が発生した際に、ユーザーに対してデスクトップシステム通知およびアプリ内トースト通知を表示する機能です。

### 本通知の処理概要

**業務上の目的・背景**：AIエージェントはタスク遂行中にユーザーからの追加情報や確認が必要になることがあります。ユーザーが他のアプリケーションで作業中の場合、質問を見逃すとエージェントの処理が停滞します。この通知により、ユーザーは即座に質問に気づき、適切な回答を提供できます。

**通知の送信タイミング**：SDKイベント `question.asked` が発生した時に送信されます。これはエージェントがユーザーへの質問を発したことを示します。

**通知の受信者**：現在OpenCodeアプリケーションを使用しているユーザー。ただし、設定画面で `settings.notifications.agent` がオフの場合はシステム通知は送信されません。アプリ内トーストは常に表示されます（ただし現在のセッション表示中を除く）。

**通知内容の概要**：通知タイトル「Question」と、「{セッションタイトル} in {プロジェクト名} has a question」という説明が表示されます。「Go to session」アクションで該当セッションに遷移できます。

**期待されるアクション**：ユーザーは通知を受け取った後、該当セッションに移動してエージェントからの質問を確認し、回答を入力することが期待されます。

## 通知種別

- デスクトップシステム通知（プラットフォームネイティブ通知）
- アプリ内トースト通知（persistent: 永続的）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（イベント駆動） |
| 優先度 | 中 |
| リトライ | なし |

### 送信先決定ロジック

1. `question.asked` イベントを受信
2. クールダウン期間（5秒）内の重複リクエストはスキップ
3. システム通知設定（settings.notifications.agent）が有効ならplatform.notify呼び出し
4. 現在のセッション・子セッションを表示中でなければトースト表示

## 通知テンプレート

### システム通知の場合

| 項目 | 内容 |
|-----|------|
| タイトル | `notification.question.title`（"Question"） |
| 説明 | `notification.question.description`（"{sessionTitle} in {projectName} has a question"） |
| リンク先 | `/{base64EncodedDirectory}/session/{sessionID}` |

### アプリ内トースト通知の場合

| 項目 | 内容 |
|-----|------|
| アイコン | "bubble-5" |
| タイトル | `notification.question.title`（"Question"） |
| 説明 | `notification.question.description` |
| 永続性 | persistent: true（手動dismissまで表示） |
| アクション1 | "Go to session" - セッションに遷移 |
| アクション2 | "Dismiss" - トーストを閉じる |

### 本文テンプレート

```
タイトル: Question
説明: {sessionTitle} in {projectName} has a question
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルは使用されません |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| sessionTitle | セッションのタイトル | session情報 または "New session" | Yes |
| projectName | プロジェクト名 | getFilename(directory) | Yes |
| sessionID | セッションの一意識別子 | イベントプロパティ | Yes |
| directory | プロジェクトディレクトリパス | イベント名 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| SDKイベント | `question.asked` | クールダウン期間外 | エージェントからの質問時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| クールダウン期間内 | 同一セッションで5秒以内に同じイベントがあった場合 |
| 現在表示中のセッション | ユーザーが該当セッションを閲覧中の場合（トーストのみスキップ） |
| 親セッション表示中 | ユーザーが該当セッションの親を閲覧中の場合（トーストのみスキップ） |
| settings.notifications.agent = false | システム通知のみスキップ |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[question.asked イベント受信] --> B{クールダウン期間内?}
    B -->|Yes| C[送信スキップ]
    B -->|No| D[クールダウン記録]
    D --> E{システム通知設定有効?}
    E -->|Yes| F[platform.notify呼び出し]
    E -->|No| G{現在のセッション?}
    F --> G
    G -->|Yes| C
    G -->|No| H[トースト表示]
    H --> I[終了]
    C --> I
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| session | セッション情報の取得 | SDK経由でアクセス |
| settings | 通知設定の確認 | ローカルストレージ |

### テーブル別参照項目詳細

#### session

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | セッション識別 | sessionID一致 |
| title | 通知説明文 | - |
| parentID | 親セッション判定 | - |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| toastBySession | SET | トーストID管理用Map |
| alertedAtBySession | SET | クールダウン管理用Map |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| セッション取得失敗 | セッションが存在しない | デフォルト値"New session"を使用 |
| 通知API失敗 | プラットフォームAPI呼び出し失敗 | エラーをキャッチして無視 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| クールダウン | 同一セッションで5秒間 |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし（24時間配信可能）

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

- セッションタイトルにはユーザーが入力した内容が含まれる可能性あり
- プロジェクト名はファイルパスから取得されるため、機密性の低い情報

## 備考

- Question通知はPermission通知と同じ処理フローを共有（アイコンとテキストのみ異なる）
- Question通知ではサウンド再生は行われない（Permission通知との差異）
- settings.notifications.agentで制御（Response readyと同じ設定）

---

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

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

### 推奨読解順序

#### Step 1: イベント処理を理解する

Permission通知との共通処理と差異を理解します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | layout.tsx | `packages/app/src/pages/layout.tsx` | イベントタイプ判定（361行目）でpermission.askedとquestion.askedの分岐を理解 |
| 1-2 | layout.tsx | `packages/app/src/pages/layout.tsx` | title/icon決定ロジック（362-366行目）で通知の差別化を理解 |

**読解のコツ**: question.askedの場合、iconは"bubble-5"、titleは"Question"になることに注目。

#### Step 2: 通知送信条件を理解する

Question固有の送信条件を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | layout.tsx | `packages/app/src/pages/layout.tsx` | question.askedの場合の処理（397-401行目）を確認 |

**主要処理フロー**:
1. **397-401行目**: question.askedではsettings.notifications.agent()で制御
2. **サウンド再生なし**: question.askedのブロックにはplaySound呼び出しがない

#### Step 3: i18nキーを理解する

通知テキストの多言語対応を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | en.ts | `packages/app/src/i18n/en.ts` | notification.question.title（477行目）を確認 |
| 3-2 | en.ts | `packages/app/src/i18n/en.ts` | notification.question.description（478行目）を確認 |

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

```
globalSDK.event.listen (layout.tsx:348)
    │
    └─ question.asked イベント
           │
           ├─ title = "Question" (layout.tsx:365)
           ├─ icon = "bubble-5" (layout.tsx:366)
           │
           ├─ クールダウン管理 (layout.tsx:383-386)
           │
           ├─ platform.notify (layout.tsx:397-401)
           │      └─ settings.notifications.agent() で制御
           │
           └─ showToast (layout.tsx:409-425)
                  ├─ アクション: navigate(href)
                  └─ アクション: dismiss
```

### データフロー図

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

SDKイベント               Layout - useSDKNotificationToasts
question.asked   ────▶   クールダウン チェック  ──────────▶   システム通知
  │                      settings.notifications.agent        アプリ内トースト
  │                           │
  ▼                           ▼
sessionID               icon = "bubble-5"
directory               title = "Question"
                        description = "{title} in {project} has a question"
                        showToast(persistent: true)
                        platform.notify()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| layout.tsx | `packages/app/src/pages/layout.tsx` | ソース | メイン処理・トースト管理 |
| platform.tsx | `packages/app/src/context/platform.tsx` | ソース | システム通知API |
| settings.tsx | `packages/app/src/context/settings.tsx` | ソース | 通知設定管理 |
| toast.tsx | `packages/ui/src/components/toast.tsx` | ソース | トーストコンポーネント |
| en.ts | `packages/app/src/i18n/en.ts` | 設定 | 翻訳キー定義 |
