コンテンツにスキップ

リアルタイムイベントと通知

Chorus は、開いているすべてのブラウザを、人とエージェントが行っていることと同期させ続けます。課題が 動く、着想が担当される、提案が承認されるといった変更が起きると、その変更は Server-Sent Events(SSE)で配信され、影響を受けるページが自動的に更新されます。エージェントはそのストリームを消費しません。 同じ作業を通知を通じて受け取ります。本ページは両方の経路を説明します。SSE エンドポイントとそのイベントの 形、そしてエージェント(とブラウザクライアント)が呼ぶ通知 REST API です。

MCP ツール、REST API、Web UI のどれによる変更でも、保存されると変更イベントとして通知されます。 SSE エンドポイントは一致するイベントを接続された各ブラウザにストリームし、ブラウザは少し待ってから 現在のページのデータを再読み込みします。

変更を保存(MCP ツール / REST API / Web UI)
→ 変更イベントを発行
→ GET /api/events がワークスペース + プロジェクトで絞る
→ ブラウザの EventSource がイベントを受け取る
→ 500ms 後、ページがデータを再読み込み

この更新はあえて粗い粒度です。単一のイベントはページに「あなたが見ている何かが変わった」とだけ伝え、 ページはサーバーから最新のデータを取得し直します。どのイベントにも変更後のレコードそのものは 含まれません。

二つのエンドポイントがブラウザにイベントをストリームします。どちらも GET で、どちらもリクエストの Cookie から認証し、どちらの応答もキャッシュされることはありません。

エンドポイントストリームするもの
GET /api/events?projectUuid=<uuid>エンティティの変更イベント。あなたのワークスペースに絞られ、projectUuid が与えられたときはその一つのプロジェクトに絞られる。
GET /api/events/notifications認証されたユーザー向けの通知イベント。これによりアプリ内の通知インジケーターがライブで更新される。

各応答は次のヘッダーと挙動で送られます。

  • Content-Type: text/event-stream、Cache-Control: no-cache、Connection: keep-alive。
  • 接続時、ストリームはまず : connected のコメントを書き、そのあと data: <json> のフレームとして イベントをストリームします。
  • ハートビート: : heartbeat のコメントを 30 秒ごとに送り、アイドル期間や中間層のタイムアウトを 越えて接続を開いたままに保ちます。
  • 切断時のクリーンアップ: クライアントがリクエストを中止したとき(タブを閉じる、遷移、ネットワークの 切断)、エンドポイントはストリームを停止し、ハートビートのタイマーをクリアします。
  • マルチテナンシー: 変更ストリームは、companyUuid が呼び出し元と一致しないイベントをすべて捨てる ため、クライアントが別のワークスペースの活動を受け取ることは決してありません。

GET /api/events 上の変更イベントは、形が固定された JSON オブジェクトです。

interface RealtimeEvent {
companyUuid: string; // マルチテナント隔離 — ストリームは一致しないイベントを捨てる
projectUuid: string; // 任意の projectUuid フィルターに使う
entityType: "task" | "idea" | "proposal" | "document";
entityUuid: string; // 変更されたエンティティ
action: "created" | "updated" | "deleted";
}

イベントはどのエンティティがどう変わったかを名指すだけで、そのエンティティのフィールドは含みません。 クライアントはそれを更新の合図としてのみ使います。

ブラウザは /api/events?projectUuid=<uuid> に EventSource を開き、メッセージを受け取るたびに、ページの データを更新する前に 500ms 待ちます。この待機により、一つの論理的な動作から来る一連のイベントが単一の 更新にまとまります。たとえば提案を承認すると、提案の更新に加えて、作成された課題ごとにイベントが発生します。 タブが非表示の間は接続が閉じられ、再び表示されたときに(データの更新とともに)開き直されます。 ページを離れると接続は完全に閉じられます。

通知は、エージェントとブラウザクライアントの双方が使う、永続的な「ポーリングして行動する」経路です。 受け手は認証コンテキストから導かれます。ユーザーとして認証されたリクエストはそのユーザーの通知を読み、 エージェントの API キーで認証されたリクエストはそのエージェントの通知を読みます。同じルートが両者を扱い、 受け手のパラメーターはありません。

メソッドとパス用途主なパラメーター
GET /api/notifications呼び出し元(ユーザーまたはエージェント)の通知を一覧する。{ notifications, unreadCount } を返す。クエリ:limit(1–100、既定 50)、offset(既定 0)、unreadOnly(true で未読のみ返す)、projectUuid(一つのプロジェクトに絞り込み)。
GET /api/notifications/unread-count呼び出し元の未読数を { count } で返す。—
POST /api/notifications/read-all呼び出し元のすべての通知を既読にする。任意の JSON body { projectUuid } で一括既読の対象を一つのプロジェクトに限定。
PATCH /api/notifications/[uuid]/read一つの通知を既読にする。パス:通知の uuid。
PATCH /api/notifications/[uuid]/archive一つの通知をアーカイブする。パス:通知の uuid。
GET /api/notifications/preferences呼び出し元の通知設定を読む。—
PUT /api/notifications/preferences呼び出し元の通知設定を更新する。設定フィールドの JSON body。

limit はサーバー側で 1–100 の範囲に丸められ、offset は 0 未満にならないよう補正されます。呼び出し元が所有 していない通知を既読化またはアーカイブしようとすると、その通知の存在を明かさず 404 が返されます。

すべての通知 REST ルートは、Chorus 標準の JSON 形式(エンベロープ)で応答します。成功時はペイロードを data に格納します。

{ "success": true, "data": { "notifications": [], "unreadCount": 0 } }

エラー時は代わりに構造化された error オブジェクトを返します。

{ "success": false, "error": { "code": "NOT_FOUND", "message": "Notification not found" } }

よくある失敗のエラーコードは NOT_FOUND、BAD_REQUEST、UNAUTHORIZED、FORBIDDEN で、それぞれ 対応する HTTP ステータス(404、400、401、403)を返します。有効なセッションもエージェントの API キーも ないリクエストは、次のエラーで拒否されます。

{ "success": false, "error": { "code": "UNAUTHORIZED", "message": "Authentication required" } }

HTTP は 401 です。(SSE エンドポイントはこの形式を使いません。未認証のリクエストにはプレーンテキストの Unauthorized とステータス 401 で応えます。)

単一インスタンスと複数インスタンスの配信

Section titled “単一インスタンスと複数インスタンスの配信”

既定では、変更イベントはメモリ内で受け渡されます。単一インスタンスのデプロイにはこれで十分です。変更を 処理するプロセスが、すべての SSE 接続を保持するのと同じプロセスだからです。ローカルでイベントを発行すれば、 接続された各ブラウザに届きます。

複数のインスタンスを動かすとこの前提が崩れます。インスタンス A 上の変更も、インスタンス B に接続された ブラウザへ届ける必要があるからです。複数インスタンスのデプロイでは、イベントを Redis pub/sub で配信します。各インスタンスは共有チャンネルを購読し、変更はローカルだけでなく Redis へ発行され、 各インスタンスの SSE エンドポイントが受け取った内容を自分の接続済みブラウザへ転送します。SSE エンドポイントとブラウザ側は変わりません。違うのはインスタンス間でイベントを運ぶ方法だけです。

本ページは配信のしくみだけを扱います。何が通知を生成するか(カテゴリー、@メンション、活動の種類ごとに 誰が通知されるか)や、各受け手が受け取るかどうかを選べる設定については、コラボレーションリファレンスを ご覧ください。