# pi-auto-title

[English](./README.md) | 日本語

Pi Coding Agent のタブタイトルを管理する拡張機能です。AI が生成したセッションタイトルと、実行中/停滞中/待機中の状態表示をタブタイトルに反映します。

リポジトリ: [yuru7/my-pi-extensions](https://github.com/yuru7/my-pi-extensions)

```text
Pi - web-app                  (起動直後、エージェント実行前)
⏳ Pi - Lambdaフォーム公開API     (実行中)
⏸️ Pi - Lambdaフォーム公開API     (監視中のツールが長時間無更新)
✅ Pi - Lambdaフォーム公開API     (待機中)
```

複数の Pi セッションを並べて開くと、どのタブが何の作業中で、まだ実行中か一目で分かるようになります。タイトル部分はセッションの最初のユーザーメッセージから AI が生成し、それまでは(そして生成できない場合は)起動ディレクトリ名が表示されます。

## インストール

```bash
pi install npm:@yuru7/pi-auto-title
```

## タブタイトル

起動直後のタイトルは `Pi - <title>` です。エージェントが一度実行されると、先頭に状態表示が付いた `<status> Pi - <title>` 形式になります。

- `<status>` は実行中が `⏳`、監視中のツールが長時間無更新なら `⏸️`([停滞検出](#停滞検出)参照)、待機中が `✅` です。変更も無効化もできます([設定](#設定)参照)。表示はエージェントの初回実行後からのみ付きます。
- `<title>` は AI が生成したタイトルが存在すればそれを、なければ Pi を起動したディレクトリの末尾名です(`/home/user/projects/web-app` → `web-app`)。末尾名が得られないパス(ファイルシステムのルートなど)では `workspace` を使います。

AI タイトル部分は最大 30 ユニコード文字です。`Pi - ` と状態表示はこの 30 文字に含みません。ディレクトリ名はセッション開始時に確定し、その後の作業ディレクトリ変更では更新されません。

## AI タイトルの生成

- タイトルはセッションごとに 1 回だけ生成し、そのセッションの最初のユーザーメッセージから作ります。タイトルが付いたセッションが再度リクエストを送ることはないので、長いセッションでもコストが積み上がりません。
- 送信するのはその最初のユーザーメッセージだけです(最大 2000 文字)。ツール出力・ファイル内容・システムプロンプトは含みません。
- 生成されたタイトルは 30 文字に制限され、前後の引用符・Markdown 装飾・制御文字を除去して整形されます。空や使えない回答の場合はディレクトリ名のままにします。
- 生成は専用リクエストで行い、期限は全体(認証解決とフォールバックを含む)で 30 秒です。メインのエージェントは生成を待たず、結果は `<title>` 部分だけを更新し、状態表示には触れません。
- タイトルは `pi.appendEntry()` で Pi セッション内に保存され、セッション再開時に復元されます。復元時にも生成時と同じ検証を適用します。グローバルな別ファイルは作りません。

**プライバシー:** セッションの最初のユーザーメッセージは、タイトル生成用に設定したモデル(`autoTitle.model`)へ送信されます。これはサードパーティのプロバイダーになり得ます。Pi 本体の会話とは別のリクエストですが、本文に個人情報や機密情報が含まれる可能性があります。生成自体を止めるには `autoTitle.enabled` を `false` にしてください。

## 設定

設定ファイルは任意で、場所は `<エージェントディレクトリ>/pi-auto-title.json`(デフォルトは `~/.pi/agent/pi-auto-title.json`。ディレクトリは Pi の `getAgentDir()` に従い、`PI_CODING_AGENT_DIR` も反映)に固定です。ファイルがなければ以下のデフォルトで動作し、勝手に作られることはありません。未指定項目はデフォルトになり、明示的な `false` は保持され、未知の項目は無視されます。JSON が不正な場合は全体がデフォルトへフォールバックし、型が違う項目はその項目だけがデフォルトになります。そのため `autoTitle.enabled: false` のような意図的な設定は、無関係な項目の誤りがあっても保たれます。不正な項目名を列挙した警告を一度出し、ファイルの修復や上書きはしません。編集後は `/reload` で反映してください。

```json
{
  "autoTitle": {
    "enabled": true,
    "model": "CURRENT",
    "thinkingLevel": "CURRENT",
    "fallbackToCurrentModel": true
  },
  "statusIndicator": {
    "enabled": true,
    "running": "⏳",
    "idle": "✅",
    "stalled": "⏸️",
    "stallDetection": {
      "enabled": true,
      "thresholdSeconds": 30
    }
  }
}
```

### autoTitle

| キー | 型 | 初期値 | 説明 |
| --- | --- | --- | --- |
| `enabled` | boolean | `true` | AI タイトル生成の有効・無効 |
| `model` | string | `"CURRENT"` | タイトル生成に使うモデル |
| `thinkingLevel` | string | `"CURRENT"` | タイトル生成に使う思考量 |
| `fallbackToCurrentModel` | boolean | `true` | 指定モデルが使えない場合に現在のモデルへフォールバック |

`model` には `CURRENT`(生成時点のアクティブモデル)か、`anthropic/claude-sonnet-4-5` のような明示的な `provider/model-id` を指定します。Pi が認識でき、認証情報が使えるモデルのみ利用できます。

`thinkingLevel` には `CURRENT`(生成時点のアクティブな思考量)か、`off` `minimal` `low` `medium` `high` `xhigh` `max` などの Pi の思考量を指定します。対象モデルが指定された思考量に対応していない場合は、Pi と同じ正規化規則で最も近い値へ調整します(調整の通知は出ません)。タイトル生成がセッション側のモデルや思考量を変更することはありません。

### statusIndicator

| キー | 型 | 初期値 | 説明 |
| --- | --- | --- | --- |
| `enabled` | boolean | `true` | 状態表示の有効・無効 |
| `running` | string | `"⏳"` | 実行中の表示 |
| `idle` | string | `"✅"` | 待機中の表示 |
| `stalled` | string | `"⏸️"` | 停滞中の表示 |
| `stallDetection.enabled` | boolean | `true` | 実行中ツールの無更新監視の有効・無効 |
| `stallDetection.thresholdSeconds` | number | `30` | ツールが無更新になってから停滞表示に切り替わるまでの秒数 |

`running` と `idle` と `stalled` は指定どおりの文字列をそのまま使います。空文字列ならその状態では表示しません。`enabled: false` では状態表示を完全に省略します。`thresholdSeconds` には 1 以上の有限数を指定し、それ以外の値はデフォルトへフォールバックして一度警告を出します。

## 停滞検出

エージェントの実行中、ツール実行を `toolCallId` ごとに監視します。ツールの種類は問いません。あるツールが `stallDetection.thresholdSeconds` の間何もイベントを出さなければ、状態表示は `stalled` に切り替わります。進捗イベントが届けばそのツールの計測はリセットされ、`running` に戻ります。並列実行されるツールはそれぞれ独立に判定し、1つでも停滞していれば `stalled`、残っているツールの停滞がすべて解ければ `running` に戻ります。ツールを実行していないモデル推論中は停滞判定をしません。

停滞表示は無更新時間に基づく推定であり、確定した診断ではありません。長時間のビルド、ネットワーク待ち、ユーザー入力待ちのツールはどれも同じように見えます。停滞を検出してもキャンセルや再実行は一切行いません。

エージェントが完了・中断・エラーで落ち着いた時点で監視情報は破棄され、表示は `idle` に戻ります。停滞表示が次の実行や次のセッションに残ることはありません。`stallDetection.enabled: false` で従来どおり実行中/待機中の2状態になります。

## コマンド

```text
/pi-auto-title init
```

`init` は `/pi-auto-title ` 直後の引数補完に表示されます。

デフォルト設定を `<エージェントディレクトリ>/pi-auto-title.json` に出力します(必要な親ディレクトリも作成)。既存ファイルがある場合は選択ダイアログを出し、`No` が先頭の選択肢のため、Enter だけでは上書きされません。`No` や Esc では既存ファイルは変更されません。新しい設定は Pi の再起動なしで即座に反映され、生成済みのタイトルは維持されます。

引数なしの `/pi-auto-title` や、それ以外の引数では利用可能なコマンドを表示します。

## モデルのフォールバック

`fallbackToCurrentModel: true` の場合、指定モデルが未登録・認証情報なし・リクエスト失敗のときは、現在のセッションのモデルへフォールバックします。フォールバックは始まる時点で警告文を会話履歴上にカスタムエントリとして表示します。カスタムエントリは LLM コンテキストに入らないため、エージェントに渡ることはありません。両モデル名と安全な理由区分(model not registered・no credentials・timed out・call failed)を表示します。フォールバックは最大 1 回です。思考量の調整はモデル名と設定値を、生成の断念は固定の理由区分のみを、同じく会話履歴の警告エントリとして表示します。プロバイダーの生エラー本文は表示も保存もされず、stderr にも書き込みません。

フォールバックが無効な場合や、フォールバックでも成功しない場合は、そのセッションの生成を諦めます。タブタイトルにはディレクトリ名が残り、エージェントの動作は通常どおり続行されます。

## 動作メモ

- タブタイトルは対話モードと RPC モードで設定します。UI のないモード(JSON・print)では、最初の `session_start` で拡張機能全体を無効化し、全イベントハンドラを解除します。以降はタイトル表示・停滞監視・AI 生成とも一切動作しません。
- Pi 本体は拡張機能のバインド直後に自前のタイトル(`π - <dir>`)を設定します。当拡張機能は、起動時・セッション名変更時・ツリー移動時に短い遅延(即時・250ms・1s)で自前のタイトルを再適用し、さらに次のプロンプト入力でも再適用します。Pi 本体の書き込みを知らせるイベントは無いため、この維持は保証ではなくベストエフォートです。
- Pi の終了時はタブタイトルを空文字列に設定します。Pi 本体は拡張機能に終了を通知した直後に UI の後始末で自前のタイトルをもう一度設定するため、プロセス終了直前に空文字列を改めて書き込み、最後のタイトル書き込みを空にします。ターミナルによってはデフォルト表示に戻り、空欄のままになるものもあります。元のタイトルは読み取れないため、それ自体を復元することはできません。設定するのは通常終了時のみで、`/reload` やセッション切り替えでは次のセッションのためにタイトルを残します。強制終了時は何も実行されません。
- `/reload` で設定を再読み込みします。セッション切り替え・フォーク・ツリー移動・リロードでは生成中のリクエストをキャンセルし、それ以降に届いた結果は破棄します。
- タイトル生成の失敗がエージェントの実行を失敗させることはありません。タイトル部分は従来の値(通常はディレクトリ名)のままで、理由はフォールバックと同じく会話履歴の警告エントリとして表示されます。

## 開発

```bash
pnpm install
pnpm run check    # 型検査・lint・テスト
```

テストは `node:test`、lint とフォーマットは Biome を使います。

## ライセンス

[MIT](./LICENSE)
