# ✳ Claude Office

Claude 風格的 multi-agent 虛擬辦公室視覺化。參考 [WW-AI-Lab/openclaw-office](https://github.com/WW-AI-Lab/openclaw-office)(MIT)的虛擬辦公室邏輯重新實作,改為 Claude Code 的語意與暖色系視覺。

![概念](https://img.shields.io/badge/Vite%206-React%2019-d97757)

## 畫面預覽(模擬模式)

辦公室自己運作:agent 進場就座、接任務、調用工具、派 subagent 到熱桌、協作連線、去休息區倒咖啡 —— 全部由內建劇本引擎自動循環。

![模擬模式總覽](docs/screenshots/claude-office-overview.png)

**主 agent 派出 subagent 協作**(陶土色虛線是上級 ↔ subagent 的協作連線,subagent 走到熱桌區工作):

![subagent 協作](docs/screenshots/claude-office-light.png)

**點擊 agent 看詳情**(狀態、模型、工具歷史、上下級關係、指派任務;右側為即時事件紀錄):

![agent 詳情面板](docs/screenshots/claude-office-panel.png)

**深色主題**:

![深色主題](docs/screenshots/claude-office-dark.png)

## 功能

- **2D 平面圖辦公室** — 一棟建築、十字走廊、四個分區:
  - 主力工位區(main agents 固定座位)
  - Subagent 熱桌區(subagent 臨時工位)
  - 會議區(協作時自動聚集圓桌)
  - 休息區(咖啡機、沙發、✳ Claude Office 接待櫃台)
- **Chibi 小人** — 依 agent id 確定性生成外觀;姿勢(站/坐/走)與動作(打字/說話/搖晃)由狀態驅動
- **狀態表情氣泡** — 思考雲、工具調用膠囊(轉動齒輪 + 工具名)、對話氣泡、錯誤驚嘆號、生成火花
- **走廊尋路** — agent 沿走廊的折線路徑行走(進門 → 走廊中心 → 目的地),等速插值 + 走路擺動
- **協作連線** — 上級 ↔ subagent(陶土色)、會議成員(紫色)的流動虛線
- **模擬導演** — 內建劇本引擎:進場報到 → 接任務 → 思考 → 調用工具 → 派 subagent → 回報 → 開會 → 倒咖啡,全部自動循環
- **控制列** — 暫停 / 1×2×4× 倍速 / 明暗主題 / 招募 agent
- **側邊面板** — 點擊 agent 查看狀態、模型、工具歷史、上下級關係,可手動指派任務;下方為即時事件紀錄

## 快速開始

```bash
npm install
npm run dev        # http://localhost:5173(預設為劇本模擬模式)
```

```bash
npm run typecheck  # tsc --noEmit
npm run build      # 產出 dist/
```

## 實況模式(LIVE)— 顯示真實 Claude Code 活動

另開一個終端啟動監看伺服器,然後點頂欄的「▶ 模擬」切換成「● LIVE」:

```bash
npm run watch      # http://localhost:5179/events (SSE)
```

原理:`server/watch.mjs`(純 Node、零依賴)輪詢 `~/.claude/projects/` 底下的
session transcript(JSONL),只讀取新增的位元組,轉譯成事件推播給前端:

| Transcript 內容 | 辦公室畫面 |
| --- | --- |
| 近 7 天有活動的專案 | 一位 agent 入座主力工位(最多 6 席,依最近活躍排序) |
| 使用者送出 prompt | 思考中(思考雲) |
| `tool_use`(Bash / Edit / Read⋯) | 工具調用(齒輪膠囊 + 工具名) |
| assistant 文字回覆 | 回覆中(對話氣泡,顯示回覆片段) |
| `Agent` / `Task` 工具派發 subagent | subagent 走進熱桌區,和上級連線 |
| subagent 的 `tool_result` 回來 | subagent 離場 |
| 專案 15 秒沒動靜 | 待命;5 分鐘沒動靜 → 離線(Zzz) |

注意:監看伺服器會讀取本機的 Claude Code 對話記錄(含程式碼與提示詞),
**只綁 127.0.0.1、不要對外開放**;如需團隊共用請自行加上驗證。

### 在辦公室對話(LIVE 模式)

點選專案 agent 後,側邊面板是一個**雙向聊天室**(參考 claudecodeui 的做法):
每則訊息 spawn 一次 `claude -p --output-format stream-json`,從輸出捕捉
`session_id`,下一則自動 `--resume` 接力 — 對話因此能連續。回覆會同時
顯示在聊天串和小人頭上的對話氣泡;執行過程(思考/工具)即時演在辦公室。

- 「接續專案最近 session」:第一句改從該專案最近一段已結束的 session 接續
- 「🔄 新對話」:清空 office 對話串(會終止進行中的執行)
- 事件:`POST /chat` → SSE 廣播 `chat_start` / `chat_text` / `chat_done`
- 同專案一次只跑一則;仍無法插入終端機正在進行中的互動對話

### 在辦公室下任務(API)

點選任一專案 agent,側邊面板會出現任務輸入框 —「🚀 派發任務」會透過
`POST /task` 讓監看伺服器在該專案目錄 spawn 一個 headless session:

```
claude -p --permission-mode acceptEdits   # prompt 由 stdin 傳入
```

- 勾「續上次 session」= `--resume <最新 sessionId>`,延續該專案上一段對話
- 執行過程會即時演在辦公室裡(思考 → 工具氣泡 → 回覆),結束時事件紀錄顯示 🏁
- 同專案同時只跑一個任務,其餘排隊(上限 3);單一任務 15 分鐘逾時
- 權限模式可用環境變數 `OFFICE_PERMISSION_MODE` 調整(acceptEdits / plan / bypassPermissions)
- 安全:`/task` 僅接受 localhost 來源 + Origin 白名單(5173/5180),
  headless 執行等於允許 AI 改檔案跑指令 — **請理解風險後再用**

## PM2 部署

```bash
npm run build                    # 產出 dist/
pm2 start ecosystem.config.cjs   # 啟動 web(5180)+ watch(5179)
pm2 save                         # 記住程序清單
```

| App | Port | 綁定 | 說明 |
| --- | --- | --- | --- |
| `claude-office-web` | 5180 | 0.0.0.0 | `server/serve.mjs` 靜態服務 dist/(區網手機可看,模擬模式) |
| `claude-office-watch` | 5179 | 127.0.0.1 | transcript 監看,僅本機(實況模式只在本機瀏覽器可用) |

常用指令:`pm2 logs claude-office-web`、`pm2 restart claude-office-web`、
`pm2 delete claude-office-web claude-office-watch`。
更新版本:`git pull && npm run build && pm2 restart all`(靜態檔重讀即可,
其實只有 server/ 變動才需要 restart)。

Windows 開機自啟:PM2 原生 `pm2 startup` 不支援 Windows,可另裝
[`pm2-windows-startup`](https://www.npmjs.com/package/pm2-windows-startup) 或用工作排程器執行 `pm2 resurrect`。

## 架構

```
src/
├── lib/
│   ├── constants.ts    # 辦公室幾何(分區/走廊/入口)、Claude 配色、狀態色
│   ├── types.ts        # VisualAgent / MovementState / Meeting / Link
│   ├── appearance.ts   # id → 確定性小人外觀(髮型/膚色/衣著)
│   ├── movement.ts     # 走廊尋路 + 折線等距插值
│   ├── positions.ts    # 工位格點、熱桌、會議圓桌座位、休息區錨點
│   └── phrases.ts      # 模擬台詞庫(任務名/完成語/會議語/工具名)
├── store/
│   └── office-store.ts # Zustand:agents/links/meetings/events + 全部動作
├── sim/
│   ├── director.ts     # 每個 agent 的有限狀態機 + 會議排程器
│   └── runtime.ts      # 單一 rAF 迴圈:tick 移動 → director 更新
└── components/
    ├── FloorPlan.tsx   # 建築外殼、地板紋理、隔間牆、家具、圖層合成
    ├── Pawn.tsx        # chibi 小人(SVG 部件 + CSS 動畫)
    ├── AgentAvatar.tsx # 小人 + 表情氣泡 + 名牌 + 點擊選取
    ├── Emotes.tsx      # 六種狀態氣泡
    ├── ConnectionLine.tsx
    ├── furniture.tsx   # 桌椅/圓桌/沙發/咖啡機/盆栽
    ├── HeaderBar.tsx / SidePanel.tsx
    └── ...
```

## 接上真實資料

模擬導演(`sim/director.ts`)只透過 store 的 action 改變世界。要接上真實的
Claude Code / gateway 事件流,把 director 換成事件轉譯器即可:

| 事件 | 對應 action |
| --- | --- |
| session 建立 | `spawnAgent` + `startWalk` 進場 |
| assistant 思考 / 串流 | `setStatus("thinking" / "speaking")` + `setSpeech` |
| tool_use | `setStatus("tool_calling")` + `setTool` |
| Task(subagent)派生 | `spawnAgent(role: "subagent")` + `addLink` |
| session 間協作 | `startMeeting` / `endMeeting` |
| session 結束 | `startWalk(entrance, { despawnOnArrive: true })` |
