# XG CWork IM Channel for OpenClaw

工作说说 IM 机器人 Channel 插件，通过 WebSocket 长连接接收 `@` 消息，由 OpenClaw AI 处理后自动回复。

## 工作原理

```
IM 系统  →  WebSocket  →  插件（本项目）  →  OpenClaw AI  →  IM 发送接口  →  IM 系统
```

1. 插件启动时用 `robotKey`（兼容旧字段 `appKey`）换取 `access_token` 和 `userId`
2. 建立 WebSocket 长连接，接收带业务 `params` 的推送（当前代码会处理 `**robotMention**` 与 `**groupMessage**`，条件：`parsed.params` 存在且 `cmd` 为上述二者之一）
3. 将收到的消息转发给 OpenClaw 处理
4. OpenClaw AI 回复后，插件：
  - 通过 HTTP `/im/message/send` 发送**业务正文**（及附件）；
  - 若本轮无业务回复或失败（超时、precheck、无回复、发 IM 失败等），通过 **HTTP 终态兜底** 发送中文说明（见 `docs/reliability-roadmap.md`）；
  - WebSocket 可发 BEGIN / 进度 / FINISHED 等信令（IM 可不展示；**不以 WS 为唯一用户反馈**）。

### 文档索引

| 文档 | 说明 |
|------|------|
| [docs/reliability-roadmap.md](docs/reliability-roadmap.md) | **可靠性优化路线图**（待办清单、风险、执行顺序） |
| [docs/插件实现逻辑说明.md](docs/插件实现逻辑说明.md) | 实现细节 |
| [docs/openclaw-hook-events.md](docs/openclaw-hook-events.md) | **OpenClaw Hook、回调与项目使用说明** |
| [docs/openclaw通道插件-BUSINESS.md](docs/openclaw通道插件-BUSINESS.md) | 业务场景 |
| [docs/im服务接口说明.md](docs/im服务接口说明.md) | IM API / WS |
| [docs/流式消息协议设计与优化.md](docs/流式消息协议设计与优化.md) | WS 协议 |

---

## 前置条件

