@zeyiy/openclaw-channel 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -95
- package/README.zh-CN.md +68 -94
- package/dist/clients.js +25 -2
- package/dist/config.js +0 -37
- package/dist/polyfills.d.ts +1 -9
- package/dist/polyfills.js +12 -1
- package/dist/portal.js +47 -81
- package/dist/setup.js +3 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,56 +1,46 @@
|
|
|
1
1
|
# @zeyiy/openclaw-channel
|
|
2
2
|
|
|
3
|
-
OpenIM channel plugin for OpenClaw Gateway.
|
|
3
|
+
OpenIM channel plugin for [OpenClaw](https://openclaw.ai) Gateway.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Chinese documentation: [README.zh-CN.md](https://github.com/ZeyiY/openclaw-channel/blob/main/README.zh-CN.md)
|
|
5
|
+
Connects OpenClaw agents to the [OpenIM](https://www.openim.io/) messaging platform, enabling AI-powered conversations in direct chats and group chats.
|
|
8
6
|
|
|
9
7
|
## Features
|
|
10
8
|
|
|
11
9
|
- Direct chat and group chat support
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
10
|
+
- Text / image / file / video message send & receive
|
|
11
|
+
- Quote/reply message context parsing
|
|
12
|
+
- Multi-account concurrent login
|
|
13
|
+
- Group trigger with optional mention-only mode
|
|
14
|
+
- Per-user session isolation (direct) / shared session (group)
|
|
17
15
|
- Auto read-receipt for direct messages
|
|
18
|
-
-
|
|
19
|
-
- Agent Portal Bridge — persistent WebSocket
|
|
20
|
-
- Interactive setup
|
|
16
|
+
- Inbound sender whitelist (optional)
|
|
17
|
+
- **Agent Portal Bridge** — persistent WebSocket to agent-portal for remote agent management
|
|
18
|
+
- Interactive setup: `openclaw openim setup`
|
|
21
19
|
|
|
22
20
|
## Installation
|
|
23
21
|
|
|
24
|
-
Install from npm:
|
|
25
|
-
|
|
26
22
|
```bash
|
|
27
23
|
openclaw plugins install @zeyiy/openclaw-channel
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
Or install from local path:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
openclaw plugins install /path/to/openclaw-channel
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Repository: https://github.com/ZeyiY/openclaw-channel
|
|
37
|
-
|
|
38
26
|
## Identity Mapping
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
| Item | Value |
|
|
29
|
+
|------|-------|
|
|
30
|
+
| npm package | `@zeyiy/openclaw-channel` |
|
|
31
|
+
| plugin id | `openclaw-channel` |
|
|
32
|
+
| channel id | `openim` |
|
|
33
|
+
| setup command | `openclaw openim setup` |
|
|
44
34
|
|
|
45
35
|
## Configuration
|
|
46
36
|
|
|
47
|
-
###
|
|
37
|
+
### Interactive setup (recommended)
|
|
48
38
|
|
|
49
39
|
```bash
|
|
50
40
|
openclaw openim setup
|
|
51
41
|
```
|
|
52
42
|
|
|
53
|
-
###
|
|
43
|
+
### Manual — edit `~/.openclaw/openclaw.json`
|
|
54
44
|
|
|
55
45
|
```json
|
|
56
46
|
{
|
|
@@ -59,11 +49,11 @@ openclaw openim setup
|
|
|
59
49
|
"accounts": {
|
|
60
50
|
"default": {
|
|
61
51
|
"enabled": true,
|
|
62
|
-
"token": "
|
|
63
|
-
"wsAddr": "
|
|
64
|
-
"apiAddr": "
|
|
65
|
-
"botId": "
|
|
66
|
-
"portalWsAddr": "wss://portal.example.com/ws"
|
|
52
|
+
"token": "your_jwt_token",
|
|
53
|
+
"wsAddr": "wss://your-openim-server/msg_gateway",
|
|
54
|
+
"apiAddr": "https://your-openim-server/api",
|
|
55
|
+
"botId": "your-bot-id",
|
|
56
|
+
"portalWsAddr": "wss://agent-portal.example.com/ws/workspace"
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
}
|
|
@@ -71,87 +61,70 @@ openclaw openim setup
|
|
|
71
61
|
}
|
|
72
62
|
```
|
|
73
63
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`requireMention` is optional and defaults to `true`.
|
|
64
|
+
### Field reference
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
| Field | Required | Description |
|
|
67
|
+
|-------|----------|-------------|
|
|
68
|
+
| `token` | Yes | OpenIM JWT token |
|
|
69
|
+
| `wsAddr` | Yes | OpenIM WebSocket endpoint |
|
|
70
|
+
| `apiAddr` | Yes | OpenIM REST API endpoint |
|
|
71
|
+
| `userID` | No | Auto-derived from JWT `UserID` claim |
|
|
72
|
+
| `platformID` | No | Auto-derived from JWT `PlatformID` claim |
|
|
73
|
+
| `enabled` | No | Default `true` |
|
|
74
|
+
| `requireMention` | No | Require @mention in groups, default `true` |
|
|
75
|
+
| `inboundWhitelist` | No | Only process messages from listed user IDs |
|
|
76
|
+
| `botId` | No | Bot ID for agent-portal connection |
|
|
77
|
+
| `portalWsAddr` | No | Agent-portal WebSocket endpoint |
|
|
78
|
+
| `historyLimit` | No | Chat history context limit, default `20` |
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
Single-account shorthand (without `accounts` wrapper) is also supported.
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Environment fallback is supported for the `default` account:
|
|
88
|
-
|
|
89
|
-
- `OPENIM_TOKEN`
|
|
90
|
-
- `OPENIM_WS_ADDR`
|
|
91
|
-
- `OPENIM_API_ADDR`
|
|
92
|
-
|
|
93
|
-
Optional env overrides:
|
|
94
|
-
|
|
95
|
-
- `OPENIM_USER_ID`
|
|
96
|
-
- `OPENIM_PLATFORM_ID`
|
|
82
|
+
> **Note:** Environment variable fallback has been removed. All configuration is via `openclaw.json`.
|
|
97
83
|
|
|
98
84
|
## Agent Tools
|
|
99
85
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- `accountId` (optional): select sending account
|
|
109
|
-
|
|
110
|
-
- `openim_send_video`
|
|
111
|
-
- `target`: `user:<id>` or `group:<id>`
|
|
112
|
-
- `video`: local path (`file://` supported) or `http(s)` URL
|
|
113
|
-
- behavior: sent as a file message (not OpenIM video message)
|
|
114
|
-
- `name` (optional): override filename for URL input
|
|
115
|
-
- `accountId` (optional): select sending account
|
|
116
|
-
|
|
117
|
-
- `openim_send_file`
|
|
118
|
-
- `target`: `user:<id>` or `group:<id>`
|
|
119
|
-
- `file`: local path (`file://` supported) or `http(s)` URL
|
|
120
|
-
- `name` (optional): override filename for URL input
|
|
121
|
-
- `accountId` (optional): select sending account
|
|
86
|
+
| Tool | Parameters | Description |
|
|
87
|
+
|------|-----------|-------------|
|
|
88
|
+
| `openim_send_text` | `target`, `text`, `accountId?` | Send text message |
|
|
89
|
+
| `openim_send_image` | `target`, `image`, `accountId?` | Send image (local path or URL) |
|
|
90
|
+
| `openim_send_file` | `target`, `file`, `name?`, `accountId?` | Send file (local path or URL) |
|
|
91
|
+
| `openim_send_video` | `target`, `video`, `name?`, `accountId?` | Send video as file message |
|
|
92
|
+
|
|
93
|
+
`target` format: `user:<id>` or `group:<id>`
|
|
122
94
|
|
|
123
95
|
## Agent Portal Bridge
|
|
124
96
|
|
|
125
|
-
When `botId` and `portalWsAddr` are configured, the plugin
|
|
97
|
+
When `botId` and `portalWsAddr` are configured, the plugin maintains a WebSocket connection to agent-portal, enabling remote management:
|
|
126
98
|
|
|
127
99
|
| Method | Description |
|
|
128
|
-
|
|
129
|
-
| `bot.agent.get` | Resolve
|
|
130
|
-
| `models.list` | List available models
|
|
131
|
-
| `agents.list` | List
|
|
132
|
-
| `agents.create` | Create
|
|
133
|
-
| `agents.files.list` | List workspace files
|
|
134
|
-
| `agents.files.get` | Read
|
|
135
|
-
| `agents.files.set` | Write
|
|
100
|
+
|--------|-------------|
|
|
101
|
+
| `bot.agent.get` | Resolve bot's bound agent |
|
|
102
|
+
| `models.list` | List available models |
|
|
103
|
+
| `agents.list` | List configured agents |
|
|
104
|
+
| `agents.create` | Create new agent + workspace |
|
|
105
|
+
| `agents.files.list` | List agent workspace files |
|
|
106
|
+
| `agents.files.get` | Read workspace file |
|
|
107
|
+
| `agents.files.set` | Write workspace file |
|
|
136
108
|
| `tools.catalog` | List available tools |
|
|
137
|
-
| `skills.status` |
|
|
138
|
-
| `skills.
|
|
139
|
-
| `skills.
|
|
140
|
-
| `
|
|
109
|
+
| `skills.status` | Skill/plugin status |
|
|
110
|
+
| `agent.skills.status` | Per-agent skill status with whitelist |
|
|
111
|
+
| `agent.skills.set` | Enable/disable skill for agent |
|
|
112
|
+
| `agent.model.set` | Switch agent model |
|
|
113
|
+
| `skills.search` | Search ClawHub registry |
|
|
114
|
+
| `skills.install` | Install skill from ClawHub or URL |
|
|
115
|
+
| `skills.set` | Enable/disable skill globally |
|
|
116
|
+
| `cron.list` | List cron jobs |
|
|
141
117
|
|
|
142
|
-
|
|
118
|
+
Features auto-reconnect with exponential backoff and heartbeat keepalive.
|
|
143
119
|
|
|
144
120
|
## Development
|
|
145
121
|
|
|
146
122
|
```bash
|
|
123
|
+
pnpm install
|
|
147
124
|
pnpm run build
|
|
148
|
-
pnpm run test:connect
|
|
125
|
+
pnpm run test:connect # configure .env first
|
|
149
126
|
```
|
|
150
127
|
|
|
151
|
-
For `test:connect`, configure `.env` first (see `.env.example`).
|
|
152
|
-
|
|
153
128
|
## License
|
|
154
129
|
|
|
155
|
-
AGPL-3.0-only. See [LICENSE](
|
|
156
|
-
|
|
157
|
-
Originally developed by [openimsdk](https://github.com/openimsdk/openclaw-channel).
|
|
130
|
+
AGPL-3.0-only. See [LICENSE](./LICENSE).
|
package/README.zh-CN.md
CHANGED
|
@@ -1,56 +1,46 @@
|
|
|
1
1
|
# @zeyiy/openclaw-channel
|
|
2
2
|
|
|
3
|
-
OpenClaw Gateway 的 OpenIM 渠道插件。
|
|
3
|
+
[OpenClaw](https://openclaw.ai) Gateway 的 OpenIM 渠道插件。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
English documentation: [README.md](https://github.com/ZeyiY/openclaw-channel/blob/main/README.md)
|
|
5
|
+
将 OpenClaw 智能体接入 [OpenIM](https://www.openim.io/) 即时通讯平台,支持私聊和群聊中的 AI 对话。
|
|
8
6
|
|
|
9
7
|
## 功能
|
|
10
8
|
|
|
11
9
|
- 支持私聊与群聊
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- 支持群聊仅 @ 触发
|
|
17
|
-
- 私聊消息自动标记已读(已读回执)
|
|
10
|
+
- 文本 / 图片 / 文件 / 视频消息收发
|
|
11
|
+
- 引用消息上下文解析
|
|
12
|
+
- 多账号并发登录
|
|
13
|
+
- 群聊 @触发模式(可选)
|
|
18
14
|
- 每用户独立会话(私聊)/ 同群共享会话(群聊)
|
|
19
|
-
-
|
|
20
|
-
-
|
|
15
|
+
- 私聊自动已读回执
|
|
16
|
+
- 入站发送者白名单(可选)
|
|
17
|
+
- **Agent Portal Bridge** — 与 agent-portal 云服务保持 WebSocket 长连接,支持远程管理
|
|
18
|
+
- 交互式配置命令:`openclaw openim setup`
|
|
21
19
|
|
|
22
20
|
## 安装
|
|
23
21
|
|
|
24
|
-
从 npm 安装:
|
|
25
|
-
|
|
26
22
|
```bash
|
|
27
23
|
openclaw plugins install @zeyiy/openclaw-channel
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
本地路径安装:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
openclaw plugins install /path/to/openclaw-channel
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
仓库地址:https://github.com/ZeyiY/openclaw-channel
|
|
37
|
-
|
|
38
26
|
## 标识说明
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
| 项目 | 值 |
|
|
29
|
+
|------|-----|
|
|
30
|
+
| npm 包名 | `@zeyiy/openclaw-channel` |
|
|
31
|
+
| 插件 ID | `openclaw-channel` |
|
|
32
|
+
| 渠道 ID | `openim` |
|
|
33
|
+
| 配置命令 | `openclaw openim setup` |
|
|
44
34
|
|
|
45
35
|
## 配置
|
|
46
36
|
|
|
47
|
-
###
|
|
37
|
+
### 交互式配置(推荐)
|
|
48
38
|
|
|
49
39
|
```bash
|
|
50
40
|
openclaw openim setup
|
|
51
41
|
```
|
|
52
42
|
|
|
53
|
-
###
|
|
43
|
+
### 手动编辑 `~/.openclaw/openclaw.json`
|
|
54
44
|
|
|
55
45
|
```json
|
|
56
46
|
{
|
|
@@ -59,11 +49,11 @@ openclaw openim setup
|
|
|
59
49
|
"accounts": {
|
|
60
50
|
"default": {
|
|
61
51
|
"enabled": true,
|
|
62
|
-
"token": "
|
|
63
|
-
"wsAddr": "
|
|
64
|
-
"apiAddr": "
|
|
65
|
-
"botId": "
|
|
66
|
-
"portalWsAddr": "wss://portal.example.com/ws"
|
|
52
|
+
"token": "your_jwt_token",
|
|
53
|
+
"wsAddr": "wss://your-openim-server/msg_gateway",
|
|
54
|
+
"apiAddr": "https://your-openim-server/api",
|
|
55
|
+
"botId": "your-bot-id",
|
|
56
|
+
"portalWsAddr": "wss://agent-portal.example.com/ws/workspace"
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
}
|
|
@@ -71,86 +61,70 @@ openclaw openim setup
|
|
|
71
61
|
}
|
|
72
62
|
```
|
|
73
63
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`requireMention` 为可选项,默认 `true`。
|
|
64
|
+
### 字段说明
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
| 字段 | 必填 | 说明 |
|
|
67
|
+
|------|------|------|
|
|
68
|
+
| `token` | 是 | OpenIM JWT token |
|
|
69
|
+
| `wsAddr` | 是 | OpenIM WebSocket 端点 |
|
|
70
|
+
| `apiAddr` | 是 | OpenIM REST API 端点 |
|
|
71
|
+
| `userID` | 否 | 自动从 JWT `UserID` 声明解析 |
|
|
72
|
+
| `platformID` | 否 | 自动从 JWT `PlatformID` 声明解析 |
|
|
73
|
+
| `enabled` | 否 | 默认 `true` |
|
|
74
|
+
| `requireMention` | 否 | 群聊需 @触发,默认 `true` |
|
|
75
|
+
| `inboundWhitelist` | 否 | 仅处理指定用户 ID 的消息 |
|
|
76
|
+
| `botId` | 否 | 用于 agent-portal 连接的 Bot ID |
|
|
77
|
+
| `portalWsAddr` | 否 | Agent-portal WebSocket 端点 |
|
|
78
|
+
| `historyLimit` | 否 | 聊天历史上下文条数,默认 `20` |
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
支持单账号简写(不使用 `accounts` 包装)。
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`default` 账号支持环境变量兜底:
|
|
87
|
-
|
|
88
|
-
- `OPENIM_TOKEN`
|
|
89
|
-
- `OPENIM_WS_ADDR`
|
|
90
|
-
- `OPENIM_API_ADDR`
|
|
91
|
-
|
|
92
|
-
可选环境变量覆盖项:
|
|
93
|
-
|
|
94
|
-
- `OPENIM_USER_ID`
|
|
95
|
-
- `OPENIM_PLATFORM_ID`
|
|
82
|
+
> **注意:** 已移除环境变量配置方式,所有配置通过 `openclaw.json` 完成。
|
|
96
83
|
|
|
97
84
|
## Agent 工具
|
|
98
85
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- `accountId`(可选):指定发送账号
|
|
108
|
-
|
|
109
|
-
- `openim_send_video`
|
|
110
|
-
- `target`: `user:<id>` 或 `group:<id>`
|
|
111
|
-
- `video`: 本地路径(支持 `file://`)或 `http(s)` URL
|
|
112
|
-
- 行为:按文件消息发送(不是视频消息)
|
|
113
|
-
- `name`(可选):URL 输入时覆盖文件名
|
|
114
|
-
- `accountId`(可选):指定发送账号
|
|
115
|
-
|
|
116
|
-
- `openim_send_file`
|
|
117
|
-
- `target`: `user:<id>` 或 `group:<id>`
|
|
118
|
-
- `file`: 本地路径(支持 `file://`)或 `http(s)` URL
|
|
119
|
-
- `name`(可选):URL 输入时覆盖文件名
|
|
120
|
-
- `accountId`(可选):指定发送账号
|
|
86
|
+
| 工具 | 参数 | 说明 |
|
|
87
|
+
|------|------|------|
|
|
88
|
+
| `openim_send_text` | `target`, `text`, `accountId?` | 发送文本消息 |
|
|
89
|
+
| `openim_send_image` | `target`, `image`, `accountId?` | 发送图片(本地路径或 URL) |
|
|
90
|
+
| `openim_send_file` | `target`, `file`, `name?`, `accountId?` | 发送文件(本地路径或 URL) |
|
|
91
|
+
| `openim_send_video` | `target`, `video`, `name?`, `accountId?` | 发送视频(按文件消息发送) |
|
|
92
|
+
|
|
93
|
+
`target` 格式:`user:<id>` 或 `group:<id>`
|
|
121
94
|
|
|
122
95
|
## Agent Portal Bridge
|
|
123
96
|
|
|
124
|
-
配置 `botId` 和 `portalWsAddr`
|
|
97
|
+
配置 `botId` 和 `portalWsAddr` 后,插件与 agent-portal 保持 WebSocket 连接,支持远程管理:
|
|
125
98
|
|
|
126
99
|
| 方法 | 说明 |
|
|
127
|
-
|
|
128
|
-
| `bot.agent.get` |
|
|
129
|
-
| `models.list` |
|
|
130
|
-
| `agents.list` |
|
|
131
|
-
| `agents.create` |
|
|
132
|
-
| `agents.files.list` |
|
|
133
|
-
| `agents.files.get` |
|
|
134
|
-
| `agents.files.set` |
|
|
100
|
+
|------|------|
|
|
101
|
+
| `bot.agent.get` | 获取 bot 绑定的 agent |
|
|
102
|
+
| `models.list` | 列出可用模型 |
|
|
103
|
+
| `agents.list` | 列出已配置的 agent |
|
|
104
|
+
| `agents.create` | 创建 agent + 工作空间 |
|
|
105
|
+
| `agents.files.list` | 列出工作空间文件 |
|
|
106
|
+
| `agents.files.get` | 读取工作空间文件 |
|
|
107
|
+
| `agents.files.set` | 写入工作空间文件 |
|
|
135
108
|
| `tools.catalog` | 列出可用工具 |
|
|
136
|
-
| `skills.status` |
|
|
137
|
-
| `skills.
|
|
138
|
-
| `skills.
|
|
139
|
-
| `
|
|
109
|
+
| `skills.status` | 技能/插件状态 |
|
|
110
|
+
| `agent.skills.status` | Agent 级技能状态(含白名单) |
|
|
111
|
+
| `agent.skills.set` | 启用/禁用 agent 技能 |
|
|
112
|
+
| `agent.model.set` | 切换 agent 模型 |
|
|
113
|
+
| `skills.search` | 搜索 ClawHub 技能 |
|
|
114
|
+
| `skills.install` | 从 ClawHub 或 URL 安装技能 |
|
|
115
|
+
| `skills.set` | 全局启用/禁用技能 |
|
|
116
|
+
| `cron.list` | 列出定时任务 |
|
|
140
117
|
|
|
141
|
-
|
|
118
|
+
支持指数退避自动重连和心跳保活。
|
|
142
119
|
|
|
143
120
|
## 开发
|
|
144
121
|
|
|
145
122
|
```bash
|
|
123
|
+
pnpm install
|
|
146
124
|
pnpm run build
|
|
147
|
-
pnpm run test:connect
|
|
125
|
+
pnpm run test:connect # 先配置 .env
|
|
148
126
|
```
|
|
149
127
|
|
|
150
|
-
运行 `test:connect` 前请先配置 `.env`(参考 `.env.example`)。
|
|
151
|
-
|
|
152
128
|
## 许可证
|
|
153
129
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
原始项目由 [openimsdk](https://github.com/openimsdk/openclaw-channel) 开发。
|
|
130
|
+
AGPL-3.0-only,详见 [LICENSE](./LICENSE)。
|
package/dist/clients.js
CHANGED
|
@@ -3,6 +3,7 @@ import { processInboundMessage } from "./inbound";
|
|
|
3
3
|
import { startPortalBridge, stopPortalBridge, stopAllPortalBridges } from "./portal";
|
|
4
4
|
import { formatSdkError } from "./utils";
|
|
5
5
|
const clients = new Map();
|
|
6
|
+
const loginInProgress = new Set();
|
|
6
7
|
function detachHandlers(state) {
|
|
7
8
|
state.sdk.off(CbEvents.OnRecvNewMessage, state.handlers.onRecvNewMessage);
|
|
8
9
|
state.sdk.off(CbEvents.OnRecvNewMessages, state.handlers.onRecvNewMessages);
|
|
@@ -21,6 +22,26 @@ export function connectedClientCount() {
|
|
|
21
22
|
return clients.size;
|
|
22
23
|
}
|
|
23
24
|
export async function startAccountClient(api, config) {
|
|
25
|
+
if (clients.has(config.accountId) || loginInProgress.has(config.accountId)) {
|
|
26
|
+
api.logger?.debug?.(`[openim] account ${config.accountId} already connected or login in progress, skipping`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
loginInProgress.add(config.accountId);
|
|
30
|
+
// Diagnostic: test raw WebSocket before SDK login
|
|
31
|
+
const WS = globalThis.WebSocket;
|
|
32
|
+
api.logger?.info?.(`[openim] WebSocket global: ${typeof WS}, constructor: ${WS?.name ?? "N/A"}`);
|
|
33
|
+
try {
|
|
34
|
+
const testWs = new WS(config.wsAddr);
|
|
35
|
+
await new Promise((resolve, reject) => {
|
|
36
|
+
const timer = setTimeout(() => { testWs.close(); reject(new Error("timeout")); }, 5000);
|
|
37
|
+
testWs.onopen = () => { clearTimeout(timer); testWs.close(); resolve(); };
|
|
38
|
+
testWs.onerror = (e) => { clearTimeout(timer); reject(e); };
|
|
39
|
+
});
|
|
40
|
+
api.logger?.info?.(`[openim] raw WebSocket test to ${config.wsAddr}: OK`);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
api.logger?.error?.(`[openim] raw WebSocket test to ${config.wsAddr}: FAILED — ${e?.message ?? e}`);
|
|
44
|
+
}
|
|
24
45
|
const sdk = getSDK();
|
|
25
46
|
const state = {
|
|
26
47
|
sdk,
|
|
@@ -62,14 +83,16 @@ export async function startAccountClient(api, config) {
|
|
|
62
83
|
platformID: config.platformID,
|
|
63
84
|
});
|
|
64
85
|
clients.set(config.accountId, state);
|
|
86
|
+
loginInProgress.delete(config.accountId);
|
|
65
87
|
api.logger?.info?.(`[openim] account ${config.accountId} connected`);
|
|
66
|
-
// Start portal bridge after successful OpenIM login
|
|
67
|
-
startPortalBridge(api, config);
|
|
68
88
|
}
|
|
69
89
|
catch (e) {
|
|
70
90
|
detachHandlers(state);
|
|
91
|
+
loginInProgress.delete(config.accountId);
|
|
71
92
|
api.logger?.error?.(`[openim] account ${config.accountId} login failed: ${formatSdkError(e)}`);
|
|
72
93
|
}
|
|
94
|
+
// Start portal bridge regardless of SDK login result — portal is an independent service
|
|
95
|
+
startPortalBridge(api, config);
|
|
73
96
|
}
|
|
74
97
|
export async function stopAccountClient(api, accountId) {
|
|
75
98
|
// Stop portal bridge before disconnecting OpenIM
|
package/dist/config.js
CHANGED
|
@@ -41,32 +41,6 @@ function extractAccountHintsFromToken(token) {
|
|
|
41
41
|
...(Number.isFinite(platformID) ? { platformID } : {}),
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function envDefaultAccount() {
|
|
45
|
-
const token = String(process.env.OPENIM_TOKEN ?? "").trim();
|
|
46
|
-
const wsAddr = String(process.env.OPENIM_WS_ADDR ?? "").trim();
|
|
47
|
-
const apiAddr = String(process.env.OPENIM_API_ADDR ?? "").trim();
|
|
48
|
-
if (!token || !wsAddr || !apiAddr)
|
|
49
|
-
return null;
|
|
50
|
-
const hints = extractAccountHintsFromToken(token);
|
|
51
|
-
const userID = String(process.env.OPENIM_USER_ID ?? hints.userID ?? "").trim();
|
|
52
|
-
const platformID = toFiniteNumber(process.env.OPENIM_PLATFORM_ID ?? hints.platformID, 5);
|
|
53
|
-
if (!userID)
|
|
54
|
-
return null;
|
|
55
|
-
const botId = String(process.env.OPENIM_BOT_ID ?? "").trim() || undefined;
|
|
56
|
-
const portalWsAddr = String(process.env.OPENIM_PORTAL_WS_ADDR ?? "").trim() || undefined;
|
|
57
|
-
return {
|
|
58
|
-
userID,
|
|
59
|
-
token,
|
|
60
|
-
wsAddr,
|
|
61
|
-
apiAddr,
|
|
62
|
-
platformID,
|
|
63
|
-
enabled: true,
|
|
64
|
-
requireMention: true,
|
|
65
|
-
historyLimit: 20,
|
|
66
|
-
botId,
|
|
67
|
-
portalWsAddr,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
44
|
function normalizeInboundWhitelist(raw) {
|
|
71
45
|
const values = Array.isArray(raw)
|
|
72
46
|
? raw
|
|
@@ -120,8 +94,6 @@ export function listAccountIds(apiOrCfg) {
|
|
|
120
94
|
}
|
|
121
95
|
if (ch?.userID || ch?.token || ch?.wsAddr || ch?.apiAddr)
|
|
122
96
|
return ["default"];
|
|
123
|
-
if (envDefaultAccount())
|
|
124
|
-
return ["default"];
|
|
125
97
|
return [];
|
|
126
98
|
}
|
|
127
99
|
export function getOpenIMAccountConfig(apiOrCfg, accountId = "default") {
|
|
@@ -136,10 +108,6 @@ export function getOpenIMAccountConfig(apiOrCfg, accountId = "default") {
|
|
|
136
108
|
if (normalized)
|
|
137
109
|
return normalized;
|
|
138
110
|
}
|
|
139
|
-
const env = envDefaultAccount();
|
|
140
|
-
if (env) {
|
|
141
|
-
return normalizeAccount("default", env);
|
|
142
|
-
}
|
|
143
111
|
}
|
|
144
112
|
return null;
|
|
145
113
|
}
|
|
@@ -162,10 +130,5 @@ export function resolveAccountConfig(apiOrCfg, accountId) {
|
|
|
162
130
|
if (id === "default" && (ch?.userID || ch?.token || ch?.wsAddr || ch?.apiAddr)) {
|
|
163
131
|
return { accountId: id, ...ch };
|
|
164
132
|
}
|
|
165
|
-
if (id === "default") {
|
|
166
|
-
const env = envDefaultAccount();
|
|
167
|
-
if (env)
|
|
168
|
-
return { accountId: id, ...env };
|
|
169
|
-
}
|
|
170
133
|
return { accountId: id };
|
|
171
134
|
}
|
package/dist/polyfills.d.ts
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
result: ArrayBuffer | null;
|
|
3
|
-
error: Error | null;
|
|
4
|
-
onload: ((ev: {
|
|
5
|
-
target: NodeFileReaderPolyfill;
|
|
6
|
-
}) => void) | null;
|
|
7
|
-
onerror: ((err: unknown) => void) | null;
|
|
8
|
-
readAsArrayBuffer(blob: Blob): void;
|
|
9
|
-
}
|
|
1
|
+
export {};
|
package/dist/polyfills.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import WsWebSocket from "ws";
|
|
2
|
+
// Force ws library over native WebSocket — OpenIM WASM SDK is incompatible
|
|
3
|
+
// with Node.js native WebSocket (undici-based), causing instant "network error".
|
|
4
|
+
globalThis.WebSocket = WsWebSocket;
|
|
5
|
+
// ws library sends Buffer for binary messages; OpenIM SDK calls .arrayBuffer() expecting a Blob.
|
|
6
|
+
// Polyfill Buffer.prototype.arrayBuffer so it returns Promise<ArrayBuffer>.
|
|
7
|
+
if (typeof Buffer !== "undefined" && !Buffer.prototype.arrayBuffer) {
|
|
8
|
+
Buffer.prototype.arrayBuffer = function () {
|
|
9
|
+
const ab = this.buffer.slice(this.byteOffset, this.byteOffset + this.byteLength);
|
|
10
|
+
return Promise.resolve(ab);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
2
13
|
class NodeFileReaderPolyfill {
|
|
3
14
|
result = null;
|
|
4
15
|
error = null;
|
package/dist/portal.js
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* Lifecycle is tied to the OpenIM account: starts/stops alongside the account.
|
|
7
7
|
*/
|
|
8
8
|
import { readFile, writeFile, stat, mkdir, unlink, rm } from "node:fs/promises";
|
|
9
|
-
import {
|
|
9
|
+
import { readdirSync, realpathSync, existsSync, readFileSync, statSync } from "node:fs";
|
|
10
|
+
import WsWebSocket from "ws";
|
|
10
11
|
import { resolve, join, dirname, basename } from "node:path";
|
|
11
|
-
import { execFile } from "node:child_process";
|
|
12
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
12
13
|
import { promisify } from "node:util";
|
|
13
14
|
import { tmpdir, homedir } from "node:os";
|
|
14
15
|
const bridges = new Map();
|
|
@@ -51,7 +52,7 @@ function resolveDefaultAgentId(cfg) {
|
|
|
51
52
|
}
|
|
52
53
|
/** Expand leading ~ to $HOME, then resolve to absolute path. */
|
|
53
54
|
function resolveUserPath(p) {
|
|
54
|
-
const home =
|
|
55
|
+
const home = homedir() || "";
|
|
55
56
|
if (p.startsWith("~/") || p === "~") {
|
|
56
57
|
return resolve(home, p.slice(2));
|
|
57
58
|
}
|
|
@@ -65,7 +66,7 @@ function resolveAgentWorkspaceDir(cfg, agentId) {
|
|
|
65
66
|
return resolveUserPath(entry.workspace.trim());
|
|
66
67
|
const fallback = cfg.agents?.defaults?.workspace?.trim();
|
|
67
68
|
const defaultId = resolveDefaultAgentId(cfg);
|
|
68
|
-
const home =
|
|
69
|
+
const home = homedir() || process.cwd();
|
|
69
70
|
if (id === defaultId) {
|
|
70
71
|
if (fallback)
|
|
71
72
|
return resolveUserPath(fallback);
|
|
@@ -498,25 +499,8 @@ function handleToolsCatalog(api, params) {
|
|
|
498
499
|
* Resolve the openclaw state directory root.
|
|
499
500
|
* Mirrors gateway's resolveStateDir: OPENCLAW_STATE_DIR > dirname(OPENCLAW_CONFIG_PATH) > ~/.openclaw
|
|
500
501
|
*/
|
|
501
|
-
/** Resolve the effective home directory
|
|
502
|
-
* OPENCLAW_HOME → process.env.HOME → process.env.USERPROFILE → os.homedir() → cwd */
|
|
502
|
+
/** Resolve the effective home directory. */
|
|
503
503
|
function resolveEffectiveHomeDir() {
|
|
504
|
-
const openclawHome = (process.env.OPENCLAW_HOME ?? "").trim();
|
|
505
|
-
if (openclawHome && openclawHome !== "undefined" && openclawHome !== "null") {
|
|
506
|
-
// Support ~/... prefix expansion using os.homedir()
|
|
507
|
-
if (openclawHome === "~" || openclawHome.startsWith("~/") || openclawHome.startsWith("~\\")) {
|
|
508
|
-
const osHome = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
509
|
-
if (osHome)
|
|
510
|
-
return resolve(openclawHome.replace(/^~(?=$|[\\/])/, osHome));
|
|
511
|
-
}
|
|
512
|
-
return resolve(openclawHome);
|
|
513
|
-
}
|
|
514
|
-
const envHome = (process.env.HOME ?? "").trim();
|
|
515
|
-
if (envHome)
|
|
516
|
-
return resolve(envHome);
|
|
517
|
-
const userProfile = (process.env.USERPROFILE ?? "").trim();
|
|
518
|
-
if (userProfile)
|
|
519
|
-
return resolve(userProfile);
|
|
520
504
|
try {
|
|
521
505
|
return resolve(homedir());
|
|
522
506
|
}
|
|
@@ -524,47 +508,26 @@ function resolveEffectiveHomeDir() {
|
|
|
524
508
|
return resolve(process.cwd());
|
|
525
509
|
}
|
|
526
510
|
function resolveStateDir() {
|
|
527
|
-
const stateDir = (process.env.OPENCLAW_STATE_DIR ?? "").trim();
|
|
528
|
-
if (stateDir)
|
|
529
|
-
return resolve(stateDir.startsWith("~") ? stateDir.replace(/^~(?=$|[\\/])/, resolveEffectiveHomeDir()) : stateDir);
|
|
530
|
-
const configPath = (process.env.OPENCLAW_CONFIG_PATH ?? "").trim();
|
|
531
|
-
if (configPath)
|
|
532
|
-
return resolve(dirname(configPath.startsWith("~") ? configPath.replace(/^~(?=$|[\\/])/, resolveEffectiveHomeDir()) : configPath));
|
|
533
511
|
return join(resolveEffectiveHomeDir(), ".openclaw");
|
|
534
512
|
}
|
|
535
|
-
/**
|
|
536
|
-
* Resolve the openclaw config file path.
|
|
537
|
-
* Mirrors gateway's resolveCanonicalConfigPath: OPENCLAW_CONFIG_PATH > {stateDir}/openclaw.json
|
|
538
|
-
*/
|
|
539
513
|
function resolveOpenClawConfigPath() {
|
|
540
|
-
const configPathOverride = (process.env.OPENCLAW_CONFIG_PATH ?? "").trim();
|
|
541
|
-
if (configPathOverride)
|
|
542
|
-
return resolveUserPath(configPathOverride);
|
|
543
514
|
return join(resolveStateDir(), "openclaw.json");
|
|
544
515
|
}
|
|
545
|
-
/** Check if a binary is executable on the system PATH.
|
|
516
|
+
/** Check if a binary is executable on the system PATH. */
|
|
546
517
|
const _binaryCache = new Map();
|
|
547
518
|
function hasBinarySync(bin) {
|
|
548
519
|
const cached = _binaryCache.get(bin);
|
|
549
520
|
if (cached !== undefined)
|
|
550
521
|
return cached;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
accessSync(join(part, bin + ext), fsConstants.X_OK);
|
|
560
|
-
_binaryCache.set(bin, true);
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
catch { /* keep searching */ }
|
|
564
|
-
}
|
|
522
|
+
try {
|
|
523
|
+
execFileSync("which", [bin], { stdio: "ignore" });
|
|
524
|
+
_binaryCache.set(bin, true);
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
_binaryCache.set(bin, false);
|
|
529
|
+
return false;
|
|
565
530
|
}
|
|
566
|
-
_binaryCache.set(bin, false);
|
|
567
|
-
return false;
|
|
568
531
|
}
|
|
569
532
|
/**
|
|
570
533
|
* Relaxed JSON5 parser: removes trailing commas before } or ].
|
|
@@ -767,9 +730,6 @@ function loadSkillsFromDir(dir, source) {
|
|
|
767
730
|
* Searches multiple known installation paths (npm, pnpm, nvm, etc.)
|
|
768
731
|
*/
|
|
769
732
|
function resolveBundledSkillsDir() {
|
|
770
|
-
const override = (process.env.OPENCLAW_BUNDLED_SKILLS_DIR ?? "").trim();
|
|
771
|
-
if (override)
|
|
772
|
-
return override;
|
|
773
733
|
const candidates = [];
|
|
774
734
|
// 1. Adjacent to node binary (nvm-style installs)
|
|
775
735
|
try {
|
|
@@ -789,7 +749,7 @@ function resolveBundledSkillsDir() {
|
|
|
789
749
|
}
|
|
790
750
|
catch { }
|
|
791
751
|
// 4. ~/.npm-global/lib/node_modules/openclaw/skills (common npm prefix)
|
|
792
|
-
const home =
|
|
752
|
+
const home = homedir() || "";
|
|
793
753
|
if (home) {
|
|
794
754
|
candidates.push(join(home, ".npm-global", "lib", "node_modules", "openclaw", "skills"));
|
|
795
755
|
}
|
|
@@ -969,7 +929,7 @@ function buildSkillStatus(entry, cfg) {
|
|
|
969
929
|
const requiresAnyBins = entry.metadata?.requires?.anyBins ?? [];
|
|
970
930
|
const requiresEnv = entry.metadata?.requires?.env ?? [];
|
|
971
931
|
const requiresConfig = entry.metadata?.requires?.config ?? [];
|
|
972
|
-
const isEnvSatisfied = (name) => Boolean(
|
|
932
|
+
const isEnvSatisfied = (name) => Boolean(skillCfg?.env?.[name] ||
|
|
973
933
|
(skillCfg?.apiKey && entry.metadata?.primaryEnv === name));
|
|
974
934
|
const missingBins = always ? [] : requiresBins.filter(b => !hasBinarySync(b));
|
|
975
935
|
const missingAnyBins = always ? [] :
|
|
@@ -1022,9 +982,7 @@ function buildSkillStatus(entry, cfg) {
|
|
|
1022
982
|
* Mirrors gateway's fetchJson (clawhub-t8tftw_j.js).
|
|
1023
983
|
*/
|
|
1024
984
|
async function fetchClawHub(path, searchParams) {
|
|
1025
|
-
const baseUrl =
|
|
1026
|
-
(process.env.CLAWHUB_URL ?? "").trim() ||
|
|
1027
|
-
"https://clawhub.ai").replace(/\/+$/, "");
|
|
985
|
+
const baseUrl = "https://clawhub.ai";
|
|
1028
986
|
let url = `${baseUrl}${path}`;
|
|
1029
987
|
if (searchParams && Object.keys(searchParams).length > 0) {
|
|
1030
988
|
url += `?${new URLSearchParams(searchParams).toString()}`;
|
|
@@ -1289,7 +1247,7 @@ async function handleSkillsInstall(api, params) {
|
|
|
1289
1247
|
let archiveBytes;
|
|
1290
1248
|
try {
|
|
1291
1249
|
const searchParams = { version };
|
|
1292
|
-
const archiveUrl = (
|
|
1250
|
+
const archiveUrl = ("https://clawhub.ai"
|
|
1293
1251
|
+ `/api/v1/packages/${encodeURIComponent(slug)}/download?` + new URLSearchParams(searchParams));
|
|
1294
1252
|
const controller = new AbortController();
|
|
1295
1253
|
const timer = setTimeout(() => controller.abort(), 60000);
|
|
@@ -1535,22 +1493,14 @@ async function handleAgentModelSet(api, params) {
|
|
|
1535
1493
|
* 4. Default: ~/.openclaw/cron/jobs.json
|
|
1536
1494
|
*/
|
|
1537
1495
|
function resolveCronStorePath(api) {
|
|
1538
|
-
const home =
|
|
1496
|
+
const home = homedir() || "";
|
|
1539
1497
|
const expandHome = (p) => p.startsWith("~/") || p === "~" ? join(home, p.slice(2)) : p;
|
|
1540
|
-
//
|
|
1541
|
-
const stateDir = (process.env.OPENCLAW_STATE_DIR ?? "").trim();
|
|
1542
|
-
if (stateDir)
|
|
1543
|
-
return resolve(join(expandHome(stateDir), "cron", "jobs.json"));
|
|
1544
|
-
// 2. OPENCLAW_CONFIG_PATH
|
|
1545
|
-
const configPath = (process.env.OPENCLAW_CONFIG_PATH ?? "").trim();
|
|
1546
|
-
if (configPath)
|
|
1547
|
-
return resolve(join(dirname(expandHome(configPath)), "cron", "jobs.json"));
|
|
1548
|
-
// 3. cfg.cron?.store
|
|
1498
|
+
// cfg.cron?.store
|
|
1549
1499
|
const cfg = getConfig(api);
|
|
1550
1500
|
const cfgStore = String(cfg?.cron?.store ?? "").trim();
|
|
1551
1501
|
if (cfgStore)
|
|
1552
1502
|
return resolve(expandHome(cfgStore));
|
|
1553
|
-
//
|
|
1503
|
+
// Default
|
|
1554
1504
|
return join(home, ".openclaw", "cron", "jobs.json");
|
|
1555
1505
|
}
|
|
1556
1506
|
/**
|
|
@@ -1704,7 +1654,8 @@ async function handlePortalRequest(api, accountId, request) {
|
|
|
1704
1654
|
// WebSocket connection management (unchanged logic)
|
|
1705
1655
|
// ---------------------------------------------------------------------------
|
|
1706
1656
|
function sendResponse(ws, response) {
|
|
1707
|
-
|
|
1657
|
+
// readyState 1 === OPEN for both native WebSocket and ws library
|
|
1658
|
+
if (ws.readyState === 1) {
|
|
1708
1659
|
ws.send(JSON.stringify(response));
|
|
1709
1660
|
}
|
|
1710
1661
|
}
|
|
@@ -1713,9 +1664,18 @@ function connectPortal(api, bridge) {
|
|
|
1713
1664
|
return;
|
|
1714
1665
|
const url = `${bridge.portalWsAddr}/${bridge.botId}`;
|
|
1715
1666
|
portalLog(api, "info", `connecting to agent-portal: url=${url} botId=${bridge.botId} accountId=${bridge.accountId}`);
|
|
1667
|
+
// Use `ws` library for wss:// to bypass undici globalDispatcher interference
|
|
1668
|
+
const useWsLib = url.startsWith("wss://");
|
|
1716
1669
|
let ws;
|
|
1717
1670
|
try {
|
|
1718
|
-
|
|
1671
|
+
if (useWsLib) {
|
|
1672
|
+
const wsClient = new WsWebSocket(url);
|
|
1673
|
+
// Wrap ws library instance to match WebSocket API used below
|
|
1674
|
+
ws = wsClient;
|
|
1675
|
+
}
|
|
1676
|
+
else {
|
|
1677
|
+
ws = new WebSocket(url);
|
|
1678
|
+
}
|
|
1719
1679
|
}
|
|
1720
1680
|
catch (err) {
|
|
1721
1681
|
portalLog(api, "error", `WebSocket constructor failed: ${err?.message ?? err}`);
|
|
@@ -1724,11 +1684,12 @@ function connectPortal(api, bridge) {
|
|
|
1724
1684
|
}
|
|
1725
1685
|
bridge.ws = ws;
|
|
1726
1686
|
let reconnectAttempts = 0;
|
|
1687
|
+
const READY_STATE_OPEN = useWsLib ? WsWebSocket.OPEN : WebSocket.OPEN;
|
|
1727
1688
|
ws.addEventListener("open", () => {
|
|
1728
1689
|
reconnectAttempts = 0;
|
|
1729
1690
|
portalLog(api, "info", `connected to agent-portal: botId=${bridge.botId}`);
|
|
1730
1691
|
bridge.heartbeatTimer = setInterval(() => {
|
|
1731
|
-
if (ws.readyState ===
|
|
1692
|
+
if (ws.readyState === READY_STATE_OPEN) {
|
|
1732
1693
|
const pingMsg = { id: `ping-${Date.now()}`, method: "ping", params: {} };
|
|
1733
1694
|
ws.send(JSON.stringify(pingMsg));
|
|
1734
1695
|
portalLog(api, "debug", `heartbeat ping sent: botId=${bridge.botId}`);
|
|
@@ -1737,14 +1698,19 @@ function connectPortal(api, bridge) {
|
|
|
1737
1698
|
});
|
|
1738
1699
|
ws.addEventListener("message", async (event) => {
|
|
1739
1700
|
let raw;
|
|
1740
|
-
|
|
1741
|
-
|
|
1701
|
+
// ws library: event.data is Buffer/string; native: event.data is string/ArrayBuffer
|
|
1702
|
+
const data = event.data ?? event;
|
|
1703
|
+
if (typeof data === "string") {
|
|
1704
|
+
raw = data;
|
|
1705
|
+
}
|
|
1706
|
+
else if (data instanceof ArrayBuffer) {
|
|
1707
|
+
raw = new TextDecoder().decode(data);
|
|
1742
1708
|
}
|
|
1743
|
-
else if (
|
|
1744
|
-
raw =
|
|
1709
|
+
else if (Buffer.isBuffer(data)) {
|
|
1710
|
+
raw = data.toString("utf-8");
|
|
1745
1711
|
}
|
|
1746
1712
|
else {
|
|
1747
|
-
portalLog(api, "warn", `unexpected message data type: ${typeof
|
|
1713
|
+
portalLog(api, "warn", `unexpected message data type: ${typeof data}`);
|
|
1748
1714
|
return;
|
|
1749
1715
|
}
|
|
1750
1716
|
let request;
|
|
@@ -1764,7 +1730,7 @@ function connectPortal(api, bridge) {
|
|
|
1764
1730
|
portalLog(api, "debug", `response sent: id=${request.id} method=${request.method} ok=${!response.error}`);
|
|
1765
1731
|
});
|
|
1766
1732
|
ws.addEventListener("close", (event) => {
|
|
1767
|
-
portalLog(api, "info", `disconnected from agent-portal: botId=${bridge.botId} code=${event.code} reason=${event.reason || "none"}`);
|
|
1733
|
+
portalLog(api, "info", `disconnected from agent-portal: botId=${bridge.botId} code=${event.code ?? 1006} reason=${event.reason || "none"}`);
|
|
1768
1734
|
clearHeartbeat(bridge);
|
|
1769
1735
|
bridge.ws = null;
|
|
1770
1736
|
if (!bridge.stopped) {
|
package/dist/setup.js
CHANGED
|
@@ -19,15 +19,15 @@ export async function runOpenIMSetup() {
|
|
|
19
19
|
clackIntro("OpenIM Channel Setup Wizard");
|
|
20
20
|
const token = guardCancel(await clackText({
|
|
21
21
|
message: "Enter OpenIM Access Token",
|
|
22
|
-
initialValue:
|
|
22
|
+
initialValue: "",
|
|
23
23
|
}));
|
|
24
24
|
const wsAddr = guardCancel(await clackText({
|
|
25
25
|
message: "Enter OpenIM WebSocket endpoint",
|
|
26
|
-
initialValue:
|
|
26
|
+
initialValue: "ws://127.0.0.1:10001",
|
|
27
27
|
}));
|
|
28
28
|
const apiAddr = guardCancel(await clackText({
|
|
29
29
|
message: "Enter OpenIM REST API endpoint",
|
|
30
|
-
initialValue:
|
|
30
|
+
initialValue: "http://127.0.0.1:10002",
|
|
31
31
|
}));
|
|
32
32
|
const trimmedToken = String(token).trim();
|
|
33
33
|
const trimmedWsAddr = String(wsAddr).trim();
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeyiy/openclaw-channel",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "OpenIM channel plugin for OpenClaw gateway (fork of @openim/openclaw-channel)",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "ZeyiY",
|
|
@@ -49,7 +49,9 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@clack/prompts": "^1.0.0",
|
|
52
|
-
"@openim/client-sdk": "^3.8.3"
|
|
52
|
+
"@openim/client-sdk": "^3.8.3",
|
|
53
|
+
"@types/ws": "^8.18.1",
|
|
54
|
+
"ws": "^8.20.0"
|
|
53
55
|
},
|
|
54
56
|
"peerDependencies": {
|
|
55
57
|
"clawdbot": "*",
|