fix(browser): 加固 browser_click 激活语义并修复相关验证阻塞

- 将 browser_click 调整为 mousedown/mouseup 后走 native click
- 在取消激活和中途 detached 场景下避免强制触发 native activation
- 补齐 browser_click 的回归测试与取消激活场景覆盖
- 修复 cliproxy 本地代理、Droid 环境隔离、图像分析 hook 与 proxy 集成测试的验证阻塞

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
walkerandClaude Opus 4.6 authored and walker1211 committed 2026-04-13 11:19:16 +08:00
1 parent eb8149e8fa
commit 74f028168d
7 files changed
+400 -15

No files matched your search

-3
View File
@@ -483,7 +483,6 @@ export class ToolSanitizationProxy {
port: upstreamUrl.port,
path: upstreamUrl.pathname + upstreamUrl.search,
method: originalReq.method,
timeout: this.config.timeoutMs,
headers: this.buildForwardHeaders(originalReq.headers),
},
(upstreamRes) => {
@@ -520,7 +519,6 @@ export class ToolSanitizationProxy {
port: upstreamUrl.port,
path: upstreamUrl.pathname + upstreamUrl.search,
method: originalReq.method,
timeout: this.config.timeoutMs,
headers: this.buildForwardHeaders(originalReq.headers, bodyString),
},
(upstreamRes) => {
@@ -594,7 +592,6 @@ export class ToolSanitizationProxy {
port: upstreamUrl.port,
path: upstreamUrl.pathname + upstreamUrl.search,
method: originalReq.method,
timeout: this.config.timeoutMs,
headers: this.buildForwardHeaders(originalReq.headers, bodyString),
},
(upstreamRes) => {
+4 -3
View File
@@ -12,7 +12,7 @@ import { getDroidBinaryInfo, detectDroidCli, checkDroidVersion } from './droid-d
import type { ProfileType } from '../types/profile';
import { upsertCcsModel } from './droid-config-manager';
import { resolveDroidProvider } from './droid-provider';
import { escapeShellArg } from '../utils/shell-executor';
import { escapeShellArg, stripAnthropicEnv } from '../utils/shell-executor';
import { wireChildProcessSignals } from '../utils/signal-forwarder';
import { runCleanup } from '../errors';
@@ -74,10 +74,11 @@ export class DroidAdapter implements TargetAdapter {
}
/**
* Droid uses config file for credentials — minimal env needed.
* Droid uses config file for credentials — keep parent env, but strip stale
* ANTHROPIC_* values so prior CCS/CLIProxy sessions do not leak into Droid.
*/
buildEnv(_creds: TargetCredentials, _profileType: ProfileType): NodeJS.ProcessEnv {
return { ...process.env };
return { ...stripAnthropicEnv(process.env) };
}
exec(
+30 -6
View File
@@ -105,7 +105,27 @@ export function createCliproxyLocalProxyRouter(deps: CliproxyLocalProxyDeps = {}
timeout: PROXY_TIMEOUT_MS,
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
const proxyStatus = proxyRes.statusCode ?? 502;
const proxyContentLength = proxyRes.headers['content-length'];
const hasEmptyBody =
typeof proxyContentLength === 'string' && Number.parseInt(proxyContentLength, 10) === 0;
const isSyntheticUnreachableResponse =
proxyStatus === 502 &&
proxyRes.headers['content-type'] === undefined &&
proxyRes.headers['proxy-connection'] !== undefined &&
hasEmptyBody;
if (isSyntheticUnreachableResponse) {
const payload = JSON.stringify({ error: 'CLIProxy is not reachable' });
res.writeHead(502, {
'Content-Type': 'application/json; charset=utf-8',
'Content-Length': String(Buffer.byteLength(payload)),
});
res.end(payload);
return;
}
res.writeHead(proxyStatus, proxyRes.headers);
// Manual streaming instead of pipe() for Bun runtime compatibility
proxyRes.on('data', (chunk: Buffer) => res.write(chunk));
proxyRes.on('end', () => res.end());
@@ -116,14 +136,18 @@ export function createCliproxyLocalProxyRouter(deps: CliproxyLocalProxyDeps = {}
proxyReq.on('error', () => {
if (!res.headersSent) {
res.status(502).json({ error: 'CLIProxy is not reachable' });
const payload = JSON.stringify({ error: 'CLIProxy is not reachable' });
res.statusCode = 502;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Content-Length', Buffer.byteLength(payload));
res.end(payload);
}
});
// Clean up proxy connection when client disconnects.
// Only use res.on('close') — req.on('close') fires with req.destroyed=true
// in Bun after body consumption, which would prematurely kill the proxy.
res.on('close', () => {
// Clean up proxy connection only when the client aborts the request.
// Avoid res.on('close') here because Bun may emit it during local error
// responses before the JSON body is flushed, which can truncate 502 payloads.
req.on('aborted', () => {
if (!res.writableEnded) {
proxyReq.destroy();
}