Skip to content

監視ダッシュボード (dashboard)

ブラウザでチーム状況を一望する read-only サーバーです。Node の標準ライブラリのみで実装され、外部 npm 依存はゼロ、LLM も一切呼びません

設計の柱:read-only と「許可リスト方式」

サーバーの安全性は「禁止を列挙する」のではなく「許可を列挙する」アプローチで担保されています。server.js の入口で 3 つのゲートを通します。

① メソッドのゲート

既定は GET/HEAD のみ。状態を変える POST は MUTATING_PATHS に列挙した 3 つだけ許可します。

js
const MUTATING_PATHS = ['/api/archive-session', '/api/restore-session', '/api/pin-session'];

function isMethodAllowed(method, pathname) {
  if (method === 'GET' || method === 'HEAD') return true;
  return method === 'POST' && MUTATING_PATHS.includes(pathname);
}

しかもこの 3 つの変更操作すら「ファイルを移動するだけ・中身は不変・完全に可逆」に限定されており、LLM 非依存です。

② Host ヘッダのゲート

config.jsonallowedHosts に含まれない Host からのアクセスは 403 で弾きます(DNS リバインディング等への最小防御)。ポートは無視して比較します。

js
function isHostAllowed(hostHeader, allowedHosts) {
  if (!allowedHosts || allowedHosts.length === 0) return true;
  if (!hostHeader) return false;
  const hostname = String(hostHeader).split(':')[0].toLowerCase();
  return allowedHosts.map((h) => String(h).toLowerCase()).includes(hostname);
}

WARNING

別の PC やスマホ(例:Tailscale 経由)から見る場合は、host0.0.0.0 にした上で allowedHosts にアクセス元のホスト名/IP を必ず追加する必要があります。未追加だと 403 で弾く安全側の挙動です。

③ 静的ファイルのゲート

配信できる静的ファイルも PUBLIC_FILES で固定列挙(/, /index.html, /README.md, /demo.html)。任意パスは配信しません。ファイル読み取り系のヘルパ resolveSafe() は、解決後の絶対パスが基準ディレクトリの外に出るとエラーを投げ、ディレクトリトラバーサルを防止します。

設定はリクエスト毎に読み直す

config.jsonリクエストごとに readConfig() で読み直されます。これにより、サーバーを再起動せずにチーム名や許可ホストの変更を反映できます(ただし listen 中の port/host だけは起動時に固定)。

js
const server = http.createServer(async (req, res) => {
  const config = readConfig(CONFIG_PATH); // 毎リクエストで読み直し、再起動なしで設定変更を反映
  ...
});

クライアントへ返す設定は sanitizePublicConfig()公開してよい範囲だけに絞られます。serverOnly(パス・スクリプト・起動コマンド等)は一切含めず、agentslabel/color だけに削ぎ落とします。

API 一覧(すべて read-only、一部 POST)

lib/api.jsROUTES テーブルでルーティングします。

