matan-bridge
v0.1.33
Published
まーたんコンパニオン — ローカルブリッジサーバー。Claude Code CLIとブラウザUIを接続します。
Maintainers
Readme
matan-bridge
まーたんWebコンパニオンのローカルブリッジサーバー。 ブラウザとClaude Code CLIの間を中継し、リアルタイム会話・TTS音声合成を提供する。
クイックスタート
npx matan-bridge初回実行時にClaude Code CLIのインストール確認・ログイン確認・~/matan-home/のセットアップが自動で行われる。
アーキテクチャ
ブラウザ (matan.app / localhost:5174)
│
│ WebSocket (port 17619)
▼
matan-bridge (Express + WebSocket)
├── /health ... ヘルスチェック
├── /api/tts ... Fish Audio TTSプロキシ(ローカル開発用)
└── WebSocket
├── chat:send ... ユーザーメッセージ → Claude CLI
├── matan:speech ... まーたんの応答(ストリーミング)
└── matan:stream-end ... 応答完了
│
│ stdin/stdout (NDJSON)
▼
Claude Code CLI (--print --input-format stream-json)
CWD: ~/matan-home/開発セットアップ
cd matan-bridge
npm install環境変数
.env ファイルを作成:
# Fish Audio TTS(音声合成に必要)
FISH_AUDIO_API_KEY=your_api_key
FISH_AUDIO_MODEL_ID=your_model_id起動
# 開発(ホットリロード)
npm run dev
# 本番
npm start初回起動時にClaude CLIの永続プロセスが起動する(約25秒)。 2回目以降のメッセージは5-8秒で応答。
~/matan-home/
Claude CLIのCWD。初回起動時に自動作成される。
~/matan-home/CLAUDE.md にまーたんのペルソナが定義される。
~/matan-home/cheer/ (v4)
cheer-generator (応援メッセージ生成用 Claude CLI 子プロセス) の CWD。
Claude Code のCLAUDE.md 階層読み込みで ~/matan-home/CLAUDE.md が自動注入される。
Cheer v4 設計
応援メッセージ機能は v4 で抜本リファクタされた。詳細設計は
ai-vtuber-context-cheer/docs/designs/cheer-2channel-design-v4.md 参照。
主要変更点
- 立ち位置: Claude 応援団 → 作業者(Claude/ユーザー)を横から見守る第三者
- トリガー(CH1):
session_start/assistant_done連打削除 → user_input + 2分CDハイブリッド - トリガー(CH2): 新設。jsonl監視ベースの 4状態遷移(IDLE/WORKING/NEEDS_ATTENTION/COMPLETED)
- 文脈: 30分循環バッファ → 5分スライディング窓 + compaction summary(1500字clip) + Supabase story_event_logs の3層注入
- チャンネル: 旧
cheer:set-mode→channel:toggleで CH1/CH2 個別ON/OFF - プロンプト: B9プロンプト v3(XMLタグ/反応多軸化/観察→褒め連結/物語touch厳格化/固有名排除)
- 生成(CH1): 50/50 AI/テンプレ → AI100%, 失敗時のみfallbackテンプレ11行
- 生成(CH2): 速度優先でテンプレ主体(completed 5行 + permission 4行、AI生成なし)
Phase 4 アーキテクチャ(God Class 分割後)
v4 初期実装の CheerMonitor は責務10個を抱えた God Class だったため、 Phase 4 で以下の4コンポーネントに分離した。各コンポーネントは単一責任で独立テスト可能。
CheerMonitor (router / bootstrap / session exclude, 薄い facade)
├─ ContextCollector — 3層文脈集約
│ ├─ SlidingWindowBuffer (短期: 5分)
│ ├─ latestCompactionSummary (中期: 1500字 clip)
│ └─ StoryEventsLoader (長期: Supabase + TTL 5分キャッシュ)
├─ CheerFirer — CH1 発火(AI 生成 + fallback + 2分CD + mutex + 履歴)
│ └─ CheerGenerator (Claude CLI 永続プロセス、--tools "" で安全サンドボックス)
└─ NotifyFirer — CH2 発火(state machine + 5秒 timeout scan)
└─ ChannelStateMachine (4状態 + TOOL_TIMEOUT_MS)
Shared utility:
JsonlParser (static) — parseTimestamp / extractUserText / extractAssistant
/ hasToolResult / isCompactionSummaryCheerMonitor は以下の薄い責務だけ残す:
- preloadContext: 起動時に既存 jsonl(直近5分)を ContextCollector に注入
- onJsonlEvent: chokidar から届く jsonl 行を各コンポーネントに配信
- isMatanSession: matan-home 配下のセッションを除外(無限ループ防止)
- purgeTimer: 1時間毎の stale state 掃除
グローバル汚染ゼロ原則
cheer 状態検出は ~/.claude/projects/*/*.jsonl の読み取り監視のみ。
~/.claude/settings.json や <project>/.claude/settings.json への書き込みは一切行わない。
先人 OSS (c9watch, claude-watch-status) の jsonl 監視アプローチに準拠。
CH2 (完了通知)
Phase 2 で実装済み。ChannelStateMachine がプロジェクト単位で以下 4 状態を管理する:
| 状態 | 遷移条件 |
|---|---|
| IDLE | 起動直後(never touched) |
| WORKING | user_input 検知 or tool_use 検知 |
| NEEDS_ATTENTION | tool_use から TOOL_TIMEOUT_MS 経過しても tool_result なし |
| COMPLETED | assistant stop_reason='end_turn' |
TOOL_TIMEOUT_MS マップ
| ツール | timeout | |---|---| | TodoWrite | 5s | | Read / Write / Edit / Bash / Glob / Grep | 10s | | WebSearch / WebFetch | 60s | | Task | 3min | | (未登録ツール) | 30s (DEFAULT) |
発火ルール
- WORKING → COMPLETED 遷移で
'completed'通知(fx_lv=1) - WORKING → NEEDS_ATTENTION 遷移で
'permission'通知(fx_lv=2) NOTIFY_COOLDOWN_MS = 3分— 同一プロジェクトの連続通知抑止(初回は即許可)- 優先度: 同一 batch で notify + cheer の発火候補がある場合は notify 優先、cheer は skip
- notifyTimer 5秒間隔で
runTimeoutScan→checkTimeouts()→ permission 通知
通知の主語
CH2 の全通知文は 「claudeが」 を主語として固定。 作業者(Claude)の状態を第三者が観察して告げる体裁。
通知テンプレ(抜粋)
- completed:
claudeが仕事終わらせたぞ、見てみろよ/claudeが返事してるぞ、{nickname}の番だ - permission:
claudeが止まってるな、許可してやれよ/claudeが固まってるぞ、{nickname}の一押しが必要だ
長期コンテキスト (Phase 3: story_event_logs Supabase 実接続)
B9 プロンプトの <long_term_user_context> に注入する StoryEvent[] は
StoryEventsLoader が Supabase story_event_logs テーブルから動的に取得する。
| 要素 | 内容 |
|---|---|
| テーブル | public.story_event_logs(RLS有効) |
| カラム | user_id / event_level / conversation jsonb / summary / created_at |
| 取得パス | /rest/v1/story_event_logs?select=...&order=created_at.desc&limit=5 |
| キャッシュ | TTL 5分 (同一ユーザーなら 5分間は fetch を再実行しない) |
| 認証 | auth:token 受信時に setAuth(userId) を bridge 側が呼び出し |
| ユーザー切替 | setAuth で userId 変化を検知 → キャッシュ自動破棄 |
Fallback 動作
以下いずれの場合も STORY_EVENTS_STUB (5件の開発用固定値) に自動フォールバックする:
- 未認証(
auth:token未送信 / 未検証) - Supabase fetch エラー (5xx / network error)
- fetch 401 (トークン期限切れ)
- レスポンスが空配列(ユーザーに
story_event_logs未蓄積)
長期コンテキスト欠落で B9 プロンプトの物語 touch が空になる事態を防ぐことが目的。 実ユーザーに story_event_logs が蓄積されるまでは stub で B9 の品質を担保する。
DB 行 → StoryEvent 変換ルール
event_level → level
created_at → createdAt (YYYY-MM-DD に丸め)
summary → summary (null なら conversation.topic、両方空なら "LvX の物語")web-companion との接続
- matan-bridge を起動(port 17619)
- web-companion を起動(
npm run dev、port 5173/5174) - Vite が
/api/ttsと WebSocket を自動的に matan-bridge にプロキシ - ブラウザで
http://localhost:5174/を開く
