claw-subagent-service 0.0.23 → 0.0.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -1,135 +1,161 @@
1
- const { exec } = require('child_process');
2
- const { promisify } = require('util');
3
- const os = require('os');
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- const execAsync = promisify(exec);
8
-
9
- /**
10
- * 获取实际用户主目录(SYSTEM 账户下 os.homedir() 返回 systemprofile)
11
- */
12
- function getRealHomeDir() {
13
- const envHome = process.env.CLAW_SERVICE_HOME || process.env.USERPROFILE || process.env.HOME;
14
- if (envHome && !envHome.includes('systemprofile')) {
15
- return envHome;
16
- }
17
- const homeDir = os.homedir();
18
- if (!homeDir.includes('systemprofile')) {
19
- return homeDir;
20
- }
21
- // SYSTEM 账户兜底:扫描 C:\Users 找包含 .openclaw 的实际用户目录
22
- const usersDir = 'C:\\Users';
23
- if (fs.existsSync(usersDir)) {
24
- const entries = fs.readdirSync(usersDir, { withFileTypes: true });
25
- for (const entry of entries) {
26
- if (entry.isDirectory() && !['Public', 'Default', 'All Users', 'Default User'].includes(entry.name)) {
27
- const candidate = path.join(usersDir, entry.name);
28
- if (fs.existsSync(path.join(candidate, '.openclaw'))) {
29
- return candidate;
30
- }
31
- }
32
- }
33
- }
34
- return homeDir;
35
- }
36
-
37
- class OpenClawClient {
38
- constructor(log) {
39
- this.log = log;
40
- }
41
-
42
- async chat(message, fromUser) {
43
- if (!message || !message.trim()) {
44
- return '消息内容为空';
45
- }
46
- this.log?.info(`[OpenClawClient] 准备发送消息到 OpenClaw,from=${fromUser}, message=${message.substring(0, 50)}`);
47
- // 固定 session-id 前缀,确保静默服务的会话与桌面客户端完全隔离
48
- const sessionId = `clawmessenger-${fromUser}`;
49
- // 转义消息中的特殊字符:双引号和换行符
50
- const escapedMessage = message
51
- .replace(/\\/g, '\\\\')
52
- .replace(/"/g, '\\"')
53
- .replace(/\r\n/g, ' ')
54
- .replace(/\n/g, ' ')
55
- .replace(/\r/g, ' ');
56
- // 使用默认 agent (main) 处理消息,不指定 --agent 参数
57
- const gatewayUrl = 'http://127.0.0.1:5678';
58
- const cmd = `openclaw agent -m "${escapedMessage}" --session-id ${sessionId}`;
59
-
60
- this.log?.info(`[OpenClawClient] 执行命令: ${cmd}`);
61
- this.log?.info(`[OpenClawClient] Gateway URL: ${gatewayUrl}`);
62
-
63
- // 注入实际用户目录,确保 openclaw CLI 能读取到正确的配置和 API key
64
- const realHome = getRealHomeDir();
65
- this.log?.info(`[OpenClawClient] 使用用户目录: ${realHome}`);
66
-
67
- try {
68
- const { stdout, stderr } = await execAsync(cmd, {
69
- timeout: 1200000,
70
- maxBuffer: 1024 * 1024,
71
- shell: true,
72
- windowsHide: true,
73
- env: {
74
- ...process.env,
75
- OPENCLAW_GATEWAY_URL: gatewayUrl,
76
- USERPROFILE: realHome,
77
- HOME: realHome,
78
- },
79
- });
80
-
81
- this.log?.info(`[OpenClawClient] stdout: ${stdout?.substring(0, 200) || '(empty)'}`);
82
- if (stderr) this.log?.error(`[OpenClawClient] stderr: ${stderr?.substring(0, 200) || '(empty)'}`);
83
-
84
- const output = stdout || stderr || '';
85
- return this.cleanOutput(output);
86
- } catch (err) {
87
- this.log?.error(`[OpenClawClient] 执行异常: ${err.message}`);
88
- this.log?.error(`[OpenClawClient] 错误码: ${err.code}`);
89
- if (err.killed) {
90
- return 'OpenClaw 响应超时';
91
- }
92
- if (err.code === 'ENOENT') {
93
- return '找不到 openclaw 命令';
94
- }
95
- return `OpenClaw 调用失败: ${err.message}`;
96
- }
97
- }
98
-
99
- cleanOutput(output) {
100
- const lines = output.split('\n');
101
- const cleanLines = [];
102
-
103
- for (const line of lines) {
104
- const trimmed = line.trim();
105
- if (!trimmed) continue;
106
-
107
- // 移除 ANSI 颜色代码
108
- const cleanLine = trimmed.replace(/\x1B\[[0-9;]*m/g, '');
109
-
110
- // 跳过所有调试/配置日志行
111
- if (cleanLine.startsWith('[ws]')) continue;
112
- if (cleanLine.startsWith('[health-monitor]')) continue;
113
- if (cleanLine.startsWith('[OpenClawConfig]')) continue;
114
- if (cleanLine.startsWith('[plugins]')) continue;
115
- if (cleanLine.includes('龙虾信使插件已注册')) continue;
116
- if (cleanLine.includes('龙虾信使')) continue;
117
- if (cleanLine.includes('已加载配置文件')) continue;
118
- if (cleanLine.includes('plugins.allow')) continue;
119
- if (cleanLine.includes('Config warnings')) continue;
120
- if (cleanLine.includes('stale config')) continue;
121
- if (cleanLine.includes('plugin not found')) continue;
122
- if (cleanLine.includes('⇄ res')) continue;
123
- if (cleanLine.includes('chat.history')) continue;
124
- if (cleanLine.includes('models.list')) continue;
125
- if (cleanLine.includes('node.list')) continue;
126
- if (/^\d{2}:\d{2}:\d{2}/.test(cleanLine)) continue; // 时间戳开头的日志
127
-
128
- cleanLines.push(cleanLine);
129
- }
130
-
131
- return cleanLines.join('\n').trim() || 'OpenClaw 未返回有效响应';
132
- }
133
- }
134
-
135
- module.exports = { OpenClawClient };
1
+ const { spawn } = require('child_process');
2
+ const os = require('os');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * 获取实际用户主目录(SYSTEM 账户下 os.homedir() 返回 systemprofile)
8
+ */
9
+ function getRealHomeDir() {
10
+ const envHome = process.env.CLAW_SERVICE_HOME || process.env.USERPROFILE || process.env.HOME;
11
+ if (envHome && !envHome.includes('systemprofile')) {
12
+ return envHome;
13
+ }
14
+ const homeDir = os.homedir();
15
+ if (!homeDir.includes('systemprofile')) {
16
+ return homeDir;
17
+ }
18
+ // SYSTEM 账户兜底:扫描 C:\Users 找包含 .openclaw 的实际用户目录
19
+ const usersDir = 'C:\\Users';
20
+ if (fs.existsSync(usersDir)) {
21
+ const entries = fs.readdirSync(usersDir, { withFileTypes: true });
22
+ for (const entry of entries) {
23
+ if (entry.isDirectory() && !['Public', 'Default', 'All Users', 'Default User'].includes(entry.name)) {
24
+ const candidate = path.join(usersDir, entry.name);
25
+ if (fs.existsSync(path.join(candidate, '.openclaw'))) {
26
+ return candidate;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ return homeDir;
32
+ }
33
+
34
+ class OpenClawClient {
35
+ constructor(log) {
36
+ this.log = log;
37
+ }
38
+
39
+ async chat(message, fromUser) {
40
+ if (!message || !message.trim()) {
41
+ return '消息内容为空';
42
+ }
43
+ this.log?.info(`[OpenClawClient] 准备发送消息到 OpenClaw,from=${fromUser}, message=${message.substring(0, 50)}`);
44
+
45
+ const sessionId = `clawmessenger-${fromUser}`;
46
+ const escapedMessage = message
47
+ .replace(/\\/g, '\\\\')
48
+ .replace(/"/g, '\\"')
49
+ .replace(/\r\n/g, ' ')
50
+ .replace(/\n/g, ' ')
51
+ .replace(/\r/g, ' ');
52
+
53
+ const gatewayUrl = 'http://127.0.0.1:5678';
54
+ const realHome = getRealHomeDir();
55
+ this.log?.info(`[OpenClawClient] 使用用户目录: ${realHome}`);
56
+
57
+ const args = ['agent', '-m', escapedMessage, '--session-id', sessionId];
58
+ this.log?.info(`[OpenClawClient] 执行: openclaw ${args.join(' ')}`);
59
+
60
+ return new Promise((resolve) => {
61
+ let stdout = '';
62
+ let stderr = '';
63
+ let killed = false;
64
+
65
+ const child = spawn('openclaw', args, {
66
+ shell: true,
67
+ windowsHide: true,
68
+ env: {
69
+ ...process.env,
70
+ OPENCLAW_GATEWAY_URL: gatewayUrl,
71
+ USERPROFILE: realHome,
72
+ HOME: realHome,
73
+ },
74
+ });
75
+
76
+ // 流式收集输出,无 maxBuffer 限制
77
+ child.stdout.on('data', (chunk) => {
78
+ stdout += chunk.toString();
79
+ });
80
+ child.stderr.on('data', (chunk) => {
81
+ stderr += chunk.toString();
82
+ });
83
+
84
+ // 超时兜底(20 分钟)
85
+ const timeout = setTimeout(() => {
86
+ killed = true;
87
+ child.kill('SIGTERM');
88
+ this.log?.error('[OpenClawClient] 执行超时,强制终止');
89
+ }, 1200000);
90
+
91
+ child.on('error', (err) => {
92
+ clearTimeout(timeout);
93
+ this.log?.error(`[OpenClawClient] 子进程错误: ${err.message}`);
94
+ if (err.code === 'ENOENT') {
95
+ resolve('找不到 openclaw 命令');
96
+ } else {
97
+ resolve(`OpenClaw 调用失败: ${err.message}`);
98
+ }
99
+ });
100
+
101
+ child.on('close', (code) => {
102
+ clearTimeout(timeout);
103
+
104
+ if (killed) {
105
+ resolve('OpenClaw 响应超时');
106
+ return;
107
+ }
108
+
109
+ this.log?.info(`[OpenClawClient] 进程退出 code=${code}`);
110
+ this.log?.info(`[OpenClawClient] stdout 长度: ${stdout.length}, stderr 长度: ${stderr.length}`);
111
+
112
+ if (code !== 0) {
113
+ const errOutput = stderr || stdout || '';
114
+ this.log?.error(`[OpenClawClient] 错误输出: ${errOutput.substring(0, 500)}`);
115
+ resolve(`OpenClaw 调用失败: ${errOutput.substring(0, 200)}`);
116
+ return;
117
+ }
118
+
119
+ const output = stdout || stderr || '';
120
+ resolve(this.cleanOutput(output));
121
+ });
122
+ });
123
+ }
124
+
125
+ cleanOutput(output) {
126
+ const lines = output.split('\n');
127
+ const cleanLines = [];
128
+
129
+ for (const line of lines) {
130
+ const trimmed = line.trim();
131
+ if (!trimmed) continue;
132
+
133
+ // 移除 ANSI 颜色代码
134
+ const cleanLine = trimmed.replace(/\x1B\[[0-9;]*m/g, '');
135
+
136
+ // 跳过所有调试/配置日志行
137
+ if (cleanLine.startsWith('[ws]')) continue;
138
+ if (cleanLine.startsWith('[health-monitor]')) continue;
139
+ if (cleanLine.startsWith('[OpenClawConfig]')) continue;
140
+ if (cleanLine.startsWith('[plugins]')) continue;
141
+ if (cleanLine.includes('龙虾信使插件已注册')) continue;
142
+ if (cleanLine.includes('龙虾信使')) continue;
143
+ if (cleanLine.includes('已加载配置文件')) continue;
144
+ if (cleanLine.includes('plugins.allow')) continue;
145
+ if (cleanLine.includes('Config warnings')) continue;
146
+ if (cleanLine.includes('stale config')) continue;
147
+ if (cleanLine.includes('plugin not found')) continue;
148
+ if (cleanLine.includes('⇄ res')) continue;
149
+ if (cleanLine.includes('chat.history')) continue;
150
+ if (cleanLine.includes('models.list')) continue;
151
+ if (cleanLine.includes('node.list')) continue;
152
+ if (/^\d{2}:\d{2}:\d{2}/.test(cleanLine)) continue; // 时间戳开头的日志
153
+
154
+ cleanLines.push(cleanLine);
155
+ }
156
+
157
+ return cleanLines.join('\n').trim() || 'OpenClaw 未返回有效响应';
158
+ }
159
+ }
160
+
161
+ module.exports = { OpenClawClient };