openclaw-xiaoyou 1.1.0 → 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 +44 -48
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.1.0",
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,
46
- };
47
- }
48
-
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",
41
+ name: "Xiaoyou",
55
42
  };
56
43
  }
57
44
 
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,11 +70,11 @@ 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,
97
78
  isConfigured: (account: any): boolean => Boolean(account?.config?.wsUrl),
98
79
  describeAccount: (account: any) => ({
99
80
  accountId: account?.accountId || "default",
@@ -103,37 +84,41 @@ export const xiayouPlugin = {
103
84
  defaultAccountId: (): string => "default",
104
85
  },
105
86
 
106
- // ── DM 安全策略 ────────────────────────────────────
107
87
  security: {
108
88
  dm: {
109
89
  channelKey: "xiaoyou",
110
- resolvePolicy: (account: ResolvedAccount) => account.dmPolicy,
111
- resolveAllowFrom: (account: ResolvedAccount) => account.allowFrom,
112
- defaultPolicy: "allowlist",
90
+ resolvePolicy: (account: any) => account?.config?.dmSecurity ?? "open",
91
+ resolveAllowFrom: (account: any) => account?.config?.allowFrom ?? ["*"],
92
+ defaultPolicy: "open",
113
93
  },
114
94
  },
115
95
 
116
- // ── 回复线程模式 ───────────────────────────────────
117
96
  threading: { topLevelReplyToMode: "reply" as const },
118
97
 
119
98
  // ── Gateway 生命周期 ───────────────────────────────
120
- reload: { configPrefixes: ["channels.xiaoyou"] },
121
-
122
99
  gateway: {
123
100
  startAccount: async (ctx: any) => {
124
101
  const { account, cfg, log } = ctx;
102
+
103
+ // 注入 runtime
104
+ if (ctx.runtime) setRuntime(ctx.runtime);
105
+
125
106
  const section = account?.config || getChannelConfig(cfg);
126
107
  const logger = log || console;
127
108
 
109
+ if (!section?.wsUrl) {
110
+ throw new Error("xiaoyou: wsUrl is required");
111
+ }
112
+
128
113
  const resolved: ResolvedAccount = {
129
- wsUrl: section?.wsUrl ?? "",
130
- authToken: section?.authToken ?? "",
131
- allowFrom: section?.allowFrom ?? [],
132
- dmPolicy: section?.dmSecurity,
133
- reconnectIntervalMs: section?.reconnectIntervalMs ?? 3000,
134
- maxReconnectAttempts: section?.maxReconnectAttempts ?? 0,
135
- heartbeatIntervalMs: section?.heartbeatIntervalMs ?? 30000,
136
- heartbeatTimeoutMs: section?.heartbeatTimeoutMs ?? 10000,
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,
137
122
  };
138
123
 
139
124
  // 断开已有连接
@@ -145,7 +130,7 @@ export const xiayouPlugin = {
145
130
  onMessage: async (msg) => {
146
131
  const runtime = getRuntime();
147
132
  if (!runtime) {
148
- logger.error("[xiaoyou] runtime not available");
133
+ logger.error("[xiaoyou] runtime not available, cannot dispatch");
149
134
  return;
150
135
  }
151
136
  await runtime.inbound.dispatch({
@@ -168,7 +153,18 @@ export const xiayouPlugin = {
168
153
  client.connect();
169
154
  _client = client;
170
155
  logger.info("[xiaoyou] gateway started");
171
- 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
+ });
172
168
  },
173
169
 
174
170
  stopAccount: async () => {
@@ -211,8 +207,8 @@ export const xiayouPlugin = {
211
207
  status: {
212
208
  describe: async ({ account }: any) => {
213
209
  const issues: Array<{ severity: string; message: string }> = [];
214
- const wsUrl = account?.wsUrl ?? account?.config?.wsUrl;
215
- const authToken = account?.authToken ?? account?.config?.authToken;
210
+ const wsUrl = account?.config?.wsUrl;
211
+ const authToken = account?.config?.authToken;
216
212
 
217
213
  if (!wsUrl) issues.push({ severity: "error", message: "wsUrl not configured" });
218
214
  if (!authToken) issues.push({ severity: "warning", message: "authToken not set" });