- 已安装 [OpenClaw](https://github.com/openclaw) (`>=2026.2.13`)
- 已在 IM 后台注册机器人，获取到 `robotKey`（旧配置中的 `appKey` 仍兼容）

---

## 安装

推荐同事通过 **npm** 安装，无需访问内部 Git；需要改源码时再从内部仓库克隆（方法 B）。

### 方法 A：通过 npm 包安装（推荐）

一条命令完成安装，依赖由 OpenClaw 自动处理：

```bash
openclaw plugins install @xgjktech/xg_cwork_im
```

运行 `openclaw plugins list` 确认列表中有 `xg_cwork_im` 即可。

> **体积说明**：扩展目录只需安装本插件 4 个运行时依赖（约十余 MB）。请勿在 `~/.openclaw/extensions/xg_cwork_im` 内执行裸 `npm install` 把 **openclaw 网关**再装一遍（会膨胀到数百 MB）。若已误装，删除该目录下 `node_modules` 后执行 `npm install --omit=dev` 即可。

### 方法 B：通过本地源码安装

如需二次开发或无法使用 npm 安装时，可从内部 Git 克隆后以链接模式安装：

```bash
git clone http://192.168.3.200/openclaw/openclaw-channel-xg-cwork-im.git
cd openclaw-channel-xg-cwork-im
npm install          # 开发：含 TypeScript、openclaw（仅 dev，用于编译）；扩展目录生产安装请用 --omit=dev
npm run build
openclaw plugins install -l .
```

### 方法 C：手动安装

1. 将本仓库下载或复制到 `~/.openclaw/extensions/xg_cwork_im`（Windows 为 `%USERPROFILE%\.openclaw\extensions\xg_cwork_im`）。
2. 确保目录内包含 `index.ts`、`openclaw.plugin.json`、`package.json`。
3. 在该目录执行 `npm install --omit=dev` 或 `npm run install:prod` 安装依赖（仅 axios/ws/zod 等，**勿**在插件目录重复安装 openclaw 网关）。
4. 运行 `openclaw plugins list` 确认 `xg_cwork_im` 已显示。

### 方法 D：国内网络环境（npm 镜像源）

若执行 `openclaw plugins install @xgjktech/xg_cwork_im` 时卡在「Installing plugin dependencies...」或出现 `npm install failed`，可临时指定国内镜像源：

```bash
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com openclaw plugins install @xgjktech/xg_cwork_im
```

若插件已半安装（扩展目录存在但依赖未装全），可进入插件目录手动补装：

```bash
cd ~/.openclaw/extensions/xg_cwork_im
# Windows: cd %USERPROFILE%\.openclaw\extensions\xg_cwork_im
rm -rf node_modules package-lock.json
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com npm install --omit=dev
```

---

## 更新已安装的插件

当插件发布新版本后，已安装的用户可按以下方式更新。

### 通过 npm 安装的（方法 A）

使用**插件 id**（不是 npm 包名）执行更新：

```bash
openclaw plugins update xg_cwork_im
```

更新完成后执行 `openclaw gateway restart` 使新版本生效。

国内网络可临时指定镜像源后再更新：

```bash
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com openclaw plugins update xg_cwork_im
```

### 通过本地源码 / 链接安装的（方法 B）

在插件目录拉取最新代码后重启网关：

```bash
cd openclaw-channel-xg-cwork-im   # 进入当时克隆的目录
git pull
npm install
npm run build
openclaw gateway restart
```

### 手动安装的（方法 C）

重新从内部 Git 下载或复制最新代码到 `~/.openclaw/extensions/xg_cwork_im`，在该目录执行 `npm install --omit=dev` 或 `npm run install:prod`，再执行 `openclaw gateway restart`。

---

## 配置

### 步骤 1：启用并信任插件

执行以下命令，将 `xg_cwork_im` 插件添加到 OpenClaw 的信任白名单中：

```bash
openclaw plugins enable xg_cwork_im
```

### 步骤 2：配置 Channel 账户信息

编辑 `~/.openclaw/openclaw.json`，添加以下配置（推荐 `accounts` 使用对象 map，key 即 `accountId`）：

```json
{
  "channels": {
    "xg_cwork_im": {
      "baseUrl": "https://cwork-web-test.xgjktech.com.cn",
      "wsBaseUrl": "wss://cwork-web-test.xgjktech.com.cn",
      "accounts": {
        "main": { "robotKey": "你的机器人 robotKey", "agentId": "main", "name": "个人助手", "enabled": true }
      }
    }
  }
}
```

> **多账户**：如有多个机器人，在 `accounts` 中继续追加即可（对象 map：key 为 `accountId`）。每个账户可指定不同的 `agentId` 对应不同的 OpenClaw Agent；设置 `accounts.<accountId>.enabled: false` 会停止该账户的 WebSocket 入站连接，不影响其他账户。

> **accounts.default（可选）**：OpenClaw 的 `doctor --fix` 可能会自动生成 `accounts.default`，用于存放默认项（例如 `groupPolicy` 等）。本插件会把 `accounts.default` 作为“默认覆盖”合并到每个真实账号上，但 **不会**把它当作一个需要启动连接的账号。

### 步骤 3：重启 Gateway

配置完成后，重启网关使配置生效：

```bash
openclaw gateway restart
```

---

## 完整配置参考

`~/.openclaw/openclaw.json` 参考结构（示例使用 CWork 测试/生产域名，详细请参考 `docs/im服务接口说明.md`）：

```json5
{
  "plugins": {
    "enabled": true,
    "allow": ["xg_cwork_im"]
  },
  "channels": {
    "xg_cwork_im": {
      "baseUrl": "https://cwork-web-test.xgjktech.com.cn",
      "wsBaseUrl": "wss://cwork-web-test.xgjktech.com.cn",
      "groupPolicy": "mention",
      "debug": false,
      // 可选：首条 AI 回复超时时间（毫秒），默认 30 分钟
      // 未配置时：30 * 60_000 = 1800000
      "firstReplyTimeoutMs": 1800000,
      // 可选：入站附件先下载到当前 Agent workspace 下该子目录，MediaPath 为相对路径或 /workspace/...（见下）
      "inboundMediaWorkspaceSubdir": "xg_im_inbound",
      // 沙箱 Agent 建议加：MediaPath 使用 /workspace/... 而不是 ./...（避免 read 按 cwd 解析失败）
      "inboundMediaSandboxRootPrefix": "/workspace",
      "accounts": {
        "main": { "robotKey": "robotKey_机器人A", "agentId": "main",  "name": "个人助手", "enabled": true },
        "sales": { "robotKey": "robotKey_机器人B", "agentId": "sales", "name": "销售助手" }
      }
    }
  },
  "bindings": [
    { "type": "route", "agentId": "main", "match": { "channel": "xg_cwork_im", "accountId": "main" } },
    { "type": "route", "agentId": "sales", "match": { "channel": "xg_cwork_im", "accountId": "sales" } }
  ]
}
```

### 生产环境配置示例

```json5
{
  "channels": {
    "xg_cwork_im": {
      "baseUrl": "https://sg-al-cwork-web.mediportal.com.cn",
      "wsBaseUrl": "wss://sg-al-cwork-web.mediportal.com.cn",
      "groupPolicy": "mention",
      "debug": false,
      "maxConnectionAttempts": 200,
      "maxReconnectDelay": 120000,
      // 生产环境可根据需要调整首回复超时时间（毫秒）
      "firstReplyTimeoutMs": 300000,
      // 按需启用：入站附件落盘到 workspace，见配置表 inboundMediaWorkspaceSubdir
      // "inboundMediaWorkspaceSubdir": "xg_im_inbound",
      "accounts": {
        "main": { "robotKey": "你的生产 robotKey", "agentId": "main", "name": "个人助手" }
      }
    }
  }
}
```

---

## 配置项说明

### 顶层配置（`channels.xg_cwork_im`）


| 选项                              | 类型      | 默认值                     | 说明                                                                                                                                                                                                                                                                                                                                               |
| ------------------------------- | ------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `baseUrl`                       | string  | **必填**                  | IM 服务域名（API 接口地址）                                                                                                                                                                                                                                                                                                                                |
| `wsBaseUrl`                     | string  | —                       | **可选**。WebSocket 独立域名。若不填，插件将尝试自动映射。                                                                                                                                                                                                                                                                                                             |
| `groupPolicy`                   | string  | `"mention"`             | 用于 OpenClaw `**groups.resolveRequireMention`**：`"open"` 时返回「不必强制仅 @ 才参与提及策略」；`"mention"` 时返回「需要 @」。**注意**：网关是否把消息交给 AI 取决于 **`params.chatType`** 与 **`actuallyMentioned`**：`chatType=SINGLE` 时无需 @；`GROUP`/未传时需 `mentions` 含机器人 `empId` 或正文 `@机器人名`。`**groupPolicy: open` 不会**让未 @ 的群消息自动触发 AI。 |
| `enabled`                       | boolean | `true`                  | 通道顶层为全局总开关；`accounts.<accountId>.enabled` 可单独停用一个账户。顶层为 `false` 时所有账户均停用。停用账户不会建立 WebSocket、发送心跳或处理入站消息。                                                                                                                                                                                                                                                                                                                                             |
| `debug`                         | boolean | `false`                 | 开启调试日志                                                                                                                                                                                                                                                                                                                                           |
| `maxConnectionAttempts`         | number  | 见下                      | Channel 配置经 Zod 校验时默认 `**10`**（`channel.ts` 中 `XgImConfigSchema`）。`connection.ts` 读取配置时若该字段仍为 `**undefined**`，则回退为 `**200**`。建议在 `openclaw.json` 中显式填写以避免与直觉不一致。                                                                                                                                                                                 |
| `initialReconnectDelay`         | number  | `1000`                  | 初始重连延迟（ms）                                                                                                                                                                                                                                                                                                                                       |
| `maxReconnectDelay`             | number  | `60000`                 | 最大重连延迟（ms）                                                                                                                                                                                                                                                                                                                                       |
| `reconnectJitter`               | number  | `0.3`                   | 重连抖动因子（0-1）                                                                                                                                                                                                                                                                                                                                      |
| `firstReplyTimeoutMs`           | number  | `1800000`（30 分钟）        | **首条 HTTP 回复超时（毫秒）**：在尚未通过 `deliver` 发出任何业务回复时触发。超时文案按插件观测分为：`never_started`（模型未启动）、`in_progress`（dispatch 仍在进行，可能仍在处理）、`ended_without_http_reply`（dispatch 已结束但仍无 HTTP 回复）。**不是** OpenClaw 运行态 API，长任务仍可能随后继续产出回复。IM 工具进度走 WS，与 HTTP 首包超时相互独立。 |
| `inboundMediaWorkspaceSubdir`   | string  | —                       | **可选**。非空时，将入站附件先下载到**当前 Agent workspace** 下该**相对子目录**（如 `xg_im_inbound`），路径约定：`{子目录}/{发送者 userId}/{YYYY-MM-DD}/{文件名}_{HHmmssmmm}.ext`（时间为本机写入时刻），再向 OpenClaw 传入 **MediaPath**；默认使用 **workspace 相对路径**（如 `./xg_im_inbound/...`），便于 **沙箱** 将 `/workspace` 映射到同一套目录树；未配置则仍使用 IM 返回的 URL。需 runtime 提供 `resolveAgentWorkspaceDir`。                   |
| `inboundMediaOpenClawPath`      | string  | `workspaceRelative`（默认） | `workspaceRelative`：入站落盘后 `MediaPath` 为 `./子目录/...`（若配置了 `inboundMediaSandboxRootPrefix` 则为该前缀 + 相对路径，如 `/workspace/xg_im_inbound/...`）；`absoluteFileUrl`：宿主机 `file://...`（仅适合无沙箱、网关与 Agent 同盘场景）。                                                                                                                                               |
| `inboundMediaSandboxRootPrefix` | string  | —                       | **可选**。沙箱内 workspace 挂载根，填 `**/workspace`** 时，在 `workspaceRelative` 模式下 `MediaPath` 为 `**/workspace/xg_im_inbound/...**`，避免 `./` 被 `read` 等工具按**进程 cwd**解析导致「不可访问」。须以 `/` 开头。                                                                                                                                                                    |
| `syncServiceBaseUrl`            | string  | —                       | **可选**。openclaw-xgkb-sync **管理 API** 根地址（如 `http://127.0.0.1:9090`）。配置后插件可处理 IM 下发的 `openclawApplySyncMapping` 信令并登记目录映射。详见 `docs/im服务接口说明.md`。                                                                                                                                                                                                  |
| `projectPromptMaxChars`         | number  | `15000`                 | `agentContext.background` 与 `userInfo.background` 单段最大字符数；超出尾部截断。                                                                                                                                                                                                                                                                          |
| `nasVolumeRootMappings`         | object[] | —                      | NAS 逻辑根到本机真实根的映射，如 `[{"logicalRoot":"/data/touqian","hostRoot":"/data/touqian"}]`；使用 NAS 挂载时推荐配置，且沙箱必须为 `scope: "session"`。 |
| `blockStreaming`                | boolean | `true`（未配置时）      | 开启后 OpenClaw 在 assistant **text 块结束（tool 调用前）** 即调用 `deliver`；插件默认经 **段缓冲** 在 `onToolStart` / 轮次结束再 HTTP（见 `deliverSegmentBuffer`）。tool 过程走 WS `TOOL_PHASE`。设为 `false` 恢复整轮结束才 deliver 一次。                                                                                                                                                                              |
| `blockStreamingCoalesce`        | object  | 不配（注入 500/500） | 合并流式碎片。未配时注入 `minChars:500 idleMs:500 maxChars:12000`（中间档；覆盖 agents.defaults 过大值）。 |
| `deliverSegmentBuffer`          | boolean | `true`               | 段缓冲：修稿覆盖、分块追加；`false` 则每次 deliver 即 HTTP + C7。 |
| `deliverSegmentBuffer`          | boolean | `true`（未配置时）      | `blockStreaming` 开启时，同一段 assistant 正文先写入 buffer，在 **tool 开始前**（interim）与 **dispatch 结束**（final）各冲刷一次 HTTP，避免修稿重复气泡。设为 `false` 恢复「每次 deliver 即 HTTP + C7 去重」。详见 `docs/段缓冲投递策略.md`。 |
| `deliverModelFallbackNotice`    | boolean | `false`（未配置时）     | 为 `true` 时向 IM 用户发送 OpenClaw 模型降级运营通知（`↪️ Model Fallback: …`）。默认不发送，仅保留 gateway 日志。 |
| `autoModelRouting`               | object  | 关闭                  | 模型 + 推理等级自动路由（见下节「单轮模型与自动路由」）。 |

### 单轮模型与自动路由（`msgContent.ext.ai_model` / `autoModelRouting`）

前端通过 `msgContent.ext.ai_model` 表达三种语义：

| `ai_model` 取值 | 行为 |
|---|---|
| 未传 / 空字符串 | **default**：使用 Agent 默认模型与 OpenClaw 默认推理配置 |
| `"auto"`        | **auto**：Auto Router 根据当前需求同时选择模型与推理等级（默认关闭） |
| `"provider/model"`（如 `openai/gpt-5.6-terra`） | **explicit**：严格使用指定模型；模型默认推理等级取自 `autoModelRouting.thinkingDefaults`，未命中则交给 OpenClaw 解析 |

规则：

- `auto` 是模式标识，**不会**被当作真实模型名传给 OpenClaw；功能未开启或 Agent 不在白名单时按 default 处理。
- 首行控制命令（`/model`、`/think`、`/reset`、`/stop` 等）忽略 IM 模型元数据。
- 用户显式 `/think`（当前消息行内或 Session 已持久化）**优先于** Auto Router / 模型默认的 thinking：此时只路由模型，不设置 `thinkingLevelOverride`。
- 单轮覆盖不写入 Session；Auto Router 为本地确定性纯函数，不调用大模型分类。

`autoModelRouting` 配置示例：

```json
{
  "channels": {
    "xg_cwork_im": {
      "baseUrl": "https://im.example.com",
      "robotKey": "…",
      "autoModelRouting": {
        "enabled": false,
        "version": "v1",
        "simpleModel": "openai/gpt-5.6-luna",
        "standardModel": "openai/gpt-5.6-terra",
        "complexModel": "openai/gpt-5.6-sol",
        "criticalModel": "openai/gpt-5.6-sol",
        "thinkingDefaults": {
          "openai/gpt-5.6-luna": "low",
          "openai/gpt-5.6-terra": "medium",
          "openai/gpt-5.6-sol": "high"
        },
        "agentAllowlist": ["assistant-1514822107563462658"]
      }
    }
  }
}
```

- `enabled`：总开关，默认 `false`。
- `agentAllowlist`：允许自动路由的 Agent ID；缺省/为空表示**不启用任何 Agent**（需显式加入后灰度）。
- `simpleModel` / `standardModel` / `complexModel` / `criticalModel`：Auto Router 仅输出这四个槽位；任一缺失则该轮回退 default。
- `thinkingDefaults`：explicit 模式下各 model ref 的默认推理等级（OpenClaw 合法取值：`off|minimal|low|medium|high|xhigh|adaptive|max`）。


### 账户配置（`accounts.<accountId>`，对象 map）


| 选项                              | 类型     | 默认值      | 说明                                   |
| ------------------------------- | ------ | -------- | ------------------------------------ |
| `robotKey`                      | string | **必填**   | 机器人标识（IM 后台注册获取）；旧字段 `appKey` 仍兼容    |
| `agentId`                       | string | `"main"` | 对应的 OpenClaw Agent ID                |
| `name`                          | string | —        | 账户显示名称（仅用于日志标识）                      |
| `groupPolicy`                   | string | 继承顶层     | 可覆盖顶层的 groupPolicy                   |
| `inboundMediaWorkspaceSubdir`   | string | 继承顶层     | 可覆盖顶层的入站附件落盘子目录                      |
| `inboundMediaOpenClawPath`      | string | 继承顶层     | 可覆盖顶层的 MediaPath 形式（相对路径 vs file://） |
| `inboundMediaSandboxRootPrefix` | string | 继承顶层     | 可覆盖沙箱根前缀（如 `/workspace`）             |
| `syncServiceBaseUrl`            | string | 继承顶层     | 可覆盖同步服务管理 API 地址                     |
| `projectPromptMaxChars`         | number | 继承顶层     | 项目/用户 background 单段最大字符数（默认 15000）   |
| `nasVolumeRootMappings`         | object[] | 继承顶层   | 可覆盖 NAS 逻辑根到本机真实根的映射                  |
| `autoModelRouting`              | object | 继承顶层     | 可覆盖顶层的自动路由配置（见上节）                |


### Agent 工具（`optional: true`）

`xg_cwork_im_get_group_chat_history` 与 `xg_cwork_im_send_group_message` 在 `index.ts` 中以 `**api.registerTool(..., { optional: true })`** 注册。需在对应 Agent 的 `**tools.allow**`（或你们环境中的工具白名单配置）中**显式加入**上述工具名后，模型才会在运行时获得它们。

---

## 故障排除

排查「无回复 / Media failed / 断连」时，请先对照 **[可靠性路线图](docs/reliability-roadmap.md)** 中的已知现状与日志字段。

### 收不到消息

- 确认 `robotKey` 正确（或旧字段 `appKey` 正确），且机器人已在 IM 后台激活
- 查看 OpenClaw 日志，确认 WebSocket 连接成功（`WebSocket connected successfully`）
- 群聊中确认已 `@` 机器人

### WebSocket 频繁断连

- 检查网络稳定性
- 适当增大 `maxConnectionAttempts` 和 `maxReconnectDelay`
- 开启 `debug: true` 查看详细日志

### token 相关报错

- 确认 `baseUrl` 域名可达
- 确认 `robotKey` 有效（或旧字段 `appKey` 有效，可在 IM 后台查看机器人状态）

### 网关报 `plugin must declare contracts.tools before registering agent tools`

OpenClaw 2026+ 要求：在 `register()` 里调用 `api.registerTool(...)` 的工具名，必须预先写在 `**openclaw.plugin.json`** 的 `**contracts.tools**` 中（与工具对象的 `name` 一致）。本仓库已列出 `xg_cwork_im_get_group_chat_history`、`xg_cwork_im_send_group_message`。若你以后新增工具，请同步扩展 manifest。

### 占位消息后变「AI 未响应」、网关日志 `invoke=0`、但网页客户端有助手回复（OpenClaw 2026.x）

原因：默认群聊在未配置 `**messages.visibleReplies`/`messages.groupChat.visibleReplies` = `automatic**` 时，OpenClaw 会启用 `**message_tool_only**`（只通过 message 类工具对用户可见），从而 **不向 Channel 插件的 `deliver` 投递**，模型仍会写入会话网页可见，但 **本插件不会 POST `/im/message/send`**，`msgId` 合并也不会发生。

当前插件已在 `dispatchMentionedReply` 中对本轮强制 `**replyOptions.sourceReplyDeliveryMode: "automatic"**`，一般无需再在 `openclaw.json` 里改该项；若你仍遇到问题，可把 `**messages.groupChat.visibleReplies**` 设为 `**"automatic"**` 做一次全局对齐。

---

## 项目结构

```
openclaw-channel-xg-cwork-im/
├── index.ts                # 插件入口
├── src/
│   ├── types.ts            # 类型定义
│   ├── auth.ts             # 认证（robotKey/appKey → token）
│   ├── connection.ts       # WebSocket 连接管理
│   ├── send-service.ts     # IM 消息发送
│   ├── group-history-tool.ts      # 拉取群消息历史的 Agent Tool
│   ├── send-group-message-tool.ts # 发送群消息的 Agent Tool
│   ├── inbound-media-local.ts   # 可选：入站附件落盘到 workspace 子目录
│   ├── channel.ts          # Channel 组装（meta / config / outbound / gateway）
│   ├── channel-config.ts   # Zod schema、getXgImConfig、GroupSystemPrompt、命令门控
│   ├── channel-log.ts      # toLogger 适配
│   ├── inbound-display.ts  # 入站正文、@ 判定、MediaPath(s)、UntrustedContext
│   ├── dispatch-mentioned-reply.ts  # OpenClaw 派发 + 流式 + HTTP deliver（忙碌排队由 OpenClaw messages.queue 处理）
├── package.json
├── tsconfig.json
├── openclaw.plugin.json
└── README.md
```

---

## 代码仓库与发布流程说明

- **代码仓库**：本项目在**内部 Git** 管理，地址：`http://192.168.3.200/openclaw/openclaw-channel-xg-cwork-im`，不推送到 GitHub。
- **安装方式**：同事安装请优先使用 **npm**（方法 A）；需要改源码时从内部 Git 克隆（方法 B）。
- **发布流程**：在内部 Git 上开发、提交后，由维护者**发布到 npm**；同事通过 `openclaw plugins install @xgjktech/xg_cwork_im` 安装，无需访问 Git。

---

## 维护者：如何发布到 npm（详细步骤）

代码在内部 Git 上维护，发布到 npm 后同事即可一条命令安装。按下列步骤操作。

### 步骤 1：在 npm 注册 / 登录（仅本次发布走官方）

你可以保留系统默认镜像不变；只在本项目发布时显式指定官方 registry。

1. 无 npm 账号：打开 [https://www.npmjs.com/signup](https://www.npmjs.com/signup) 注册。
2. 在项目根目录执行（不会改全局配置）：

```bash
npm login --registry=https://registry.npmjs.org/
```

1. 登录成功后校验当前身份（同样只看官方 registry）：

```bash
npm whoami --registry=https://registry.npmjs.org/
```

1. 若账号开启了**双因素认证（2FA）**，发布时必须使用：
  - 在 npm 网站 **Access Tokens** 里创建 **Granular Access Token**，勾选 **Publish packages**，并勾选 **Bypass 2FA**；或  
  - 在发布时输入 **OTP**（一次性密码）。  
   否则会报错：`Two-factor authentication or granular access token with bypass 2fa enabled is required to publish packages`。

### 步骤 2：确认 scope 与权限

- 包名为 `@xgjktech/xg_cwork_im`，属于 **scope** `@xgjktech`。
- 在 [npm 官网](https://www.npmjs.com/) 登录 → **Organizations** → **Create**，创建组织名为 **xgjktech**，确保你的账号有该组织的发布权限。
- 若改用个人 scope（如 `@你的用户名/xg_cwork_im`），需修改 `package.json` 的 `"name"` 并同步改 README 中的安装命令。

### 步骤 3：发布前检查

在项目根目录执行：

```bash
npm pack --dry-run
```

确认列出的文件无敏感内容（如 `.env`、密钥）。发布内容由 `package.json` 的 `"files"` 控制。

### 步骤 4：执行发布（强制官方 registry）

**scoped 包必须加 `--access public`**，否则会按私有包计费并可能报 402：

```bash
npm publish --access public --registry=https://registry.npmjs.org/
```

成功后可在此查看：  
[https://www.npmjs.com/package/@xgjktech/xg_cwork_im](https://www.npmjs.com/package/@xgjktech/xg_cwork_im)

### 步骤 5：后续更新（重新推送到 npm）

每次发新版本都按下面做一遍即可：

1. 在内部 Git 上改代码、提交、推送。
2. 在 **package.json** 里把 **version** 升高（如 `1.0.0` → `1.0.1`），保存。
3. 在项目根目录执行：

```bash
npm publish --access public --registry=https://registry.npmjs.org/
```

1. 通知同事执行 **更新已安装的插件**（见上文「更新已安装的插件」），例如：
  `openclaw plugins update xg_cwork_im` → `openclaw gateway restart`。

（若该 scope 已发过公开包，有时可只执行 `npm publish`，建议仍带 `--access public` 避免误发成私有。）

### 常见问题


| 报错或现象                              | 原因                                 | 处理                                                                                                                  |
| ---------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **403 Forbidden**，且提示需 2FA 或 token | 账号开启 2FA 后，普通登录无法发布                | 使用 **Granular Access Token**（勾选 Publish + Bypass 2FA），或在发布时提供 OTP                                                   |
| **403 Forbidden**，无 2FA 提示         | 当前账号无 `@xgjktech` 发布权限             | 在 npm 创建/加入组织 `xgjktech`，或将包名改为个人 scope                                                                             |
| **402 Payment Required**           | 未加 `--access public`，scoped 包被视为私有 | 执行 `npm publish --access public`                                                                                    |
| 国内网络登录/发布慢或超时                      | 默认 registry 在国外                    | 登录：`npm login --registry=https://registry.npmjs.org/`；发布时也可加 `--registry=https://registry.npmjs.org/`，不推荐长期用第三方镜像发布 |
