evolclaw 3.1.8 → 3.1.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.1.10 (2026-06-04)
4
+
5
+ ### Bug Fixes
6
+
7
+ - **Windows `evolclaw watch web` 仍 ENOENT(续 3.1.9)** — `where` 会列出同名的全部文件,npm 全局 bin 同时生成无后缀 sh 包装脚本和 `.cmd`/`.ps1`;3.1.9 取首行恰好选中无法在 Windows 执行的 sh 包装。改为优先选 PATHEXT 可执行后缀(`.cmd`/`.exe`/`.bat`/`.com`)。另外 Node 18.20+/20+/22 起 `execFile` 拒绝直接 spawn `.cmd`/`.bat`(CVE-2024-27980),改为 `shell:true` + 引号包裹参数执行
8
+
9
+ ## v3.1.9 (2026-06-04)
10
+
11
+ ### Bug Fixes
12
+
13
+ - **Windows `evolclaw watch web` 启动失败(ENOENT)** — Windows 上全局 bin 是 `evolclaw-web.cmd`,`execFileSync('evolclaw-web')` 不补 `.cmd` 后缀导致 spawn ENOENT。新增 `resolveCommandPath()` 通过 `where`/`which` 解析可执行文件真实绝对路径(不缓存,刚安装的命令会重新探测)再执行;定位失败时给出"重开终端"提示
14
+
3
15
  ## v3.1.8 (2026-06-04)
4
16
 
5
17
  ### Bug Fixes
package/dist/cli/index.js CHANGED
@@ -2117,7 +2117,24 @@ async function cmdWatchWeb() {
2117
2117
  process.exit(1);
2118
2118
  }
2119
2119
  }
2120
- execFileSync('evolclaw-web', ['--home', home], { stdio: 'inherit' });
2120
+ // 解析可执行文件的真实绝对路径:
2121
+ // - Windows 上 bin 是 evolclaw-web.cmd,execFileSync 不会自动补后缀
2122
+ // - 刚安装的命令可能不在当前进程已缓存的 PATH 里,用 where/which 重新探测
2123
+ const exe = platform.resolveCommandPath('evolclaw-web');
2124
+ if (!exe) {
2125
+ process.stderr.write('❌ 已安装 evolclaw-web 但无法定位可执行文件。\n 请重新打开终端后再次运行,或手动执行: evolclaw-web --home ' + home + '\n');
2126
+ process.exit(1);
2127
+ }
2128
+ // Node 18.20+/20+/22 起,execFile 拒绝直接 spawn .cmd/.bat(CVE-2024-27980),必须 shell:true。
2129
+ // shell 模式下含空格的路径/参数需加引号。
2130
+ const isBatch = /\.(cmd|bat)$/i.test(exe);
2131
+ if (isBatch) {
2132
+ const q = (s) => `"${s}"`;
2133
+ execFileSync(q(exe), ['--home', q(home)], { stdio: 'inherit', shell: true });
2134
+ }
2135
+ else {
2136
+ execFileSync(exe, ['--home', home], { stdio: 'inherit' });
2137
+ }
2121
2138
  }
