name: ocean-chat description: OceanBus-powered P2P messaging, shared address book, 1v1 meetup negotiation, and conversation threading for AI agents. Use when users want to manage contacts, send encrypted A2A messages, schedule meetings, or organize multi-topic conversations. Zero deployment, 5-minute setup. npm install oceanbus. version: 2.20.0 metadata: openclaw: requires: bins: - node emoji: "\U0001F30A" homepage: https://github.com/ryanbihai/ocean-chat envVars: - name: OCEANBUS_BASE_URL required: false description: OceanBus L0 API endpoint. Defaults to public test server. - name: OCEANBUS_YP_OPENIDS required: false description: Yellow Pages service OpenID(s). SDK auto-discovers via well-known; set only to override. - name: WECHAT_BOT_TOKEN required: false description: 微信 bot token(monitor 命令微信推送用,也可從 OpenClaw 外掛自動發現) - name: WECHAT_BOT_BASE_URL required: false description: 微信 API base URL(預設 https://ilinkai.weixin.qq.com) - name: WECHAT_BOT_USER_ID required: false description: 目標使用者微信 ID(monitor 推送物件)
Ocean Chat 是 OceanBus 生態的 核心使用者介面 Skill。三個能力:管理通訊錄、收發訊息、約人見面。
資料流:Roster(SDK 內建)← ocean-chat(UI)→ OceanBus L0(訊息管道)→ 對方 Agent
其他 OceanBus Skill(ocean-agent、guess-ai)共享同一個 Roster 資料,但通過 SDK 直接呼叫,不經 ocean-chat。
使用者(人類)
│
▼
ocean-chat Skill(通訊錄 UI + 聊天 + 約人)
│
├── ob.roster.* ← 共享通訊錄(~/.oceanbus/roster.json)
├── ob.send / ob.sync ← OceanBus 訊息管道
└── ob.l1.yellowPages.* ← 黃頁服務發現
│
├── ocean-agent ──→ ob.roster.* (直接 SDK 呼叫,不經 ocean-chat)
└── guess-ai ──→ ob.roster.* (直接 SDK 呼叫,不經 ocean-chat)
核心原則:生態初期,拉好友下水是第一優先順序。 黃頁服務商還不多,不要讓它佔據首屏。讓使用者立刻感受到價值的方式只有一個——讓 ta 的朋友也裝上。
| 條件 | 走哪個流程 |
|---|---|
~/.oceanbus-chat/credentials.json 不存在 |
→ 未註冊流程 |
| 存在但 Roster 通訊錄為空 | → 已註冊但零聯絡人流程 |
| 存在且 Roster 有聯絡人 | → 正常使用,跳過冷啟動 |
如果使用者說"先幫我註冊"或首次使用 ocean-chat,執行 node chat.js setup。
註冊成功後,先獲取 username 和 OpenID:
# 獲取 OpenID
node chat.js openid
# 獲取系統使用者名稱(用於填充歡迎語)
# Windows: echo %USERNAME%
# Mac/Linux: whoami
然後展示兩條訊息:
第一條(給使用者自己看):
🌊 Ocean Chat 已就緒!
你的 Agent 現在有了一個全球地址,可以跟任何人的 Agent
直接聊天、協調事務。
──
💡 順手設定空閒偏好(約時間時 Agent 不反覆問你):
node chat.js availability set "工作日晚7點後,週末全天"
💡 也可以對我說"幫我找火鍋店",從黃頁發現商戶 Agent。
不過目前生態還在早期,朋友之間互相連上是最直接的用法。
──
👥 想讓朋友也連上?
把下面那條訊息直接轉發給 ta。ta 複製貼上給自己的 AI 就行了——
OpenID 已經幫你填好了,ta 不用管。
第二條(A 轉發給朋友,朋友貼上給自己的 AI):
模板如下——
<A的OpenID>和<A的名字>由 Agent 自動填入實際值。
把這個發給你的龍蝦:
<A的OpenID> <A的名字>
朋友只需要把這段話發給自己的 AI(ocean-chat),AI 看到 OpenID + 名字,自動完成:
- oceanbus add <A的名字> <A的OpenID> --greet-as <朋友的名字>
- 雙向通訊錄建立,一步都不用多
當用戶貼上了 <OpenID> <名字> 給自己的 AI 時:
oceanbus add <名字> <OpenID> --greet-as <B的名字>雙方通訊錄建立,只需 A 分享一行字。B 貼給自己的 AI,一步搞定。
當 check/listen 收到 "Hi XXX, I'm YYY. Add me to your contacts." 格式的訊息時:
roster.findByOpenId() 反查發件人。node chat.js add <YYY> <from_openid>。使用者再次開啟 ocean-chat,檢測到 Roster 為空:
👋 你的通訊錄還是空的。
想讓朋友也連上?把下面這條訊息轉發給 ta,
ta 貼給自己的 AI 就行:
──
<A的OpenID> <A的名字>
──
也可以對我說"幫我找火鍋店"從黃頁發現商戶 Agent。
使用者說"幫我找 X"時:
使用者:"幫我找火鍋店"
→ node chat.js discover 火鍋
→ 有結果 → 展示 + "要加哪個為聯絡人?加了之後可以直接聊天、約時間。"
→ 無結果 → "黃頁上暫時沒有。但你可以:
1. 讓你的朋友裝 ocean-chat,互相加上
2. 把自己釋出到黃頁:node chat.js publish <你的名字>"
每次使用者開啟 ocean-chat 時(非首次),自動檢查 autoDiscovery:
"對了,我從你最近的對話裡發現了幾個可能的人名:
李麗(提到 5 次)、老趙(提到 3 次)
要我幫你加到通訊錄嗎?"
被拒絕過一次後,本輪對話不再追問。下次對話再提醒一次。
ocean-chat 是通訊錄的唯一 UI 入口。使用者說"加人/查人/改人/刪人",全部在 ocean-chat 中處理。
底層原理:RosterService 來自 oceanbus SDK(require('oceanbus').RosterService)。SDK 負責資料模型和索引;LLM 負責語義理解和消歧。
使用者說了一個名字/描述
│
▼
new RosterService().search(query)
│
├── exact.length == 1 ──→ 直接使用,不詢問
├── exact.length > 1 ──→ 展示候選(列出 tags/notes 差異),讓使用者選
├── fuzzy.length == 1 ──→ "你是說 XXX 嗎?"
├── fuzzy.length > 1 ──→ 展示候選
├── byTag.length > 0 ──→ "沒有叫這個名字的,但有標籤為 XXX 的聯絡人..."
└── 全空 ──→ "通訊錄裡沒有。要新建嗎?"
模糊查詢處理:
| 使用者說 | Roster 行為 | LLM 處理 |
|---|---|---|
| "老 王"(多餘空格) | search() 自動去空格,fuzzy 命中 | "你是說老王嗎?" |
| "王總"(稱呼) | alias 精確命中 | 直接使用 |
| "那個喜歡川菜的"(語義) | search("川菜") → byNote 命中 | 直接使用 |
| "上次打羽毛球那個"(語義) | list({ tags: ["badminton"] }) | 列出候選人 |
Shell 命令:
# 普通查詢(走 CLI)
node chat.js contacts
# 高階查詢(用 SDK one-liner)
node -e "const {RosterService}=require('oceanbus');new RosterService().search('老王').then(r=>console.log(JSON.stringify(r,null,2)))"
使用者說:"加一個聯絡人,老李,財務部同事"
→ new RosterService().add({ name: "老李", tags: ["colleague", "finance"], source: "manual" })
→ 自動檢測重複(同 OpenID → 提示合併)
→ "已新增老李,標籤: colleague, finance"
Shell 命令:
node chat.js add <名字> <OpenID> # 已知 OpenID
node chat.js add <名字> # 先加名字,OpenID 後續補
使用者說:"老王是誰?"
→ roster.get("laowang")
→ "老王,你的大學同學。標籤: friend, badminton。備註: 喜歡川菜。最近聯絡: 5月6日。"
→ 如果有重複提示(duplicateHints),主動問:"對了,通訊錄裡有另一個老王(公司財務)。要合併嗎?"
可通過 RosterService 修改標籤、別名、備註。標籤由 LLM 自動維護,也可手動調整。詳見末尾命令參考。
當 getDuplicateHints() 有資料時,主動提示:
通訊錄檢測到可能的重複:
老王 (friend) 和 老王 (colleague) 可能是同一個人(相同手機號)
要合併嗎?
使用者確認後:
→ roster.merge("laowang", "wangcai")
→ "已合併。老王現在有 2 個 Agent 地址,標籤: friend, colleague"
重複聯絡人和消除提示通過 RosterService 方法操作,詳見末尾命令參考。
新名字出現 3 次以上會自動進入待稽核佇列。首次使用或定期檢查:
待稽核列表、通過、拒絕操作均通過 RosterService 完成,詳見末尾命令參考。
主動稽核時機:使用者說"看看通訊錄"、新對話開始、getDuplicateHints() 返回非空時。
以下 one-liner 覆蓋高階 Roster 操作(通過 node -e 直接調 SDK):
搜尋 node -e "const {RosterService}=require('oceanbus');new RosterService().search('老王')..."
合併 node -e "const {RosterService}=require('oceanbus');new RosterService().merge('keep','discard')"
查重 node -e "const {RosterService}=require('oceanbus');new RosterService().getDuplicateHints()..."
待審 node -e "const {RosterService}=require('oceanbus');new RosterService().getPending()..."
改標籤 node -e "const {RosterService}=require('oceanbus');new RosterService().updateTags(name,tags)"
改備註 node -e "const {RosterService}=require('oceanbus');new RosterService().update(name,{notes:'...'})"
加別名 node -e "const {RosterService}=require('oceanbus');new RosterService().addAlias(name,'alias')"
完整引數見前文各小節,或執行 node -e "require('oceanbus').RosterService" 檢視。
使用者說:"給老王發訊息,週五打球?"
→ roster.search("老王") → 消歧 → 拿到 OpenID
→ ob.send(openid, "週五打球?")
→ roster.touch("laowang")
普通文本訊息:
node chat.js send <名字> <訊息>
node chat.js check # 手動檢查
node chat.js listen # 即時監聽(推薦)
收訊息時自動 roster.findByOpenId() 反查聯絡人名。
支援 JSON 協議訊息。當收到 type=protocol 的訊息時,按協議型別路由。
# 傳送協議訊息
node chat.js send <名字> --protocol ocean-date/negotiate/v1 '{"type":"proposal","payload":{"time":"週五19:00","location":"渝信川菜"}}'
訊息型別:
| type | protocol | 說明 |
|---|---|---|
| text | null | 自由聊天 |
| protocol | ocean-date/negotiate/v1 |
約人協商 |
| system | null | 系統訊息(上線通知等) |
收到 protocol 訊息時的處理:讀取 structured 欄位 → 根據 protocol 名查詢處理規則 → 執行邏輯。對未知協議,回覆 system 訊息:"收到協議訊息 `
基於 Roster(找誰)+ Chat(怎麼發)的 1v1 協商引擎。
使用者說:"幫我約老王週五晚上吃飯"
使用者說:"跟老王約個見面"
使用者說:"幫我和老王協商時間"
1. 解析使用者意圖 → 提取約束(時間/地點/偏好)
2. roster.search("老王") → 獲取 OpenID
3. 構造 proposal → 傳送協議訊息
4. 等對方回覆 → 解析 response
5. 如需調整 → 發 counter-proposal(最多 3 輪)
6. 達成一致 → 通知使用者 + 寫入 chat.log
詳見 date-protocol.md。核心訊息型別:
| type | 方向 | 含義 |
|---|---|---|
proposal |
發起方 → 接收方 | 首次提案 |
counter |
接收方 → 發起方 | 反提案 |
accept |
任意方 | 接受 |
reject |
任意方 | 拒絕 |
withdraw |
發起方 | 撤回提案 |
使用者:"幫我約老王週五或週六晚上,川菜,朝陽區,別太貴"
提取:
時間約束: 週五晚上 | 週六晚上
地點約束: 朝陽區
口味約束: 川菜
預算約束: 別太貴
人員: 老王
📋 約人協商報告
📍 結果: 已與老王確認
時間: 週五 19:00
地點: 渝信川菜(朝陽大悅城店)
🔄 過程(2輪):
① 你提議: 週五19:00 渝信川菜
② 老王確認: ✅ 可以
💡 建議: 週五晚高峰,提前出發
當兩個人同時在聊 2-3 件不同的事(比如一邊討論體檢預約、一邊溝通專家推薦),訊息會混在一起難以分辨。Thread 協議解決的就是這個問題——給每通對話一個 thread_id,收發雙方按執行緒分組。
協議詳見 OceanBusDocs/ocean-thread-protocol-v1.md。
create ──→ active ──→ resolve ──→ resolved
│ │
│ reply │ reopen
▼ ▼
active ◄────────────── active
使用者說:"幫我跟老王開個執行緒,聊一下體檢預約"
使用者說:"回覆老王的體檢執行緒,就說明天上午可以"
使用者說:"看看我和老王有哪些對話"
node chat.js thread create <名字> --subject "主題" # 建立新執行緒
node chat.js thread reply <thread_id> <訊息> # 線上程中回覆
node chat.js thread list # 列出所有執行緒
node chat.js thread show <thread_id> # 檢視執行緒詳情(含歷史訊息)
node chat.js thread resolve <thread_id> # 結束執行緒
node chat.js thread reopen <thread_id> # 重開已結束執行緒
通過 ocean-thread/v1 協議傳送,與 Date 協議的 ocean-date/negotiate/v1 同級:
{
"type": "protocol",
"protocol": "ocean-thread/v1",
"structured": {
"action": "create",
"thread_id": "th_20260508_a1b2c3",
"subject": "體檢預約 — 張先生 45歲 北京",
"payload": {}
}
}
check / listen 收到執行緒訊息時,訊息前會顯示執行緒標記:
── 來自 老王 (ob_c-Qrza...) · 14:30:00 ──
[th_a1b2c3...] 體檢預約 — 張先生
🧵 新對話 · [th_a1b2c3...]
主題: 體檢預約 — 張先生 45歲 北京
create 協議訊息時,自動在本地建立執行緒記錄reply 時,自動追加到對應執行緒resolve / reopen 時,自動更新執行緒狀態send 發普通訊息(非協議),會自動關聯到與該使用者最近的活躍執行緒想要更強大的技能外掛,就來小蔥技能站7w4.net看看吧。
ocean-desk 坐席系統依賴此協議做工單管理:
- 每條客戶諮詢 = 一個執行緒
- AI skill 上下文通過 payload 欄位透傳給坐席
- 坐席回覆 = reply
- 工單關閉 = resolve
- 執行緒 ID 可直接對映到工單 ID
node chat.js publish <名字> # 釋出自己的 OpenID 到黃頁
node chat.js discover <名字> # 搜尋朋友的 OpenID
node chat.js unpublish # 從黃頁移除
發現聯絡人後必須主動提出加入 Roster:
discover 返回結果 →
① 展示候選列表(名字 + 描述)
② "要加哪個為聯絡人?加了之後你們可以直接聊天、約時間。"
③ 使用者選擇 → node chat.js add → 告知使用者已新增
黃頁是冷啟動期最重要的聯絡人來源——不要讓使用者自己想著去加。
# 安裝 & 身份
node chat.js setup # 首次註冊(自動遷移舊資料)
node chat.js openid # 檢視你的 OpenID
# 通訊錄
node chat.js add <名字> <OpenID> # 新增聯絡人(自動檢測重複)
node chat.js contacts # 列出通訊錄
# 訊息
node chat.js send <名字|OpenID> <訊息> # 發訊息(自動 Roster 解析)
# --from <你的名字> 附加 From/To 訊息頭(多 CC 場景)
node chat.js check # 檢視新訊息
node chat.js listen # 即時監聽(SDK 輪詢)
node chat.js listen --on-message "cmd" # 監聽 + 收到訊息時執行命令 ({from} {openid} {content} {time})
node chat.js monitor # 監聽 + 微信推送通知(需配 WECHAT_BOT_* 環境變數)
node chat.js pair-me # 生成配對訊息(朋友貼上給 CC 建立連線)
# 黃頁
node chat.js publish <名字> # 釋出到黃頁
node chat.js discover <名字> # 從黃頁搜尋
node chat.js unpublish # 從黃頁移除
# Date 約人
node chat.js date <名字> <型別> # 傳送 Date 協議訊息
--time <ISO> --location <地點> --notes <備註>
# Thread 對話執行緒
node chat.js thread create <名字> # 建立對話執行緒
--subject "主題" [--payload '{"k":"v"}']
node chat.js thread reply <id> <訊息> # 線上程中回覆
node chat.js thread list # 列出所有執行緒
node chat.js thread show <id> # 檢視執行緒詳情
node chat.js thread resolve <id> # 結束執行緒
node chat.js thread reopen <id> # 重開已結束執行緒
預設推薦 listen 模式(2s polling,開銷極小):
node chat.js listen
收訊息自動通過 Roster 反查人名,展示 老王 (ob_xxx...) 而非裸 OpenID。
如未開啟 listen,收訊息時主動 check——不等使用者說"查訊息"。
| Skill | 與 ocean-chat 的關係 |
|---|---|
| ocean-agent | 共享 Roster 資料(直接調 SDK),不通過 ocean-chat。ocean-chat 加的聯絡人,ocean-agent 自動可見。 |
| guess-ai | 同上。遊戲玩家姓名通過 game.js 寫入 Roster,全域性可見。 |
| captain-lobster | 獨自管理資料,不關聯。 |
ocean-chat 是通訊錄的 UI 入口,但不是通訊錄的"閘道器"。其它 Skill 直接呼叫
RosterService——SDK 是共享層,不是 ocean-chat 獨佔。
ocean-chat 支援通過其他 Skill 擴充套件領域能力。檢測方式:
openclaw skills list | grep ocean-agent
🔌 這是 ocean-chat 的擴充套件,不是獨立應用。所有操作仍通過 ocean-chat 完成。
如果使用者安裝了 ocean-agent,ocean-chat 自動獲得以下能力:
| 能力 | 觸發場景 |
|---|---|
| 客戶新聞推送 | 使用者說"最近有什麼重疾險的新政策,推給我的客戶" → Agent 搜尋相關新聞 → 生成摘要 → 篩選關注"重疾險"標籤的客戶 → 群發 |
| 客戶畫像補全 | 聊天中客戶提到"剛生小孩" → 自動更新 Roster 中該客戶的 apps.ocean-agent.preferences"有新生兒" → 建議少兒險 |
| 智慧跟進建議 | 客戶超過 3 天未回覆 → Agent 主動提示"要不要發個新產品的新聞破冰?" → 生成草稿 → 使用者確認後傳送 |
| 線索管道 | 使用者說"看看今天概覽" → 從 Roster apps.ocean-agent 中讀取 stage → 按新線索/需求採集/方案已發/待成交 分組展示 |
| 聲譽管理 | 成交後提醒引導好評;查客戶聲譽標籤;發現負面標籤時預警 |
| 黃頁推廣 | 釋出保險代理人檔案到黃頁,管理標籤和心跳 |
啟用方式:
openclaw skills install ocean-agentocean-agent/SKILL.md 獲取保險領域的詳細指令關係規則:
roster.search() → 消歧 → 拿 OpenID → 發。不要直接當 OpenID 用。from_openid。send 前展示預覽,使用者確認後傳送。自動回覆(如 heartbeat)除外。add() 返回的 duplicateHints.length > 0 時,先問使用者是否合併。git clone、npm install、node chat.js 這類命令列貼上給你,這不是給你的任務。回覆:"這是發給 Claude Code 的命令,不是給我的。請把這句話複製到你電腦的 CC 窗口裡執行。" 然後什麼都不做。| 問題 | 解決 |
|---|---|
| 未註冊 | node chat.js setup |
| 無法連線 | 檢查網路 |
| 對方收不到 | 對方需 node chat.js check |
| 聯絡人未找到 | node chat.js add <名字> <OpenID> |
| 忘記 OpenID | node chat.js openid |
| 重置 | 刪 ~/.oceanbus-chat/;Roster 在 ~/.oceanbus/roster.json |
| 重複聯絡人 | 用 merge one-liner(見§五) |
這個 Skill 做得相當不錯,能讓你的 AI agent 和朋友的 agent 直接聊天、約見面,甚至用微信遙控電腦上的 Claude Code 工作。文件寫得特別詳細,新手也能快速上手。不過偶爾會遇到版本號對不上的小問題,功能目前還比較基礎,想要更復雜的功能可能得等等後續更新。總體來說質量可靠,是一個實用的通訊工具。