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
|
-
|
|
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 = {
|
|
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
|
-
|
|
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 ??
|
|
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