openclaw-xiaoyou 1.0.0 → 1.0.2

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 CHANGED
@@ -85,32 +85,6 @@ openclaw channels add --channel xiaoyou
85
85
  | `heartbeatIntervalMs` | number | 否 | 30000 | 心跳间隔(ms) |
86
86
  | `heartbeatTimeoutMs` | number | 否 | 10000 | 心跳超时(ms) |
87
87
 
88
- ### 多账号
89
-
90
- 同时连接多个企业服务:
91
-
92
- ```json
93
- {
94
- "channels": {
95
- "xiaoyou": {
96
- "enabled": true,
97
- "wsUrl": "wss://default.corp.com/ws",
98
- "authToken": "default-token",
99
- "accounts": {
100
- "sales": {
101
- "wsUrl": "wss://sales.corp.com/ws",
102
- "authToken": "sales-token"
103
- },
104
- "support": {
105
- "wsUrl": "wss://support.corp.com/ws",
106
- "authToken": "support-token"
107
- }
108
- }
109
- }
110
- }
111
- }
112
- ```
113
-
114
88
  ## CLI 命令
115
89
 
116
90
  ```bash
@@ -8,7 +8,7 @@ description: Use when users want to install, upgrade, uninstall, configure, or t
8
8
  小优 channel 安装、升级、卸载统一使用远程脚本:
9
9
 
10
10
  ```bash
11
- https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh
11
+ https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh
12
12
  ```
13
13
 
14
14
  不要把 OpenClaw 的底层安装命令拆散执行。除非远程脚本失败且需要排障,否则直接调用 `install-xiaoyou.sh`。
@@ -28,14 +28,14 @@ https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh
28
28
  优先直接通过 `curl | bash` 执行远程脚本:
29
29
 
30
30
  ```bash
31
- curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh | bash -s -- --ws-url wss://im.corp.example.com/ws --token <AccessToken>
31
+ curl -fsSL https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token <AccessToken>
32
32
  ```
33
33
 
34
34
  ### 参数说明
35
35
 
36
36
  | 参数 | 必填 | 说明 |
37
37
  |------|------|------|
38
- | `--ws-url <url>` | ✅ 首次安装 | 企业 WebSocket 服务地址 |
38
+ | `--ws-url <url>` | ✅ 首次安装 | 企业 WebSocket 服务地址,默认 `ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw` |
39
39
  | `--token <token>` | ✅ 首次安装 | 企业服务认证 Token |
40
40
  | `--version <ver>` | 否 | 指定 openclaw-xiaoyou 版本,默认 latest |
41
41
  | `--check-only` | 否 | 只输出计划,不执行真实改动 |
@@ -46,7 +46,7 @@ curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh
46
46
  用户必须提供 `--ws-url` 和 `--token`:
47
47
 
48
48
  ```bash
49
- curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh | bash -s -- --ws-url wss://im.corp.example.com/ws --token my-enterprise-token
49
+ curl -fsSL https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token my-enterprise-token
50
50
  ```
51
51
 
52
52
  ### 升级
@@ -54,7 +54,7 @@ curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh
54
54
  已安装后的升级可以不传参数,脚本会复用现有配置:
55
55
 
56
56
  ```bash
57
- curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh | bash -s --
57
+ curl -fsSL https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh | bash -s --
58
58
  ```
59
59
 
60
60
  如果升级时传入 `--ws-url` 或 `--token`,会覆盖现有配置。
@@ -62,13 +62,13 @@ curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh
62
62
  ### 指定版本
63
63
 
64
64
  ```bash
65
- curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh | bash -s -- --version 1.0.2 --ws-url wss://im.corp.example.com/ws --token my-token
65
+ curl -fsSL https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh | bash -s -- --version 1.0.2 --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token my-token
66
66
  ```
67
67
 
68
68
  ## 卸载
69
69
 
70
70
  ```bash
71
- curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh | bash -s -- uninstall
71
+ curl -fsSL https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh | bash -s -- uninstall
72
72
  ```
73
73
 
74
74
  卸载会删除 xiaoyou channel 配置、插件记录和扩展目录。
@@ -76,8 +76,8 @@ curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh
76
76
  ## 预检
77
77
 
78
78
  ```bash
