mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 12:16:38 +00:00
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:
1 parent
eb8149e8fa
commit
74f028168d
7 files changed
+400
-15
No files matched your search
@@ -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) => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user