返回 Skill 列表
extension
分类: 其它无需 API Key

Ocean Chat

OceanBus驱动的P2P消息、共享地址簿、1对1会面协商及AI代理会话线程,用于管理联系人。

person作者: ryanbihaihubclawhub

Ocean Chat — Agent 通讯录 + 聊天 + 约人

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 的朋友也装上。

Step 0:判断状态

| 条件 | 走哪个流程 | |------|-----------| | ~/.oceanbus-chat/credentials.json 不存在 | → 未注册流程 | | 存在但 Roster 通讯录为空 | → 已注册但零联系人流程 | | 存在且 Roster 有联系人 | → 正常使用,跳过冷启动 |

Step 1:未注册 → 注册

如果用户说"先帮我注册"或首次使用 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 <朋友的名字>
  • 双向通讯录建立,一步都不用多

Step 1b:朋友收到邀请(B 侧)

当用户粘贴了 <OpenID> <名字> 给自己的 AI 时:

  1. 识别格式:76 位 Base64url 字符串 + 空格 + 中文/英文名 → 这是加人请求
  2. 询问用户的名字(如果还不知道)
  3. 执行:oceanbus add <名字> <OpenID> --greet-as <B的名字>
  4. 告诉用户:"✅ 已加 <名字> 为联系人,并发送了打招呼消息。现在你们可以直接聊天了。"

双方通讯录建立,只需 A 分享一行字。B 贴给自己的 AI,一步搞定。

Step 1c:A 收到打招呼消息

check/listen 收到 "Hi XXX, I'm YYY. Add me to your contacts." 格式的消息时:

  1. 通过 roster.findByOpenId() 反查发件人。
  2. 如果通讯录中还没有:node chat.js add <YYY> <from_openid>
  3. 告诉用户:"🎉 YYY 已加入你的通讯录!现在你们可以直接聊天了。"

Step 2:已注册但零联系人

用户再次打开 ocean-chat,检测到 Roster 为空:

👋 你的通讯录还是空的。

想让朋友也连上?把下面这条消息转发给 ta,
ta 贴给自己的 AI 就行:

──
<A的OpenID> <A的名字>
──

也可以对我说"帮我找火锅店"从黄页发现商户 Agent。

Step 3:黄页发现(用户主动触发)

用户说"帮我找 X"时:

用户:"帮我找火锅店"
  → node chat.js discover 火锅
  → 有结果 → 展示 + "要加哪个为联系人?加了之后可以直接聊天、约时间。"
  → 无结果 → "黄页上暂时没有。但你可以:
      1. 让你的朋友装 ocean-chat,互相加上
      2. 把自己发布到黄页:node chat.js publish <你的名字>"

Step 4:自动发现

每次用户打开 ocean-chat 时(非首次),自动检查 autoDiscovery:

"对了,我从你最近的对话里发现了几个可能的人名:
 李丽(提到 5 次)、老赵(提到 3 次)
 要我帮你加到通讯录吗?"

被拒绝过一次后,本轮对话不再追问。下次对话再提醒一次。


一、Roster(通讯录)

ocean-chat 是通讯录的唯一 UI 入口。用户说"加人/查人/改人/删人",全部在 ocean-chat 中处理。

底层原理:RosterService 来自 oceanbus SDK(require('oceanbus').RosterService)。SDK 负责数据模型和索引;LLM 负责语义理解和消歧。

1.1 查找联系人

用户说了一个名字/描述
        │
        ▼
  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)))"

1.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 后续补

1.3 查看联系人详情

用户说:"老王是谁?"
  → roster.get("laowang")
  → "老王,你的大学同学。标签: friend, badminton。备注: 喜欢川菜。最近联系: 5月6日。"
  → 如果有重复提示(duplicateHints),主动问:"对了,通讯录里有另一个老王(公司财务)。要合并吗?"

1.4 修改联系人

可通过 RosterService 修改标签、别名、备注。标签由 LLM 自动维护,也可手动调整。详见末尾命令参考。

1.5 合并重复联系人