79
- curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh | bash -s -- --check-only
80
- curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh | bash -s -- uninstall --check-only
79
+ curl -fsSL https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh | bash -s -- --check-only
80
+ curl -fsSL https://d10.tfdl.net/public/2026-05-06/e466d49d-4c5c-4fca-88dd-d69a38aec3ba/install-xiaoyou.sh | bash -s -- uninstall --check-only
81
81
  ```
82
82
 
83
83
  ## 失败处理
@@ -86,7 +86,6 @@ curl -fsSL https://aitest.haiersmarthomes.com/aiservice/claw/install-xiaoyou.sh
86
86
 
87
87
  | 日志现象 | 处理 |
88
88
  |----------|------|
89
- | 缺少 `--ws-url` | 要求用户提供企业 WebSocket 服务地址 |
90
89
  | 缺少 `--token` | 要求用户提供企业服务认证 Token |
91
90
  | 未检测到 OpenClaw | 提示用户先安装并启动 OpenClaw |
92
91
  | npm latest 查询失败 | 可让脚本继续幂等安装,或指定 `--version` |
@@ -410,49 +410,9 @@ xiaoyou 插件采用 **Bridge 模式**:由插件主动向企业服务发起 We
410
410
  └─────────────────────────────────────────────────────────────────┘
411
411
  ```
412
412
 
413
- ## 7. 多账号场景
414
-
415
- xiaoyou 支持同时连接多个企业服务(如销售系统 + 客服系统):
416
-
417
- ```
418
- ┌──────────────────────┐
419
- │ OpenClaw Gateway │
420
- │ │
421
- │ ┌────────────────┐ │
422
- │ │ xiaoyou 插件 │ │
423
- │ │ │ │
424
- ┌──────────┐ │ │ ┌──────────┐ │ │
425
- │ 销售 IM │◀════╪══╪══│ client │ │ │
426
- │ 服务 │ │ │ │ "sales" │ │ │
427
- └──────────┘ │ │ └──────────┘ │ │
428
- │ │ │ │
429
- ┌──────────┐ │ │ ┌──────────┐ │ │
430
- │ 客服 IM │◀════╪══╪══│ client │ │ │
431
- │ 服务 │ │ │ │"support" │ │ │
432
- └──────────┘ │ │ └──────────┘ │ │
433
- │ │ │ │
434
- │ └────────────────┘ │
435
- └──────────────────────┘
436
-
437
- 配置:
438
- {
439
- "channels": {
440
- "xiaoyou": {
441
- "accounts": {
442
- "sales": { "wsUrl": "wss://sales.corp.com/ws", "authToken": "..." },
443
- "support": { "wsUrl": "wss://support.corp.com/ws", "authToken": "..." }
444
- }
445
- }
446
- }
447
- }
448
- ```
449
-
450
- 每个 account 独立维护一个 EnterpriseClient 实例,独立的连接、认证、心跳和重连。
451
-
452
-
453
- ## 8. 异常处理与容错
413
+ ## 7. 异常处理与容错
454
414
 
455
- ### 8.1 异常场景处理矩阵
415
+ ### 7.1 异常场景处理矩阵
456
416
 
457
417
  ```
458
418
  ┌──────────────────────┬─────────────────────────┬──────────────────────────┐
@@ -473,7 +433,7 @@ xiaoyou 支持同时连接多个企业服务(如销售系统 + 客服系统)
473
433
  └──────────────────────┴─────────────────────────┴──────────────────────────┘
474
434
  ```
475
435
 
476
- ### 8.2 重连时序
436
+ ### 7.2 重连时序
477
437
 
478
438
  ```
479
439
  时间轴 ──────────────────────────────────────────────────────▶
@@ -490,7 +450,7 @@ xiaoyou 支持同时连接多个企业服务(如销售系统 + 客服系统)
490
450
  ═══════════════▶
491
451
  ```
492
452
 
493
- ## 9. 企业服务端实现要点
453
+ ## 8. 企业服务端实现要点
494
454
 
495
455
  企业侧需要实现一个 WebSocket Server,处理以下职责:
496
456
 
@@ -523,7 +483,7 @@ xiaoyou 支持同时连接多个企业服务(如销售系统 + 客服系统)
523
483
  └─────────────────────────────────────────────────────────┘