エンドポイント内容メソッド
/api/config公開用にサニタイズした設定GET
/api/taskstasks/*.md を読み取りGET
/api/projectsproject_*.md の一覧GET
/api/skillsskillsDir 配下の SKILL.md 一覧GET
/api/agentstmux ペインの稼働状況GET
/api/sessionsresume 可能なセッション一覧(新しい順)GET
/api/archiveアーカイブ済みセッション一覧GET
/api/memory-search記憶(FTS5)横断検索GET
/api/session-search本物のセッションに絞った全文検索GET
/api/log-search全 transcript を grepGET
/api/docs許可リスト一致のドキュメントのみ読むGET
/api/archive-sessionセッションをアーカイブへ移動POST
/api/restore-sessionアーカイブから復元POST
/api/pin-sessionピン留めのトグルPOST
/api/health死活確認GET

各データソースの読み取りは lib/sources.js に集約され、どれも「ファイルを読む / 既存 CLI を呼ぶ / grep を呼ぶ」だけで課金が乗る経路を持ちません。

  • 記憶検索:index_memory.py search --jsonexecFile で呼ぶ。
  • 生ログ検索:grep -rIiF -m1 で全 transcript を検索(exit code 1 = 該当なしを正常扱い)。
  • tmux:tmux list-panes を 3 秒タイムアウトで呼び、無ければ空配列。

セッション発見ロジックの妙:lib/sessions.js

ダッシュボードの目玉は「散らばった transcript から 本物のセッションを見つけて一覧・検索する」ことです。ここには Claude Code の transcript 構造に踏み込んだ実装があります。

大きな transcript を端だけ読む

セッションの表示には「cwd」「最初のユーザー発話」「タイトル」が要りますが、transcript 全文を読むのはセッション数 × ファイルサイズで重い。そこで 先頭 256KB と末尾 64KB だけを読みます。

js
const HEAD_BYTES = 262144; // 256KB: 先頭ユーザー発話は CLAUDE.md/記憶の自動注入込みで大きいことがある
const TAIL_BYTES = 65536;  // 64KB: 末尾のタイトルレコードを拾う範囲
  • 先頭から:cwd と最初のユーザー発話を拾う。発話は system-reminder ブロックや XML 風タグを除去(cleanText)してから採用。
  • 末尾から:タイトルレコード(ai-title / custom-title)を拾う。末尾=最新として優先。

タイトルの優先順位は 手動命名(/rename = custom)> 自動生成(ai)> 最初の発話 です(pickSessionTitle)。なお 256KB〜末尾 64KB の中間帯に挟まった custom-title は拾えないという妥協がコメントで明記されています(全文走査を避けるための割り切り)。

稼働中(live)判定:pid 生存チェック

「いま動いているセッションか」は、ライブレジストリ(~/.claude/sessions/*.json)に書かれた pid が生きているかで判定します。process.kill(pid, 0) はシグナルを送らず生存確認だけを行う Unix の定石です。

js
if (j.sessionId && j.pid) {
  try { process.kill(j.pid, 0); live.add(j.sessionId); }
  catch (_e) { /* 死んでる */ }
}

稼働中セッションはアーカイブ対象から自動除外され(409 を返す)、誤って動作中の会話を退避してしまう事故を防ぎます。

「本物のセッション」だけに絞る検索

session-search は grep で当たった jsonl のうち、ファイル名が UUID かつ **親フォルダが - 始まり(プロジェクト直下)**のものだけを採用します。これによりサブエージェントやツール結果の jsonl を除外し、resume 可能な本セッションだけを返します。

js
if (!UUID_RE.test(base) || !parent.startsWith('-')) continue; // 本セッション かつ プロジェクト直下のみ

active なら resume コマンド、archive なら復元コマンドを各ヒットに付けて返すのも気の利いた点です。

ピン留めはサイドカー JSON

ピン留めはセッション実体に触れず、別ファイル pins.json に ID を記録するだけ(サイドカー方式)。ピン留めしたセッションは limit から漏れても必ず一覧の先頭に表示されます。

セッションのアーカイブは「移動するだけ」

moveSessionFile()~/.claude/projects/<folder>/<id>.jsonl~/.claude/projects-archive/<folder>/fs.rename するだけです。中身は不変・プロジェクトフォルダ名を維持・完全に可逆。同じ処理は CLI(scripts/manage_sessions.sh)からも実行でき、ダッシュボードの POST API と CLI が同じファイル移動セマンティクスを共有します(運用参照)。

デモと起動

bash
cd dashboard
cp config.example.json config.json   # <...> を自分の環境値に置換
node server.js                        # 既定 :8080

サーバーを立てなくても、dashboard/demo.html をブラウザで開けば サンプルデータ入り・各タブに使い方解説つきの自己完結 HTML で全体像を確認できます。

start.sh は冪等で、既に同ポートでダッシュボードが稼働中なら /api/health で検知して正常終了します。手動起動・tmux 起動・プラグイン monitor が競合しても二重起動・クラッシュになりません。

テスト

ダッシュボードは node:test 組み込みのみ(外部依存ゼロ)で、lib/ の各モジュール(純粋関数・データソース読み取り・セッション操作・API ルーティング)を単体テストし、サーバーを実起動して全 API を叩く統合テスト(アーカイブ往復・method/host ガード含む)まで備えます。

bash
cd dashboard && npm test

agent-team-pack(MIT OSS)の仕組みを読み解いた技術解説資料