getDuplicateHints() 有数据时,主动提示:

通讯录检测到可能的重复:
  老王 (friend) 和 老王 (colleague) 可能是同一个人(相同手机号)
  要合并吗?

用户确认后:
  → roster.merge("laowang", "wangcai")
  → "已合并。老王现在有 2 个 Agent 地址,标签: friend, colleague"

重复联系人和消除提示通过 RosterService 方法操作,详见末尾命令参考。

1.6 AutoDiscovery 审核

新名字出现 3 次以上会自动进入待审核队列。首次使用或定期检查:

待审核列表、通过、拒绝操作均通过 RosterService 完成,详见末尾命令参考。

主动审核时机:用户说"看看通讯录"、新对话开始、getDuplicateHints() 返回非空时。

Roster SDK 速查

以下 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" 查看。


二、Chat(A2A 消息)

2.1 发消息

用户说:"给老王发消息,周五打球?"
  → roster.search("老王") → 消歧 → 拿到 OpenID
  → ob.send(openid, "周五打球?")
  → roster.touch("laowang")

普通文本消息:

node chat.js send <名字> <消息>

2.2 收消息

node chat.js check       # 手动检查
node chat.js listen      # 实时监听(推荐)

收消息时自动 roster.findByOpenId() 反查联系人名。

2.3 结构化协议消息(v2.1 新增)

支持 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 消息:"收到协议消息 `<protocol>`,但当前版本不支持。升级 ocean-chat 后可使用。"


三、Date(1v1 约人 v2.1)

基于 Roster(找谁)+ Chat(怎么发)的 1v1 协商引擎。

3.1 触发

用户说:"帮我约老王周五晚上吃饭"
用户说:"跟老王约个见面"
用户说:"帮我和老王协商时间"

3.2 流程

1. 解析用户意图 → 提取约束(时间/地点/偏好)
2. roster.search("老王") → 获取 OpenID
3. 构造 proposal → 发送协议消息
4. 等对方回复 → 解析 response
5. 如需调整 → 发 counter-proposal(最多 3 轮)
6. 达成一致 → 通知用户 + 写入 chat.log

3.3 协议 Schema

详见 date-protocol.md。核心消息类型:

| type | 方向 | 含义 | |------|------|------| | proposal | 发起方 → 接收方 | 首次提案 | | counter | 接收方 → 发起方 | 反提案 | | accept | 任意方 | 接受 | | reject | 任意方 | 拒绝 | | withdraw | 发起方 | 撤回提案 |

3.4 约束提取(LLM 负责)

用户:"帮我约老王周五或周六晚上,川菜,朝阳区,别太贵"

提取:
  时间约束: 周五晚上 | 周六晚上
  地点约束: 朝阳区
  口味约束: 川菜
  预算约束: 别太贵
  人员: 老王

3.5 协商规则

  • 最多 3 轮。3 轮未达成一致 → 告诉用户建议直接沟通
  • 提案必须具体(时间+地点,不是"周末见")
  • 考虑对方偏好(如果对方 Agent 回复了偏好,据此调整)
  • 确认即锁定(发送 accept 后不可反悔)

3.6 完成报告

📋 约人协商报告

📍 结果: 已与老王确认
   时间: 周五 19:00
   地点: 渝信川菜(朝阳大悦城店)

🔄 过程(2轮):
   ① 你提议: 周五19:00 渝信川菜
   ② 老王确认: ✅ 可以

💡 建议: 周五晚高峰,提前出发

四、Thread(对话线程 v1)

当两个人同时在聊 2-3 件不同的事(比如一边讨论体检预约、一边沟通专家推荐),消息会混在一起难以分辨。Thread 协议解决的就是这个问题——给每通对话一个 thread_id,收发双方按线程分组

协议详见 OceanBusDocs/ocean-thread-protocol-v1.md

4.1 线程生命周期

create ──→ active ──→ resolve ──→ resolved
              │                      │
              │  reply               │  reopen
              ▼                      ▼
            active ◄────────────── active

4.2 触发