524
484
  ```
525
485
 
526
- ### 9.2 企业服务端伪代码示例
486
+ ### 8.2 企业服务端伪代码示例
527
487
 
528
488
  ```python
529
489
  # Python 示例 (FastAPI + websockets)
@@ -586,9 +546,9 @@ async def on_user_message(ws, user_msg):
586
546
  })
587
547
  ```
588
548
 
589
- ## 10. 部署拓扑
549
+ ## 9. 部署拓扑
590
550
 
591
- ### 10.1 最简部署(单机)
551
+ ### 9.1 最简部署(单机)
592
552
 
593
553
  ```
594
554
  ┌─────────────────────────────────────────────┐
@@ -604,7 +564,7 @@ async def on_user_message(ws, user_msg):
604
564
  └─────────────────────────────────────────────┘
605
565
  ```
606
566
 
607
- ### 10.2 生产部署(分离)
567
+ ### 9.2 生产部署(分离)
608
568
 
609
569
  ```
610
570
  ┌──────────────────┐ ┌──────────────────┐
@@ -622,7 +582,7 @@ async def on_user_message(ws, user_msg):
622
582
  - xiaoyou 的重连机制可以应对单节点故障
623
583
  ```
624
584
 
625
- ## 11. 快速对照表
585
+ ## 10. 快速对照表
626
586
 
627
587
  | 你想知道... | 看这里 |
628
588
  |------------|--------|
@@ -633,7 +593,6 @@ async def on_user_message(ws, user_msg):
633
593
  | Agent 回复怎么送回用户? | §4.1 步骤 ⑨⑩⑪ |
634
594
  | WebSocket 帧格式是什么? | §5 协议帧参考 |
635
595
  | 插件内部怎么组织的? | §6 插件内部模块交互 |
636
- | 怎么连多个企业服务? | §7 多账号场景 |
637
- | 出了问题怎么处理? | §8 异常处理与容错 |
638
- | 企业侧要实现什么? | §9 企业服务端实现要点 |
639
- | 怎么部署? | §10 部署拓扑 |
596
+ | 出了问题怎么处理? | §7 异常处理与容错 |
597
+ | 企业侧要实现什么? | §8 企业服务端实现要点 |
598
+ | 怎么部署? | §9 部署拓扑 |
@@ -2,8 +2,9 @@
2
2
  # install-xiayou.sh — OpenClaw xiaoyou channel 一键安装/升级/卸载脚本
3
3
  #
4
4
  # 用法:
5
+ # install-xiayou.sh --token <token>
5
6
  # install-xiayou.sh --ws-url <url> --token <token>
6
- # install-xiayou.sh --version <ver> --ws-url <url> --token <token>
7
+ # install-xiayou.sh --version <ver> --token <token>
7
8
  # install-xiayou.sh uninstall
8
9
  # install-xiayou.sh --check-only
9
10
 
@@ -18,6 +19,7 @@ fi
18
19
  set -Eeuo pipefail
19
20
 
20
21
  SCRIPT_NAME="$(basename "$0")"
22
+ DEFAULT_WS_URL="ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw"
21
23
  WS_URL=""
22
24
  TOKEN=""
23
25
  CHECK_ONLY=0
@@ -89,13 +91,14 @@ capture_timeout_or_empty() {
89
91
  usage() {
90
92
  cat <<'USAGE'
91
93
  Usage:
94
+ install-xiayou.sh --token <token>
92
95
  install-xiayou.sh --ws-url <url> --token <token>
93
- install-xiayou.sh --version <ver> --ws-url <url> --token <token>
96
+ install-xiayou.sh --version <ver> --token <token>
94
97
  install-xiayou.sh uninstall
95
98
  install-xiayou.sh --check-only
96
99
 
97
100
  Options:
98
- --ws-url <url> 企业 WebSocket 服务地址 (wss://... ws://...)
101
+ --ws-url <url> 企业 WebSocket 服务地址 (默认 ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw)
99
102
  --token <token> 企业服务认证 Token
100
103
  --version <ver> 指定 openclaw-xiaoyou 版本 (默认 latest)
101
104
  --check-only 只输出计划,不执行
@@ -263,10 +266,11 @@ install_or_upgrade() {
263
266
  fi
264
267
  fi
265
268
 
266
- # 首次安装必须有 ws-url 和 token
269
+ # 首次安装:ws-url 未提供时使用默认值
267
270
  if [[ -z "$current" && "$CHECK_ONLY" -eq 0 ]]; then
268
271
  if [[ -z "$WS_URL" ]]; then
269
- die "首次安装必须提供 --ws-url。请运行: $SCRIPT_NAME --ws-url <url> --token <token>"
272
+ WS_URL="$DEFAULT_WS_URL"
273
+ log "未指定 --ws-url,使用默认地址: $WS_URL"
270
274
  fi
271
275
  if [[ -z "$TOKEN" ]]; then
272
276
  die "首次安装必须提供 --token。请运行: $SCRIPT_NAME --ws-url <url> --token <token>"
@@ -0,0 +1,16 @@
1
+
2
+ /aiysnfs/nginx/ssl/aiservice/html/
3
+
4
+
5
+ https://aitest.haiersmarthomes.com:11001/skills/upload/emotion/robot_emotion_v1.0.tar.gz
6
+
7
+ 验收环境位置:
8
+ /aiysnfs/nginx/share/skills/upload/claw
9
+ 仿真环境位置:
10
+ /ai-sim/export/nginx/share/skills/upload/claw
11
+ 生产环境位置:
12
+ /ai-prod/export/nginx/share/skills/upload/claw
13
+
14
+ /aiysnfs/nginx/ssl/aiservice/html/
15
+
16
+ 按照 https://aitest.haiersmarthomes.com/aiservice/html/claw/SKILL.md 文档完成小优channel配置,AccessToken为xiaoyou-025b07e43ae84cd6
package/index.ts CHANGED
@@ -35,7 +35,7 @@ const plugin = {
35
35
  .description("Show enterprise WebSocket connection status")
36
36
  .action(async () => {
37
37
  const status = await xiayouPlugin.status.describe({
38
- account: { accountId: "default" },
38
+ account: {},
39
39
  });
40
40
  console.log(JSON.stringify(status, null, 2));
41
41
  });
@@ -51,7 +51,6 @@ const plugin = {
51
51
  return;
52
52
  }
53
53
  const result = await xiayouPlugin.outbound.send({
54
- account: { accountId: "default" },
55
54
  to: opts.to,
56
55
  payload: { kind: "text", text: opts.text },
57
56
  });
@@ -3,6 +3,59 @@
3
3
  "name": "Xiaoyou",
4
4
  "description": "Bridge OpenClaw to enterprise services via persistent WebSocket connection",
5
5
  "version": "1.0.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "required": ["wsUrl", "authToken"],
9
+ "properties": {
10
+ "enabled": {
11
+ "type": "boolean",
12
+ "default": false,
13
+ "description": "启用/禁用此 channel"
14
+ },
15
+ "wsUrl": {
16
+ "type": "string",
17
+ "description": "企业 WebSocket 服务地址",
18
+ "examples": ["wss://im.corp.example.com/ws", "ws://192.168.1.100:9090"]
19
+ },
20
+ "authToken": {
21
+ "type": "string",
22
+ "sensitive": true,
23
+ "description": "连接企业服务的认证 Token"
24
+ },
25
+ "dmSecurity": {
26
+ "type": "string",
27
+ "enum": ["open", "allowlist"],
28
+ "default": "open",
29
+ "description": "DM 安全策略。open=允许所有用户,allowlist=仅白名单用户"
30
+ },
31
+ "allowFrom": {
32
+ "type": "array",
33
+ "items": { "type": "string" },
34
+ "default": [],
35
+ "description": "白名单用户 ID 列表(dmSecurity=allowlist 时生效)"
36
+ },
37
+ "reconnectIntervalMs": {
38
+ "type": "number",
39
+ "default": 3000,
40
+ "description": "重连基础间隔(毫秒),实际按指数退避递增"
41
+ },
42
+ "maxReconnectAttempts": {
43
+ "type": "number",
44
+ "default": 0,
45
+ "description": "最大重连次数,0=无限重试"
46
+ },
47
+ "heartbeatIntervalMs": {
48
+ "type": "number",
49
+ "default": 30000,
50
+ "description": "心跳发送间隔(毫秒)"
51
+ },
52
+ "heartbeatTimeoutMs": {
53
+ "type": "number",
54
+ "default": 10000,
55
+ "description": "心跳超时时间(毫秒),超时则断开重连"
56
+ }
57
+ }
58
+ },
6
59
  "channel": {
7
60
  "id": "xiaoyou",
8
61
  "configSchema": {
@@ -55,18 +108,6 @@
55
108
  "type": "number",
56
109
  "default": 10000,
57
110
  "description": "心跳超时时间(毫秒),超时则断开重连"
58
- },
59
- "accounts": {
60
- "type": "object",
61
- "description": "多账号配置,key 为 accountId,值为上述字段的覆盖",
62
- "additionalProperties": {
63
- "type": "object",
64
- "properties": {
65
- "wsUrl": { "type": "string" },
66
- "authToken": { "type": "string", "sensitive": true },
67
- "allowFrom": { "type": "array", "items": { "type": "string" } }
68
- }
69
- }
70
111
  }
71
112
  }
72
113
  }
package/package.json CHANGED
@@ -1,11 +1,26 @@
1
1
  {
2
2
  "name": "openclaw-xiaoyou",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "Xiaoyou channel plugin for OpenClaw — connects enterprise services via persistent outbound WebSocket",
6
6
  "openclaw": {
7
7
  "extensions": ["./index.ts"],
8
8
  "setupEntry": "./setup-entry.ts",
9
+ "configSchema": {
10
+ "type": "object",
11
+ "required": ["wsUrl", "authToken"],
12
+ "properties": {
13
+ "enabled": { "type": "boolean", "default": false },
14
+ "wsUrl": { "type": "string", "description": "企业 WebSocket 服务地址" },
15
+ "authToken": { "type": "string", "sensitive": true, "description": "连接企业服务的认证 Token" },
16
+ "dmSecurity": { "type": "string", "enum": ["open", "allowlist"], "default": "open" },
17
+ "allowFrom": { "type": "array", "items": { "type": "string" }, "default": [] },
18
+ "reconnectIntervalMs": { "type": "number", "default": 3000 },
19
+ "maxReconnectAttempts": { "type": "number", "default": 0 },
20
+ "heartbeatIntervalMs": { "type": "number", "default": 30000 },
21
+ "heartbeatTimeoutMs": { "type": "number", "default": 10000 }
22
+ }
23
+ },
9
24
  "channel": {
10
25
  "id": "xiaoyou",
11
26
  "label": "Xiaoyou",
@@ -15,8 +15,8 @@ const fullCfg = {
15
15
  };
16
16
 
17
17
  describe("config adapter", () => {
18
- it("resolves account with all fields", () => {
19
- const acct = xiayouPlugin.config.resolveAccount(fullCfg, undefined);
18
+ it("resolves config with all fields", () => {
19
+ const acct = xiayouPlugin.config.resolveAccount(fullCfg);
20
20
  expect(acct.wsUrl).toBe("wss://im.corp.example.com/ws");
21
21
  expect(acct.authToken).toBe("test-token-123");
22
22
  expect(acct.allowFrom).toEqual(["user-1", "user-2"]);
@@ -27,7 +27,7 @@ describe("config adapter", () => {
27
27
 
28
28
  it("applies defaults for optional fields", () => {
29
29
  const cfg = { channels: { xiaoyou: { wsUrl: "ws://localhost:9090", authToken: "t" } } };
30
- const acct = xiayouPlugin.config.resolveAccount(cfg, undefined);
30
+ const acct = xiayouPlugin.config.resolveAccount(cfg);
31
31
  expect(acct.reconnectIntervalMs).toBe(3000);
32
32
  expect(acct.maxReconnectAttempts).toBe(0);
33
33
  expect(acct.heartbeatIntervalMs).toBe(30000);
@@ -35,106 +35,27 @@ describe("config adapter", () => {
35
35
  });
36
36
 
37
37
  it("throws when wsUrl missing", () => {
38
- expect(() => xiayouPlugin.config.resolveAccount({ channels: { xiaoyou: {} } }, undefined))
38
+ expect(() => xiayouPlugin.config.resolveAccount({ channels: { xiaoyou: {} } }))
39
39
  .toThrow("wsUrl is required");
40
40
  });
41
41
 
42
42
  it("throws when section missing", () => {
43
- expect(() => xiayouPlugin.config.resolveAccount({ channels: {} }, undefined))
43
+ expect(() => xiayouPlugin.config.resolveAccount({ channels: {} }))
44
44
  .toThrow("config section not found");
45
45
  });
46
-
47
- it("resolves multi-account override", () => {
48
- const cfg = {
49
- channels: {
50
- xiaoyou: {
51
- wsUrl: "ws://default/ws",
52
- authToken: "default-token",
53
- accounts: {
54
- sales: { wsUrl: "ws://sales/ws", authToken: "sales-token" },
55
- },
56
- },
57
- },
58
- };
59
- const acct = xiayouPlugin.config.resolveAccount(cfg, "sales");
60
- expect(acct.wsUrl).toBe("ws://sales/ws");
61
- expect(acct.authToken).toBe("sales-token");
62
- });
63
-
64
- it("falls back to default when accountId not in accounts", () => {
65
- const cfg = {
66
- channels: {
67
- xiaoyou: {
68
- wsUrl: "ws://default/ws",
69
- authToken: "default-token",
70
- accounts: { sales: { wsUrl: "ws://sales/ws" } },
71
- },
72
- },
73
- };
74
- const acct = xiayouPlugin.config.resolveAccount(cfg, "unknown");
75
- expect(acct.wsUrl).toBe("ws://default/ws");
76
- });
77
46
  });
78
47
 
79
48
  describe("inspectAccount", () => {
80
49
  it("reports configured", () => {
81
- const r = xiayouPlugin.config.inspectAccount(fullCfg, undefined);
50
+ const r = xiayouPlugin.config.inspectAccount(fullCfg);
82
51
  expect(r.enabled).toBe(true);
83
52
  expect(r.configured).toBe(true);
84
53
  expect(r.tokenStatus).toBe("available");
85
54
  });
86
55
 
87
56
  it("reports not configured", () => {
88
- const r = xiayouPlugin.config.inspectAccount({ channels: {} }, undefined);
57
+ const r = xiayouPlugin.config.inspectAccount({ channels: {} });
89
58
  expect(r.enabled).toBe(false);
90
59
  expect(r.configured).toBe(false);
91
60
  });
92
61
  });
93
-
94
- describe("listAccounts", () => {
95
- it("returns default when no accounts section", () => {
96
- expect(xiayouPlugin.config.listAccounts(fullCfg)).toEqual(["default"]);
97
- });
98
-
99
- it("returns account keys", () => {
100
- const cfg = {
101
- channels: {
102
- xiaoyou: {
103
- wsUrl: "ws://x",
104
- accounts: { a: {}, b: {} },
105
- },
106
- },
107
- };
108
- expect(xiayouPlugin.config.listAccounts(cfg)).toEqual(["a", "b"]);
109
- });
110
-
111
- it("returns empty when not configured", () => {
112
- expect(xiayouPlugin.config.listAccounts({ channels: {} })).toEqual([]);
113
- });
114
- });
115
-
116
- describe("capabilities", () => {
117
- it("declares correct feature set", () => {
118
- const c = xiayouPlugin.capabilities;
119
- expect(c.media).toBe(true);
120
- expect(c.reply).toBe(true);
121
- expect(c.edit).toBe(false);
122
- expect(c.polls).toBe(false);
123
- expect(c.reactions).toBe(false);
124
- });
125
- });
126
-
127
- describe("status adapter", () => {
128
- it("reports issues when not connected", async () => {
129
- const s = await xiayouPlugin.status.describe({
130
- account: { accountId: "default", wsUrl: "ws://x", authToken: "t" },
131
- });
132
- expect(s.summary).toBe("error");
133
- expect(s.issues.some((i: any) => i.message.includes("not connected"))).toBe(true);
134
- });
135
-
136
- it("reports wsUrl missing", async () => {
137
- const s = await xiayouPlugin.status.describe({ account: { accountId: "default" } });
138
- expect(s.issues.some((i: any) => i.message.includes("wsUrl"))).toBe(true);
139
- });
140
- });
package/src/channel.ts CHANGED
@@ -12,8 +12,7 @@ import { createEnterpriseClient, type EnterpriseClient } from "./enterprise-clie
12
12
 
13
13
  // ─── 运行时状态 ──────────────────────────────────────
14
14
 
15
- /** 每个 account 一个 EnterpriseClient */
16
- const clients = new Map<string, EnterpriseClient>();
15
+ let _client: EnterpriseClient | null = null;
17
16
 
18
17
  /** OpenClaw runtime API 引用,由 index.ts register() 注入 */
19
18
  let _runtime: any = null;
@@ -22,31 +21,24 @@ export function getRuntime() { return _runtime; }
22
21
 
23
22
  // ─── Config Adapter ──────────────────────────────────
24
23
 
25
- function resolveAccount(cfg: any, accountId?: string | null): ResolvedAccount {
24
+ function resolveAccount(cfg: any): ResolvedAccount {
26
25
  const section = cfg.channels?.["xiaoyou"];
27
26
  if (!section) throw new Error("xiaoyou: channel config section not found");
28
-
29
- // 多账号支持:accounts.<id> 覆盖顶层默认值
30
- const acct = accountId && section.accounts?.[accountId]
31
- ? { ...section, ...section.accounts[accountId] }
32
- : section;
33
-
34
- if (!acct.wsUrl) throw new Error("xiaoyou: wsUrl is required");
27
+ if (!section.wsUrl) throw new Error("xiaoyou: wsUrl is required");
35
28
 
36
29
  return {
37
- accountId: accountId ?? acct.defaultAccount ?? null,
38
- wsUrl: acct.wsUrl,
39
- authToken: acct.authToken ?? "",
40
- allowFrom: acct.allowFrom ?? [],
41
- dmPolicy: acct.dmSecurity,
42
- reconnectIntervalMs: acct.reconnectIntervalMs ?? 3000,
43
- maxReconnectAttempts: acct.maxReconnectAttempts ?? 0,
44
- heartbeatIntervalMs: acct.heartbeatIntervalMs ?? 30000,
45
- heartbeatTimeoutMs: acct.heartbeatTimeoutMs ?? 10000,
30
+ wsUrl: section.wsUrl,
31
+ authToken: section.authToken ?? "",
32
+ allowFrom: section.allowFrom ?? [],
33
+ dmPolicy: section.dmSecurity,
34
+ reconnectIntervalMs: section.reconnectIntervalMs ?? 3000,
35
+ maxReconnectAttempts: section.maxReconnectAttempts ?? 0,
36
+ heartbeatIntervalMs: section.heartbeatIntervalMs ?? 30000,
37
+ heartbeatTimeoutMs: section.heartbeatTimeoutMs ?? 10000,
46
38
  };
47
39
  }
48
40
 
49
- function inspectAccount(cfg: any, _accountId?: string | null) {
41
+ function inspectAccount(cfg: any) {
50
42
  const section = cfg.channels?.["xiaoyou"];
51
43
  return {
52
44
  enabled: Boolean(section?.wsUrl),
@@ -55,13 +47,6 @@ function inspectAccount(cfg: any, _accountId?: string | null) {
55
47
  };
56
48
  }
57
49
 
58
- function listAccounts(cfg: any): string[] {
59
- const section = cfg.channels?.["xiaoyou"];
60
- if (!section?.wsUrl) return [];
61
- if (section.accounts) return Object.keys(section.accounts);
62
- return ["default"];
63
- }
64
-
65
50
  // ─── Channel Plugin ──────────────────────────────────
66
51
 
67
52
  export const xiayouPlugin = {
@@ -94,7 +79,6 @@ export const xiayouPlugin = {
94
79
  config: {
95
80
  resolveAccount,
96
81
  inspectAccount,
97
- listAccounts,
98
82
  },
99
83
 
100
84
  // ── DM 安全策略 ────────────────────────────────────
@@ -116,13 +100,10 @@ export const xiayouPlugin = {
116
100
  const resolved: ResolvedAccount =
117
101
  typeof account.wsUrl === "string"
118
102
  ? account
119
- : resolveAccount(config, account.accountId);
120
-
121
- const clientKey = resolved.accountId ?? "default";
103
+ : resolveAccount(config);
122
104
 
123
105
  // 断开已有连接
124
- const existing = clients.get(clientKey);
125
- if (existing) existing.disconnect();
106
+ if (_client) _client.disconnect();
126
107
 
127
108
  const client = createEnterpriseClient({
128
109
  account: resolved,
@@ -135,7 +116,6 @@ export const xiayouPlugin = {
135
116
  }
136
117
  await runtime.inbound.dispatch({
137
118
  channelId: "xiaoyou",
138
- accountId: resolved.accountId ?? "default",
139
119
  senderId: msg.senderId,
140
120
  senderName: msg.senderName ?? msg.senderId,
141
121
  conversationId: msg.conversationId,
@@ -152,25 +132,21 @@ export const xiayouPlugin = {
152
132
  });
153
133
 
154
134
  client.connect();
155
- clients.set(clientKey, client);
156
- logger.info(`[xiaoyou] gateway started (account=${clientKey})`);
135
+ _client = client;
136
+ logger.info("[xiaoyou] gateway started");
157
137
  return client;
158
138
  },
159
139
 
160
140
  stop: async (client: EnterpriseClient) => {
161
141
  client.disconnect();
162
- for (const [key, c] of clients.entries()) {
163
- if (c === client) { clients.delete(key); break; }
164
- }
142
+ if (_client === client) _client = null;
165
143
  },
166
144
  },
167
145
 
168
146
  // ── Outbound 出站 ──────────────────────────────────
169
147
  outbound: {
170
- send: async ({ account, to, payload }: any) => {
171
- const clientKey = account.accountId ?? "default";
172
- const client = clients.get(clientKey);
173
- if (!client || !client.isConnected()) {
148
+ send: async ({ to, payload }: any) => {
149
+ if (!_client || !_client.isConnected()) {
174
150
  return { ok: false, error: "xiaoyou: not connected" };
175
151
  }
176
152
 
@@ -183,11 +159,11 @@ export const xiayouPlugin = {
183
159
  };
184
160
 
185
161
  if (payload.kind === "text") {
186
- client.sendReply({ ...baseReply, text: payload.text });
162
+ _client.sendReply({ ...baseReply, text: payload.text });
187
163
  return { ok: true };
188
164
  }
189
165
  if (payload.kind === "image" || payload.kind === "file") {
190
- client.sendReply({
166
+ _client.sendReply({
191
167
  ...baseReply,
192
168
  text: payload.caption ?? "",
193
169
  mediaUrls: [payload.url ?? payload.filePath],
@@ -201,13 +177,11 @@ export const xiayouPlugin = {
201
177
  // ── Status 状态检查 ────────────────────────────────
202
178
  status: {
203
179
  describe: async ({ account }: any) => {
204
- const clientKey = account.accountId ?? "default";
205
- const client = clients.get(clientKey);
206
180
  const issues: Array<{ severity: string; message: string }> = [];
207
181
 
208
- if (!account.wsUrl) issues.push({ severity: "error", message: "wsUrl not configured" });
209
- if (!account.authToken) issues.push({ severity: "warning", message: "authToken not set" });
210
- if (!client || !client.isConnected()) issues.push({ severity: "error", message: "WebSocket not connected" });
182
+ if (!account?.wsUrl) issues.push({ severity: "error", message: "wsUrl not configured" });
183
+ if (!account?.authToken) issues.push({ severity: "warning", message: "authToken not set" });
184
+ if (!_client || !_client.isConnected()) issues.push({ severity: "error", message: "WebSocket not connected" });
211
185
 
212
186
  return {
213
187
  summary: issues.length === 0 ? "connected" : "error",
@@ -78,7 +78,7 @@ export function createEnterpriseClient(opts: EnterpriseClientOptions): Enterpris
78
78
  ws.send(JSON.stringify({
79
79
  type: "auth",
80
80
  token: account.authToken,
81
- clientId: `openclaw-xiaoyou-${account.accountId ?? "default"}`,
81
+ clientId: "openclaw-xiaoyou",
82
82
  clientVersion: "1.0.0",
83
83
  }));
84
84
  logger.info("[xiaoyou] auth sent, waiting for auth_result...");
package/src/types.ts CHANGED
@@ -7,7 +7,6 @@
7
7
  // ─── 解析后的账号配置 ────────────────────────────────────
8
8
 
9
9
  export type ResolvedAccount = {
10
- accountId: string | null;
11
10
  /** 企业 WebSocket 服务地址,如 wss://im.corp.example.com/ws */
12
11
  wsUrl: string;
13
12
  /** 连接企业服务的认证 token */