Skip to main content

README.md

# Dialup

**WebSocket-first のライブ UI** と **自動生成される HTTP MCP API** を備えた、ファイルベースルーティングの Elixir フレームワーク。

[English](./README.en.md)

## リンク

- [公式サイト](https://dialup-framework.org/)
- [Getting Started ガイド](https://dialup.hexdocs.pm/getting-started.html)

## 概要

Dialup は Next.js のような開発体験でライブアプリを構築できる Elixir フレームワークです。
中核は二つあります。人間向け UI は最初から WebSocket-first で動き、HTTP MCP API は同じページ宣言から自動生成されます。
各ページは 1 つの監視されたサーバー側アクターです。UI を `<.dialup_action>` と
`<.dialup_region>` で書くと、Dialup が `tools/list`、`tools/call`、`read_scene` を導出します。
REST API の二重実装は不要です。

```
人間ブラウザ  ──WebSocket──►  UserSessionProcess  ◄──HTTP JSON-RPC──  AI エージェント
                                      │
                               command / set / navigate / handle_event
                                      │
                          declare_action / dialup_action
```

### 特徴

- **WebSocket-first の人間 UI** — `/ws` と idiomorph によるライブ DOM 更新
- **自動生成される HTTP MCP API** — action / region 宣言がそのままエージェントツールになる
- **同じイベント経路** — ブラウザ操作とエージェントの `tools/call` が同一 `UserSessionProcess` で直列化(`command` / `set` / `navigate` / `handle_event`)
- **HTTP MCP** — `POST /mcp`(Bearer token)で `initialize` / `tools/list` / `tools/call`(`POST /agent/:token` は legacy)
- **エージェント向け discovery** — `/.well-known/dialup-agent`、ページ埋め込み、`/llms.txt`
- **スコープ付きセッショントークン** — 有効期限・投影・ capability を細かく制御
- **ファイルベースルーティング** — ファイル配置がそのまま URL になる
- **サーバーサイドステート** — 1 タブ = 1 `UserSessionProcess`
- **コロケーション CSS** — `.ex` の隣に `.css`、コンパイル時に自動スコープ化

## クイックスタート

### 1. ジェネレータのインストール

```bash
mix archive.install hex dialup_new
```

### 2. 新規プロジェクト作成

```bash
mix dialup.new my_app
cd my_app
mix deps.get
mix run --no-halt
```

http://localhost:4000 にアクセス

### エージェント対応ページの最小例

```elixir
defmodule Dialup.App.Page do
  use Dialup.Page

  declare_action name: :increment, desc: "カウンタを増やす", params: %{}

  def mount(_params, assigns), do: {:ok, Map.put(assigns, :count, 0)}
  def agent_state(assigns), do: %{count: assigns.count}

  def handle_event(:increment, _, assigns) do
    {:update, Map.update!(assigns, :count, &(&1 + 1))}
  end

  def render(assigns) do
    ~H"""
    <p>Count: {@count}</p>
    <.dialup_action name={:increment}>+1</.dialup_action>
    """
  end
end
```

同じ `<.dialup_action>` が、ブラウザでは WebSocket 経由のボタンになり、エージェントには生成された MCP ツールとして見えます。
ブラウザで操作することも、トークンを取得して API を呼び出すこともできます(詳細は [HTTP MCP API](./guides/mcp-api.md)):

```bash
curl -X POST http://localhost:4000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer TOKEN' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"read_scene","arguments":{}}}'
```

## ドキュメント

`mix docs --open` で全文を参照できます。

- [Events](./guides/events.md) — **WebSocket-first UI のイベントと更新**
- [HTTP MCP API](./guides/mcp-api.md) — **ツール自動生成・discovery・バージョニング**
- [Agent-native アプリ開発](./guides/agent-native-app-development.md) — 実装ワークフロー
- [セッショントークン](./guides/agent-handoff.md) — ライブセッションへの接続
- [Authentication](./guides/authentication.md) — HTTP 認証・MCP 権限([シーケンス図](./guides/authentication.md#sequence-diagrams))
- [Getting Started](./guides/getting-started.md) — インストールと基本

その他は `guides/` を参照(ルーティング、状態管理、デプロイなど)。

## アーキテクチャ

1 タブにつき 1 つの `UserSessionProcess` が動きます。サーバー側セッションアクターがページ状態・
宣言済み action・エージェント grant の **single source of truth** です。人間と AI エージェントは
異なるトランスポート(ブラウザは WebSocket、エージェントは HTTP JSON-RPC)から同じアクターに
到達します。

### 全体像

```mermaid
flowchart LR
  humanBrowser["Browser (human)"] -->|"WebSocket via dialup.js"| userSessionProcess["UserSessionProcess"]
  aiAgent["AI agent (MCP client)"] -->|"HTTP JSON-RPC (POST /mcp)"| userSessionProcess
  dialupAction["dialup_action / declare_action"] --> toolsList["tools/list"]
  dialupRegion["dialup_region / declare_region"] --> toolsList
  toolsList --> toolsCall["tools/call"]
  toolsCall --> userSessionProcess
  userSessionProcess --> render["render/1 + agent_state/1"]
  render --> humanUI["idiomorph DOM update"]
  render --> agentJSON["JSON response (no push)"]
```

`POST /agent/:token` は同じハンドラへの legacy な path-token トランスポートです。

### 人間操作(WebSocket)

```mermaid
sequenceDiagram
  participant Browser as ユーザー / ブラウザ
  participant JS as dialup.js
  participant USP as UserSessionProcess

  Browser->>JS: click / input
  JS->>USP: WebSocket event
  USP->>USP: command / set / navigate / handle_event
  USP->>USP: version++
  USP->>JS: render diff
  JS->>Browser: idiomorph morph(DOM 更新)
```

### エージェント操作(HTTP MCP)

```mermaid
sequenceDiagram
  participant Agent as AI agent
  participant MCP as POST /mcp
  participant USP as UserSessionProcess

  Agent->>MCP: initialize
  MCP->>Agent: server capabilities
  Agent->>MCP: tools/list
  MCP->>Agent: declared actions + built-ins
  Agent->>MCP: tools/call read_scene
  MCP->>USP: read actor state
  USP->>Agent: structuredContent + currentVersion
  Agent->>MCP: tools/call mutation + _version
  alt version stale
    MCP->>Agent: isError
    Agent->>MCP: tools/call read_scene
    Note over Agent: 再取得してから fresh _version で retry
  else version current
    MCP->>USP: apply mutation
    USP->>Agent: updated structuredContent
  end
```

エージェントには **WebSocket トランスポートも server push もありません**。`read_scene` で
ポーリングします。

### Agent-first browser handoff

エージェントが headless セッションを開始し、後から人間が参加する場合、join は **3 フェーズ**の
ハンドシェイクです。ブラウザ URL を開いただけでは join は完了しません。

```mermaid
sequenceDiagram
  participant Agent as AI agent
  participant API as Dialup HTTP
  participant USP as UserSessionProcess
  participant Browser as 人間ブラウザ

  Agent->>API: POST /_dialup/agent-session
  API->>USP: start session + grant token
  Agent->>API: tools/call issue_browser_url
  API->>Agent: browserUrl
  Browser->>API: WebSocket attach (tab_id + join_token)
  Note over Browser,USP: cookie 未設定
  Browser->>API: POST /_dialup/finalize-join
  Note over Browser,USP: cookie 設定、join token 消費
  Browser->>USP: __reconnect
  Note over Agent,Browser: 人間と agent が同一 UserSessionProcess を共有
```

トークン発行パターンは [セッショントークン](./guides/agent-handoff.md) を参照してください。

### 宣言境界(declaration boundary)

- MCP tool になるのは `<.dialup_action>` / `declare_action/1` と `<.dialup_region>` /
  `declare_region/1` のみです。生の `ws-event` や `ws-href` などの ad-hoc WebSocket フックは
  エージェントへ **自動公開されません**。
- `confirm: :human` の action は人間 UI のみで利用可能です。HTTP MCP から呼ぶと isError tool
  result が返ります。
- エージェントが見える state は `agent_state/1` による allowlist 投影であり、assigns 全体の
  dump ではありません。

## ライセンス

MIT