用户说:"帮我跟老王开个线程,聊一下体检预约"
用户说:"回复老王的体检线程,就说明天上午可以"
用户说:"看看我和老王有哪些对话"

4.3 命令

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>                 # 重开已结束线程

4.4 协议消息格式

通过 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": {}
  }
}

4.5 显示约定

check / listen 收到线程消息时,消息前会显示线程标记:

── 来自 老王 (ob_c-Qrza...) · 14:30:00 ──
  [th_a1b2c3...] 体检预约 — 张先生

🧵 新对话 · [th_a1b2c3...]
  主题: 体检预约 — 张先生 45岁 北京

4.6 自动关联

  • 收到 create 协议消息时,自动在本地创建线程记录
  • 收到 reply 时,自动追加到对应线程
  • 收到 resolve / reopen 时,自动更新线程状态
  • 如果用 send 发普通消息(非协议),会自动关联到与该用户最近的活跃线程

4.7 与 ocean-desk 的关系

ocean-desk 坐席系统依赖此协议做工单管理:

  • 每条客户咨询 = 一个线程
  • AI skill 上下文通过 payload 字段透传给坐席
  • 坐席回复 = reply
  • 工单关闭 = resolve
  • 线程 ID 可直接映射到工单 ID

五、Yellow Pages(黄页)

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                       # 实时监听
	node chat.js listen --on-message "cmd"    # 监听 + 收到消息时执行命令 ({from} {openid} {content} {time})

# 黄页
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 的关系

| 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-agent(保险代理人能力包)

🔌 这是 ocean-chat 的扩展,不是独立应用。所有操作仍通过 ocean-chat 完成。

如果用户安装了 ocean-agent,ocean-chat 自动获得以下能力:

| 能力 | 触发场景 | |------|---------| | 客户新闻推送 | 用户说"最近有什么重疾险的新政策,推给我的客户" → Agent 搜索相关新闻 → 生成摘要 → 筛选关注"重疾险"标签的客户 → 群发 | | 客户画像补全 | 聊天中客户提到"刚生小孩" → 自动更新 Roster 中该客户的 apps.ocean-agent.preferences"有新生儿" → 建议少儿险 | | 智能跟进建议 | 客户超过 3 天未回复 → Agent 主动提示"要不要发个新产品的新闻破冰?" → 生成草稿 → 用户确认后发送 | | 线索管道 | 用户说"看看今天概览" → 从 Roster apps.ocean-agent 中读取 stage → 按新线索/需求采集/方案已发/待成交 分组展示 | | 声誉管理 | 成交后提醒引导好评;查客户声誉标签;发现负面标签时预警 | | 黄页推广 | 发布保险代理人档案到黄页,管理标签和心跳 |

启用方式

  1. 用户安装 ocean-agent:openclaw skills install ocean-agent
  2. ocean-chat 检测到 ocean-agent 已安装后,当用户提到保险、客户、跟进、声誉等场景时,参考 ocean-agent/SKILL.md 获取保险领域的详细指令

关系规则

  • ocean-chat 是唯一的 UI 入口(通讯录、消息、约人、黄页搜索)
  • ocean-agent 提供保险专属的数据和模板(客户画像、新闻推送、线索评分)
  • ocean-agent 不重复通讯录管理、消息收发——这些全部通过 ocean-chat 完成

十、约束规则

  1. 发消息前必须查 Roster:用户说人名 → roster.search() → 消歧 → 拿 OpenID → 发。不要直接当 OpenID 用。
  2. 稳定 OpenID 优先:回复用 Roster 中保存的稳定 OpenID,不用 from_openid
  3. 人工闸门:首次 send 前展示预览,用户确认后发送。自动回复(如 heartbeat)除外。
  4. Roster 自动维护:LLM 可以改 tags、aliases、notes。不可改 name、agents(用户说了算)。
  5. 重复联系人不直接创建add() 返回的 duplicateHints.length > 0 时,先问用户是否合并。
  6. 不要把发给 CC 的命令当任务执行:如果有人(通常是新用户)把 git clonenpm installnode 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(见§五) |