2122
2139
  async function cmdRestartMonitor() {
2123
2140
  const p = resolvePaths();
@@ -162,7 +162,7 @@ export class IMRenderer {
162
162
  }
163
163
  /** 添加工具调用 */
164
164
  addToolCall(name, input, callId, descText, turn, outputTokens) {
165
- this.emitProgress('tool_call', outputTokens, turn);
165
+ this.emitProgress('tool_call', outputTokens, turn, { toolName: name, callId });
166
166
  if (this.opts.envelope.chatmode === 'proactive')
167
167
  return;
168
168
  if (this.opts.suppressActivities)
@@ -181,7 +181,7 @@ export class IMRenderer {
181
181
  }
182
182
  /** 添加工具结果 */
183
183
  addToolResult(name, ok, result, error, callId, durationMs, descText) {
184
- this.emitProgress('tool_result');
184
+ this.emitProgress('tool_result', undefined, undefined, { toolName: name, callId, ok, durationMs });
185
185
  if (this.opts.envelope.chatmode === 'proactive')
186
186
  return;
187
187
  if (this.opts.suppressActivities)
@@ -369,8 +369,19 @@ export class IMRenderer {
369
369
  }
370
370
  }
371
371
  // ── 内部:status.progress 发送 ──
372
- emitProgress(activityType, outputTokens, turn) {
373
- const payload = { kind: 'status.progress', metadata: { activityType, ...(turn != null && { turn }), ...(outputTokens != null && { outputTokens }) } };
372
+ emitProgress(activityType, outputTokens, turn, extra) {
373
+ const payload = {
374
+ kind: 'status.progress',
375
+ metadata: {
376
+ activityType,
377
+ ...(turn != null && { turn }),
378
+ ...(outputTokens != null && { outputTokens }),
379
+ ...(extra?.toolName != null && { toolName: extra.toolName }),
380
+ ...(extra?.callId != null && { callId: extra.callId }),
381
+ ...(extra?.ok != null && { ok: extra.ok }),
382
+ ...(extra?.durationMs != null && { durationMs: extra.durationMs }),
383
+ },
384
+ };
374
385
  this.opts.send(payload).catch(() => { });
375
386
  }
376
387
  // ── 内部:proactive 模式(逐事件 activity.batch[1 item]) ──
@@ -392,7 +403,12 @@ export class IMRenderer {
392
403
  const outputTokens = event.outputTokens;
393
404
  const turn = event.turn;
394
405
  const activityType = item.kind === 'text' ? 'text' : item.kind === 'tool_call' ? 'tool_call' : 'tool_result';
395
- this.emitProgress(activityType, outputTokens, turn);
406
+ const extra = item.kind === 'tool_call'
407
+ ? { toolName: item.name, callId: item.call_id }
408
+ : item.kind === 'tool_result'
409
+ ? { toolName: item.name, callId: item.call_id, ok: item.ok, durationMs: item.duration_ms }
410
+ : undefined;
411
+ this.emitProgress(activityType, outputTokens, turn, extra);
396
412
  const payload = { kind: 'activity.batch', items: [item] };
397
413
  // fire-and-forget
398
414
  this.opts.send(payload).catch(err => {
@@ -29,7 +29,7 @@ export class MessageBridge {
29
29
  this.messageQueue = messageQueue;
30
30
  this.cmdHandler = cmdHandler;
31
31
  this.eventBus = eventBus;
32
- this.defaultDebounce = defaultDebounce ?? 2;
32
+ this.defaultDebounce = defaultDebounce ?? 0;
33
33
  }
34
34
  /** Inject EvolAgentRegistry so owner lookups/writes route to agent.json for agent-owned channels. */
35
35
  setAgentRegistry(registry) {
@@ -133,6 +133,35 @@ export function commandExists(cmd) {
133
133
  _commandExistsCache.set(cmd, exists);
134
134
  return exists;
135
135
  }
136
+ /**
137
+ * 解析命令的真实可执行文件绝对路径。
138
+ * Windows: `where` 会列出全部同名文件(npm 全局 bin 同时生成无后缀 sh 包装、.cmd、.ps1),
139
+ * 其中无后缀的那个是 Unix sh 脚本,Windows 无法直接 spawn(ENOENT)。
140
+ * 因此优先选 PATHEXT 可执行后缀(.cmd/.exe/.bat/.com),都没有才退回首行。
141
+ * 失败返回 null。不缓存——刚安装的命令需要重新探测。
142
+ */
143
+ export function resolveCommandPath(cmd) {
144
+ try {
145
+ if (isWindows) {
146
+ const r = spawnSync('where', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
147
+ if (r.status !== 0 || !r.stdout)
148
+ return null;
149
+ const candidates = r.stdout.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
150
+ if (candidates.length === 0)
151
+ return null;
152
+ const execExts = ['.cmd', '.exe', '.bat', '.com'];
153
+ const runnable = candidates.find(p => execExts.some(ext => p.toLowerCase().endsWith(ext)));
154
+ return runnable || candidates[0];
155
+ }
156
+ else {
157
+ const out = execFileSync('which', [cmd], { encoding: 'utf-8', stdio: 'pipe' }).trim();
158
+ return out || null;
159
+ }
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ }
136
165
  /**
137
166
  * Cross-platform live log tailing (replaces tail -f).
138
167
  * Returns an abort function.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolclaw",
3
- "version": "3.1.8",
3
+ "version": "3.1.10",
4
4
  "description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",