openclaw-xiaoyou 1.0.7 → 1.2.0

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.
Files changed (3) hide show
  1. package/index.ts +1 -5
  2. package/package.json +1 -1
  3. package/src/channel.ts +60 -55
package/index.ts CHANGED
@@ -3,10 +3,6 @@
3
3
  *
4
4
  * 安装方式:
5
5
  * openclaw plugins install openclaw-xiaoyou
6
- * openclaw plugins enable openclaw-xiaoyou
7
- *
8
- * 配置方式:
9
- * openclaw channels add --channel xiaoyou
10
6
  */
11
7
 
12
8
  import { xiayouPlugin, setRuntime } from "./src/channel.js";
@@ -19,7 +15,7 @@ const plugin = {
19
15
 
20
16
  register(api: any) {
21
17
  // 注入 runtime,供 gateway/outbound 内部使用
22
- setRuntime(api.runtime);
18
+ if (api.runtime) setRuntime(api.runtime);
23
19
 
24
20
  // 注册 channel
25
21
  api.registerChannel({ plugin: xiayouPlugin });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-xiaoyou",
3
- "version": "1.0.7",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Xiaoyou channel plugin for OpenClaw — connects enterprise services via persistent outbound WebSocket",
6
6
  "openclaw": {
package/src/channel.ts CHANGED
@@ -3,8 +3,6 @@
3
3
  *
4
4
  * 运行在 OpenClaw Gateway 进程内,通过 gateway adapter 管理
5
5
  * 与企业服务的 WebSocket 连接生命周期。
6
- *
7
- * 用户通过 OpenClaw 标准 channel 配置来设置企业服务地址等参数。
8
6
  */
9
7
 
10
8
  import type { ResolvedAccount } from "./types.js";
@@ -14,7 +12,6 @@ import { createEnterpriseClient, type EnterpriseClient } from "./enterprise-clie
14
12
 
15
13
  let _client: EnterpriseClient | null = null;
16
14
 
17
- /** OpenClaw runtime API 引用,由 index.ts register() 注入 */
18
15
  let _runtime: any = null;
19
16
  export function setRuntime(rt: any) { _runtime = rt; }
20
17
  export function getRuntime() { return _runtime; }
@@ -25,6 +22,12 @@ function getChannelConfig(cfg: any): any {
25
22
  return cfg?.channels?.xiaoyou ?? {};
26
23
  }
27
24
 
25
+ function listAccountIds(cfg: any): string[] {
26
+ const section = getChannelConfig(cfg);
27
+ if (section?.wsUrl) return ["default"];
28
+ return [];
29
+ }
30
+
28
31
  function resolveAccount(cfg: any, accountId?: string | null): any {
29
32
  const section = getChannelConfig(cfg);
30
33
  const id = accountId || "default";
@@ -35,32 +38,10 @@ function resolveAccount(cfg: any, accountId?: string | null): any {
35
38
  config: section,
36
39
  enabled: section.enabled !== false,
37
40
  configured,
38
- wsUrl: section.wsUrl ?? "",
39
- authToken: section.authToken ?? "",
40
- allowFrom: section.allowFrom ?? [],
41
- dmPolicy: section.dmSecurity,
42
- reconnectIntervalMs: section.reconnectIntervalMs ?? 3000,
43
- maxReconnectAttempts: section.maxReconnectAttempts ?? 0,
44
- heartbeatIntervalMs: section.heartbeatIntervalMs ?? 30000,
45
- heartbeatTimeoutMs: section.heartbeatTimeoutMs ?? 10000,
41
+ name: "Xiaoyou",
46
42
  };
47
43
  }
48
44
 
49
- function inspectAccount(cfg: any) {
50
- const section = getChannelConfig(cfg);
51
- return {
52
- enabled: Boolean(section?.wsUrl),
53
- configured: Boolean(section?.wsUrl),
54
- tokenStatus: section?.authToken ? "available" : "missing",
55
- };
56
- }
57
-
58
- function listAccountIds(cfg: any): string[] {
59
- const section = getChannelConfig(cfg);
60
- if (section?.wsUrl) return ["default"];
61
- return [];
62
- }
63
-
64
45
  // ─── Channel Plugin ──────────────────────────────────
65
46
 
66
47
  export const xiayouPlugin = {
@@ -89,42 +70,56 @@ export const xiayouPlugin = {
89
70
  blockStreaming: false,
90
71
  },
91
72
 
92
- // ── Config(必须)──────────────────────────────────
73
+ reload: { configPrefixes: ["channels.xiaoyou"] },
74
+
93
75
  config: {
94
- resolveAccount,
95
- inspectAccount,
96
76
  listAccountIds,
77
+ resolveAccount,
78
+ isConfigured: (account: any): boolean => Boolean(account?.config?.wsUrl),
79
+ describeAccount: (account: any) => ({
80
+ accountId: account?.accountId || "default",
81
+ name: "Xiaoyou",
82
+ enabled: account?.enabled !== false,
83
+ }),
84
+ defaultAccountId: (): string => "default",
97
85
  },
98
86
 
99
- // ── DM 安全策略 ────────────────────────────────────
100
87
  security: {
101
88
  dm: {
102
89
  channelKey: "xiaoyou",
103
- resolvePolicy: (account: ResolvedAccount) => account.dmPolicy,
104
- resolveAllowFrom: (account: ResolvedAccount) => account.allowFrom,
105
- defaultPolicy: "allowlist",
90
+ resolvePolicy: (account: any) => account?.config?.dmSecurity ?? "open",
91
+ resolveAllowFrom: (account: any) => account?.config?.allowFrom ?? ["*"],
92
+ defaultPolicy: "open",
106
93
  },
107
94
  },
108
95
 
109
- // ── 回复线程模式 ───────────────────────────────────
110
96
  threading: { topLevelReplyToMode: "reply" as const },
111
97
 
112
98
  // ── Gateway 生命周期 ───────────────────────────────
113
99
  gateway: {
114
- start: async ({ account, config, logger }: any) => {
115
- const resolved: ResolvedAccount =
116
- typeof account?.wsUrl === "string"
117
- ? account
118
- : {
119
- wsUrl: account?.config?.wsUrl ?? "",
120
- authToken: account?.config?.authToken ?? "",
121
- allowFrom: account?.config?.allowFrom ?? [],
122
- dmPolicy: account?.config?.dmSecurity,
123
- reconnectIntervalMs: account?.config?.reconnectIntervalMs ?? 3000,
124
- maxReconnectAttempts: account?.config?.maxReconnectAttempts ?? 0,
125
- heartbeatIntervalMs: account?.config?.heartbeatIntervalMs ?? 30000,
126
- heartbeatTimeoutMs: account?.config?.heartbeatTimeoutMs ?? 10000,
127
- };
100
+ startAccount: async (ctx: any) => {
101
+ const { account, cfg, log } = ctx;
102
+
103
+ // 注入 runtime
104
+ if (ctx.runtime) setRuntime(ctx.runtime);
105
+
106
+ const section = account?.config || getChannelConfig(cfg);
107
+ const logger = log || console;
108
+
109
+ if (!section?.wsUrl) {
110
+ throw new Error("xiaoyou: wsUrl is required");
111
+ }
112
+
113
+ const resolved: ResolvedAccount = {
114
+ wsUrl: section.wsUrl,
115
+ authToken: section.authToken ?? "",
116
+ allowFrom: section.allowFrom ?? [],
117
+ dmPolicy: section.dmSecurity ?? "open",
118
+ reconnectIntervalMs: section.reconnectIntervalMs ?? 3000,
119
+ maxReconnectAttempts: section.maxReconnectAttempts ?? 0,
120
+ heartbeatIntervalMs: section.heartbeatIntervalMs ?? 30000,
121
+ heartbeatTimeoutMs: section.heartbeatTimeoutMs ?? 10000,
122
+ };
128
123
 
129
124
  // 断开已有连接
130
125
  if (_client) _client.disconnect();
@@ -135,7 +130,7 @@ export const xiayouPlugin = {
135
130
  onMessage: async (msg) => {
136
131
  const runtime = getRuntime();
137
132
  if (!runtime) {
138
- logger.error("[xiaoyou] runtime not available");
133
+ logger.error("[xiaoyou] runtime not available, cannot dispatch");
139
134
  return;
140
135
  }
141
136
  await runtime.inbound.dispatch({
@@ -158,12 +153,22 @@ export const xiayouPlugin = {
158
153
  client.connect();
159
154
  _client = client;
160
155
  logger.info("[xiaoyou] gateway started");
161
- return client;
156
+
157
+ // 保持 startAccount 挂起,直到 abortSignal 触发
158
+ return new Promise<void>((resolve) => {
159
+ if (ctx.abortSignal) {
160
+ ctx.abortSignal.addEventListener("abort", () => {
161
+ logger.info("[xiaoyou] abortSignal received, disconnecting");
162
+ client.disconnect();
163
+ if (_client === client) _client = null;
164
+ resolve();
165
+ });
166
+ }
167
+ });
162
168
  },
163
169
 
164
- stop: async (client: EnterpriseClient) => {
165
- client.disconnect();
166
- if (_client === client) _client = null;
170
+ stopAccount: async () => {
171
+ if (_client) { _client.disconnect(); _client = null; }
167
172
  },
168
173
  },
169
174
 
@@ -202,8 +207,8 @@ export const xiayouPlugin = {
202
207
  status: {
203
208
  describe: async ({ account }: any) => {
204
209
  const issues: Array<{ severity: string; message: string }> = [];
205
- const wsUrl = account?.wsUrl ?? account?.config?.wsUrl;
206
- const authToken = account?.authToken ?? account?.config?.authToken;
210
+ const wsUrl = account?.config?.wsUrl;
211
+ const authToken = account?.config?.authToken;
207
212
 
208
213
  if (!wsUrl) issues.push({ severity: "error", message: "wsUrl not configured" });
209
214
  if (!authToken) issues.push({ severity: "warning", message: "authToken not set" });