ai-helper-agent 1.9.4 → 1.9.5
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 +1 -1
- package/src/executor.js +54 -4
package/package.json
CHANGED
package/src/executor.js
CHANGED
|
@@ -51,9 +51,14 @@ function getShellForExec() {
|
|
|
51
51
|
return { shell: '/bin/bash', args: ['-c'] };
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// 运行中的进程映射:execId → { process, wsClients: Set, timeoutId, forceKillId }
|
|
54
|
+
// 运行中的进程映射:execId → { process, wsClients: Set, timeoutId, forceKillId, stdoutBuf, stderrBuf }
|
|
55
55
|
const runningProcesses = new Map();
|
|
56
56
|
|
|
57
|
+
// 已完成进程缓存(解决竞态条件:进程完成太快,WebSocket 客户端来不及连接)
|
|
58
|
+
// execId → { stdoutBuf, stderrBuf, exitCode, killed, cleanupTimeout }
|
|
59
|
+
const completedProcesses = new Map();
|
|
60
|
+
const COMPLETED_CACHE_TTL = 30000; // 30秒后清理
|
|
61
|
+
|
|
57
62
|
// 安全的子进程环境变量白名单(不泄露宿主敏感信息)
|
|
58
63
|
const SAFE_ENV_KEYS = [
|
|
59
64
|
'PATH', 'HOME', 'USER', 'SHELL', 'TERM', 'LANG', 'LC_ALL', 'LC_CTYPE',
|
|
@@ -127,7 +132,8 @@ function executeCommand(command, cwd, wsClient, onComplete, collectOutput = fals
|
|
|
127
132
|
let finished = false; // 防止 close/error 重复处理
|
|
128
133
|
|
|
129
134
|
// 存储到 entry 中供 clearTimers 读取
|
|
130
|
-
|
|
135
|
+
// stdoutBuf/stderrBuf 始终收集,用于回放给延迟连接的 WS 客户端
|
|
136
|
+
const entry = { process: proc, wsClients, stdoutBuf: '', stderrBuf: '' };
|
|
131
137
|
runningProcesses.set(execId, entry);
|
|
132
138
|
|
|
133
139
|
// 超时控制
|
|
@@ -160,6 +166,7 @@ function executeCommand(command, cwd, wsClient, onComplete, collectOutput = fals
|
|
|
160
166
|
proc.stdout.on('data', (chunk) => {
|
|
161
167
|
const str = chunk.toString();
|
|
162
168
|
if (collectOutput) stdoutCollected += str;
|
|
169
|
+
entry.stdoutBuf += str;
|
|
163
170
|
broadcast({ type: 'stdout', data: str, execId });
|
|
164
171
|
});
|
|
165
172
|
|
|
@@ -167,6 +174,7 @@ function executeCommand(command, cwd, wsClient, onComplete, collectOutput = fals
|
|
|
167
174
|
proc.stderr.on('data', (chunk) => {
|
|
168
175
|
const str = chunk.toString();
|
|
169
176
|
if (collectOutput) stderrCollected += str;
|
|
177
|
+
entry.stderrBuf += str;
|
|
170
178
|
broadcast({ type: 'stderr', data: str, execId });
|
|
171
179
|
});
|
|
172
180
|
|
|
@@ -175,12 +183,24 @@ function executeCommand(command, cwd, wsClient, onComplete, collectOutput = fals
|
|
|
175
183
|
if (finished) return;
|
|
176
184
|
finished = true;
|
|
177
185
|
clearTimers();
|
|
178
|
-
|
|
186
|
+
const finalExitCode = typeof exitCode === 'number' ? exitCode : -1;
|
|
187
|
+
broadcast({ type: 'exit', exitCode: finalExitCode, execId, killed });
|
|
188
|
+
// 移入已完成缓存,解决竞态条件:延迟连接的 WS 客户端可回放输出
|
|
189
|
+
const cleanupTimeout = setTimeout(() => {
|
|
190
|
+
completedProcesses.delete(execId);
|
|
191
|
+
}, COMPLETED_CACHE_TTL);
|
|
192
|
+
completedProcesses.set(execId, {
|
|
193
|
+
stdoutBuf: entry.stdoutBuf,
|
|
194
|
+
stderrBuf: entry.stderrBuf,
|
|
195
|
+
exitCode: finalExitCode,
|
|
196
|
+
killed,
|
|
197
|
+
cleanupTimeout
|
|
198
|
+
});
|
|
179
199
|
runningProcesses.delete(execId);
|
|
180
200
|
if (onComplete) {
|
|
181
201
|
onComplete({
|
|
182
202
|
execId,
|
|
183
|
-
exitCode:
|
|
203
|
+
exitCode: finalExitCode,
|
|
184
204
|
killed,
|
|
185
205
|
stdout: collectOutput ? stdoutCollected : undefined,
|
|
186
206
|
stderr: collectOutput ? stderrCollected : undefined
|
|
@@ -194,6 +214,18 @@ function executeCommand(command, cwd, wsClient, onComplete, collectOutput = fals
|
|
|
194
214
|
finished = true;
|
|
195
215
|
clearTimers();
|
|
196
216
|
broadcast({ type: 'error', error: err.message, execId });
|
|
217
|
+
// 移入已完成缓存
|
|
218
|
+
const cleanupTimeout = setTimeout(() => {
|
|
219
|
+
completedProcesses.delete(execId);
|
|
220
|
+
}, COMPLETED_CACHE_TTL);
|
|
221
|
+
completedProcesses.set(execId, {
|
|
222
|
+
stdoutBuf: entry.stdoutBuf,
|
|
223
|
+
stderrBuf: entry.stderrBuf,
|
|
224
|
+
exitCode: -1,
|
|
225
|
+
killed: false,
|
|
226
|
+
error: err.message,
|
|
227
|
+
cleanupTimeout
|
|
228
|
+
});
|
|
197
229
|
runningProcesses.delete(execId);
|
|
198
230
|
if (onComplete) {
|
|
199
231
|
onComplete({
|
|
@@ -241,6 +273,7 @@ function executeCommandSync(command, cwd) {
|
|
|
241
273
|
|
|
242
274
|
/**
|
|
243
275
|
* 添加 WebSocket 客户端到已有进程的监听中
|
|
276
|
+
* 如果进程已结束,从 completedProcesses 缓存回放全部输出
|
|
244
277
|
*/
|
|
245
278
|
function addWsClient(execId, wsClient) {
|
|
246
279
|
const entry = runningProcesses.get(execId);
|
|
@@ -248,6 +281,23 @@ function addWsClient(execId, wsClient) {
|
|
|
248
281
|
entry.wsClients.add(wsClient);
|
|
249
282
|
return true;
|
|
250
283
|
}
|
|
284
|
+
// 进程已结束,检查缓存并回放全部输出
|
|
285
|
+
const completed = completedProcesses.get(execId);
|
|
286
|
+
if (completed) {
|
|
287
|
+
if (completed.stdoutBuf) {
|
|
288
|
+
wsClient.send(JSON.stringify({ type: 'stdout', data: completed.stdoutBuf, execId }));
|
|
289
|
+
}
|
|
290
|
+
if (completed.stderrBuf) {
|
|
291
|
+
wsClient.send(JSON.stringify({ type: 'stderr', data: completed.stderrBuf, execId }));
|
|
292
|
+
}
|
|
293
|
+
if (completed.error) {
|
|
294
|
+
wsClient.send(JSON.stringify({ type: 'error', error: completed.error, execId }));
|
|
295
|
+
}
|
|
296
|
+
wsClient.send(JSON.stringify({ type: 'exit', exitCode: completed.exitCode, execId, killed: completed.killed }));
|
|
297
|
+
clearTimeout(completed.cleanupTimeout);
|
|
298
|
+
completedProcesses.delete(execId);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
251
301
|
return false;
|
|
252
302
|
}
|
|
253
303
|
|