diff --git a/packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts b/packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts index c1302c11f0..e91c19a290 100644 --- a/packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts +++ b/packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts @@ -9,6 +9,7 @@ import { SocketConnectionError } from './mcpSocketClient.js' import { localPlatformLabel, type BridgePermissionRequest, + toLoggerDetail, type ChromeExtensionInfo, type ClaudeForChromeContext, type PermissionMode, @@ -578,7 +579,7 @@ export class BridgeClient implements SocketClient { const durationMs = Date.now() - this.connectionStartTime logger.error( `[${serverName}] Failed to create WebSocket after ${durationMs}ms:`, - error, + toLoggerDetail(error), ) trackEvent?.('chrome_bridge_connection_failed', { duration_ms: durationMs, @@ -618,7 +619,10 @@ export class BridgeClient implements SocketClient { ) this.handleMessage(message) } catch (error) { - logger.error(`[${serverName}] Failed to parse bridge message:`, error) + logger.error( + `[${serverName}] Failed to parse bridge message:`, + toLoggerDetail(error), + ) } }) @@ -862,7 +866,10 @@ export class BridgeClient implements SocketClient { const allowed = await pending.onPermissionRequest(request) this.sendPermissionResponse(requestId, allowed) } catch (error) { - logger.error(`[${serverName}] Error handling permission request:`, error) + logger.error( + `[${serverName}] Error handling permission request:`, + toLoggerDetail(error), + ) this.sendPermissionResponse(requestId, false) } } diff --git a/packages/@ant/claude-for-chrome-mcp/src/index.ts b/packages/@ant/claude-for-chrome-mcp/src/index.ts index c6d82f73fe..cebec3a504 100644 --- a/packages/@ant/claude-for-chrome-mcp/src/index.ts +++ b/packages/@ant/claude-for-chrome-mcp/src/index.ts @@ -8,8 +8,11 @@ export { localPlatformLabel } from './types.js' export type { BridgeConfig, ChromeExtensionInfo, + ChromeBridgeTrackEventMetadata, ClaudeForChromeContext, Logger, + LoggerDetail, PermissionMode, SocketClient, } from './types.js' +export { toLoggerDetail } from './types.js' diff --git a/packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts b/packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts index f359667ad8..4dbfa289e4 100644 --- a/packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts +++ b/packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts @@ -9,6 +9,7 @@ import type { PermissionMode, PermissionOverrides, } from './types.js' +import { toLoggerDetail } from './types.js' export class SocketConnectionError extends Error { constructor(message: string) { @@ -87,7 +88,10 @@ class McpSocketClient { await this.validateSocketSecurity(socketPath) } catch (error) { this.connecting = false - logger.info(`[${serverName}] Security validation failed:`, error) + logger.info( + `[${serverName}] Security validation failed:`, + toLoggerDetail(error), + ) // Don't retry on security failures (wrong perms/owner) - those won't // self-resolve. Only the error handler retries on transient errors. return @@ -145,14 +149,20 @@ class McpSocketClient { logger.info(`[${serverName}] Received unknown message: ${message}`) } } catch (error) { - logger.info(`[${serverName}] Failed to parse message:`, error) + logger.info( + `[${serverName}] Failed to parse message:`, + toLoggerDetail(error), + ) } } }) this.socket.on('error', (error: Error & { code?: string }) => { clearTimeout(connectTimeout) - logger.info(`[${serverName}] Socket error (code: ${error.code}):`, error) + logger.info( + `[${serverName}] Socket error (code: ${error.code}):`, + toLoggerDetail(error), + ) this.connected = false this.connecting = false diff --git a/packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts b/packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts index 587428d404..15584cd89c 100644 --- a/packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts +++ b/packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts @@ -7,6 +7,7 @@ import type { PermissionOverrides, SocketClient, } from './types.js' +import { toLoggerDetail } from './types.js' export const handleToolCall = async ( context: ClaudeForChromeContext, @@ -44,7 +45,10 @@ export const handleToolCall = async ( return handleToolCallDisconnected(context) } catch (error) { - context.logger.info(`[${context.serverName}] Error calling tool:`, error) + context.logger.info( + `[${context.serverName}] Error calling tool:`, + toLoggerDetail(error), + ) if (error instanceof SocketConnectionError) { return handleToolCallDisconnected(context) @@ -165,8 +169,7 @@ async function handleToolCallConnected( // Fallback for unexpected result format context.logger.warn( - `[${context.serverName}] Unexpected result format from socket bridge`, - response, + `[${context.serverName}] Unexpected result format from socket bridge: ${JSON.stringify(response)}`, ) return { diff --git a/packages/@ant/claude-for-chrome-mcp/src/types.ts b/packages/@ant/claude-for-chrome-mcp/src/types.ts index 67927e0027..989deab8f0 100644 --- a/packages/@ant/claude-for-chrome-mcp/src/types.ts +++ b/packages/@ant/claude-for-chrome-mcp/src/types.ts @@ -1,11 +1,84 @@ +/** + * Logger 第二参数的可选类型。 + * 调用方通过 util.format 追加详情,实践中多为 catch 到的异常对象。 + */ +export type LoggerDetail = Error | NodeJS.ErrnoException + +/** 将 unknown 收窄为 LoggerDetail,供 catch 块传给 logger 使用。 */ +export function toLoggerDetail(detail: unknown): LoggerDetail | undefined { + return detail instanceof Error ? detail : undefined +} + +/** 宿主注入的日志接口,与 DebugLogger(util.format)对齐。 */ export interface Logger { - info: (message: string, ...args: unknown[]) => void - error: (message: string, ...args: unknown[]) => void - warn: (message: string, ...args: unknown[]) => void - debug: (message: string, ...args: unknown[]) => void - silly: (message: string, ...args: unknown[]) => void + info: (message: string, detail?: LoggerDetail) => void // 信息 + error: (message: string, detail?: LoggerDetail) => void // 错误 + warn: (message: string, detail?: LoggerDetail) => void // 警告 + debug: (message: string, detail?: LoggerDetail) => void // 调试 + silly: (message: string, detail?: LoggerDetail) => void // 最细粒度调试 +} + +/** + * Bridge 连接失败时的 error_type 枚举。 + * 由 bridgeClient 在 getUserId / getOAuthToken / WebSocket 创建失败时上报。 + */ +export type ChromeBridgeConnectionErrorType = + | 'no_user_id' // 无法获取用户 UUID + | 'no_oauth_token' // 无法获取 OAuth token + | 'websocket_error' // WebSocket 创建或运行异常 + +/** 工具调用相关遥测元数据(started / completed / timeout / error)。 */ +export type ChromeBridgeToolCallMetadata = { + tool_name: string // MCP 工具名 + tool_use_id: string // 本次调用的 UUID + duration_ms?: number // 耗时(毫秒) + timeout_ms?: number // 超时阈值(毫秒),仅 timeout 事件 + error_message?: string // 错误摘要(截断),仅 error 事件 +} + +/** Bridge 连接失败遥测元数据。 */ +export type ChromeBridgeConnectionFailedMetadata = { + duration_ms: number // 自连接开始到失败的耗时(毫秒) + error_type: ChromeBridgeConnectionErrorType // 失败原因分类 + reconnect_attempt: number // 当前重连尝试次数 } +/** Bridge 开始连接遥测元数据。 */ +export type ChromeBridgeConnectionStartedMetadata = { + bridge_url: string // 目标 WebSocket URL(含用户路径) +} + +/** Bridge 断开连接遥测元数据。 */ +export type ChromeBridgeDisconnectedMetadata = { + close_code: number // WebSocket 关闭码 + duration_since_connect_ms: number // 自连接成功到断开的时长(毫秒) + reconnect_attempt: number // 即将进行的重连序号 +} + +/** Bridge 连接成功遥测元数据。 */ +export type ChromeBridgeConnectionSucceededMetadata = { + duration_ms: number // 自开始到连接就绪的耗时(毫秒) + status: 'paired' | 'waiting' // paired=已配对扩展;waiting=等待扩展接入 +} + +/** Bridge 重连次数耗尽遥测元数据。 */ +export type ChromeBridgeReconnectExhaustedMetadata = { + total_attempts: number // 累计重连次数上限 +} + +/** + * trackEvent 回调的 metadata 联合类型。 + * 各变体对应 bridgeClient 内 chrome_bridge_* 事件;null 表示无附加字段。 + */ +export type ChromeBridgeTrackEventMetadata = + | ChromeBridgeToolCallMetadata + | ChromeBridgeConnectionFailedMetadata + | ChromeBridgeConnectionStartedMetadata + | ChromeBridgeDisconnectedMetadata + | ChromeBridgeConnectionSucceededMetadata + | ChromeBridgeReconnectExhaustedMetadata + | null // 无元数据(如 peer_connected / peer_disconnected) + export type PermissionMode = | 'ask' | 'skip_all_permission_checks' @@ -48,10 +121,10 @@ export interface ClaudeForChromeContext { bridgeConfig?: BridgeConfig /** If set, permission mode is sent to the extension immediately on bridge connection. */ initialPermissionMode?: PermissionMode - /** Optional callback to track telemetry events for bridge connections */ - trackEvent?: ( - eventName: K, - metadata: Record | null, + /** Bridge 遥测回调;eventName 为 chrome_bridge_* 事件名 */ + trackEvent?: ( + eventName: string, // 事件名 + metadata: ChromeBridgeTrackEventMetadata, // 事件元数据 ) => void /** Called when user pairs with an extension via the browser pairing flow. */ onExtensionPaired?: (deviceId: string, name: string) => void diff --git a/packages/@ant/computer-use-mcp/src/pixelCompare.ts b/packages/@ant/computer-use-mcp/src/pixelCompare.ts index b8f0efae3b..4c898d2136 100644 --- a/packages/@ant/computer-use-mcp/src/pixelCompare.ts +++ b/packages/@ant/computer-use-mcp/src/pixelCompare.ts @@ -20,7 +20,7 @@ */ import type { ScreenshotResult } from './executor.js' -import type { Logger } from './types.js' +import { type Logger, toLoggerDetail } from './types.js' /** Injected by the host. See `ComputerUseHostAdapter.cropRawPatch`. */ export type CropRawPatchFn = ( @@ -165,7 +165,10 @@ export async function validateClickTarget( } catch (err) { // Skip validation on technical errors, execute action anyway. // Battle-tested: validation failure must never block the click. - logger.debug('[pixelCompare] validation error, skipping', err) + logger.debug( + '[pixelCompare] validation error, skipping', + toLoggerDetail(err), + ) return { valid: true, skipped: true } } } diff --git a/packages/@ant/computer-use-mcp/src/toolCalls.ts b/packages/@ant/computer-use-mcp/src/toolCalls.ts index 46f209cc24..404c38cd2c 100644 --- a/packages/@ant/computer-use-mcp/src/toolCalls.ts +++ b/packages/@ant/computer-use-mcp/src/toolCalls.ts @@ -91,6 +91,7 @@ import type { ResolvedAppRequest, TeachStepRequest, } from './types.js' +import { toLoggerDetail } from './types.js' /** * Finder is never hidden by the hide loop (hiding Finder kills the Desktop), @@ -4446,7 +4447,10 @@ export async function handleToolCall( // For ungated tools, the executor may have been mid-call; that's fine — // the result is still a tool error, never an implicit success. const msg = err instanceof Error ? err.message : String(err) - logger.error(`[${serverName}] tool=${name} threw: ${msg}`, err) + logger.error( + `[${serverName}] tool=${name} threw: ${msg}`, + toLoggerDetail(err), + ) return errorResult(`Tool "${name}" failed: ${msg}`, 'executor_threw') } } diff --git a/packages/@ant/computer-use-mcp/src/types.ts b/packages/@ant/computer-use-mcp/src/types.ts index 03725c9f6a..614980aaed 100644 --- a/packages/@ant/computer-use-mcp/src/types.ts +++ b/packages/@ant/computer-use-mcp/src/types.ts @@ -8,13 +8,24 @@ import type { * cross-respawn `scaleCoord` survival. */ export type ScreenshotDims = Omit -/** Shape mirrors claude-for-chrome-mcp/src/types.ts:1-7 */ +/** + * Logger 第二参数的可选类型(与 claude-for-chrome-mcp 对齐)。 + * 实践中多为 catch 到的 Error。 + */ +export type LoggerDetail = Error | NodeJS.ErrnoException + +/** 将 unknown 收窄为 LoggerDetail,供 catch 块传给 logger 使用。 */ +export function toLoggerDetail(detail: unknown): LoggerDetail | undefined { + return detail instanceof Error ? detail : undefined +} + +/** 宿主注入的日志接口(与 claude-for-chrome-mcp/src/types.ts 对齐)。 */ export interface Logger { - info: (message: string, ...args: unknown[]) => void - error: (message: string, ...args: unknown[]) => void - warn: (message: string, ...args: unknown[]) => void - debug: (message: string, ...args: unknown[]) => void - silly: (message: string, ...args: unknown[]) => void + info: (message: string, detail?: LoggerDetail) => void // 信息 + error: (message: string, detail?: LoggerDetail) => void // 错误 + warn: (message: string, detail?: LoggerDetail) => void // 警告 + debug: (message: string, detail?: LoggerDetail) => void // 调试 + silly: (message: string, detail?: LoggerDetail) => void // 最细粒度调试 } /** diff --git a/packages/@ant/ink/src/core/dom.ts b/packages/@ant/ink/src/core/dom.ts index 993dadd3ad..3699ce6224 100644 --- a/packages/@ant/ink/src/core/dom.ts +++ b/packages/@ant/ink/src/core/dom.ts @@ -1,3 +1,4 @@ +import type { EventHandlerProps } from './events/event-handlers.js' import type { FocusManager } from './focus.js' import { createLayoutNode } from './layout/engine.js' import type { LayoutNode } from './layout/node.js' @@ -45,10 +46,9 @@ export type DOMElement = { dirty: boolean // Set by the reconciler's hideInstance/unhideInstance; survives style updates. isHidden?: boolean - // Event handlers set by the reconciler for the capture/bubble dispatcher. - // Stored separately from attributes so handler identity changes don't - // mark dirty and defeat the blit optimization. - _eventHandlers?: Record + // 协调器写入的事件处理器(捕获/冒泡分发用)。 + // 与 attributes 分离,避免 handler 引用变化触发 dirty 破坏 blit 优化。 + _eventHandlers?: Partial // 见 event-handlers.ts EventHandlerProps // Scroll state for overflow: 'scroll' boxes. scrollTop is the number of // rows the content is scrolled down by. scrollHeight/scrollViewportHeight diff --git a/packages/@ant/ink/src/core/events/terminal-event.ts b/packages/@ant/ink/src/core/events/terminal-event.ts index 9a86bf8b29..99b49d4c91 100644 --- a/packages/@ant/ink/src/core/events/terminal-event.ts +++ b/packages/@ant/ink/src/core/events/terminal-event.ts @@ -101,7 +101,10 @@ export class TerminalEvent extends Event { _prepareForTarget(_target: EventTarget): void {} } +import type { EventHandlerProps } from './event-handlers.js' + +/** 终端事件系统的目标节点(DOM 树节点或根节点)。 */ export type EventTarget = { - parentNode: EventTarget | undefined - _eventHandlers?: Record + parentNode: EventTarget | undefined // 父节点,根节点为 undefined + _eventHandlers?: Partial // 事件处理器,与 dom.ts DOMElement 同构 } diff --git a/packages/@ant/ink/src/core/reconciler.ts b/packages/@ant/ink/src/core/reconciler.ts index 18164bb203..d3e2174ad4 100644 --- a/packages/@ant/ink/src/core/reconciler.ts +++ b/packages/@ant/ink/src/core/reconciler.ts @@ -20,7 +20,10 @@ import { type TextNode, } from './dom.js' import { Dispatcher } from './events/dispatcher.js' -import { EVENT_HANDLER_PROPS } from './events/event-handlers.js' +import { + EVENT_HANDLER_PROPS, + type EventHandlerProps, +} from './events/event-handlers.js' import { getFocusManager, getRootNode } from './focus.js' import { LayoutDisplay } from './layout/node.js' import applyStyles, { type Styles, type TextStyles } from './styles.js' @@ -111,7 +114,11 @@ type HostContext = { isInsideText: boolean } -function setEventHandler(node: DOMElement, key: string, value: unknown): void { +function setEventHandler( + node: DOMElement, + key: K, + value: EventHandlerProps[K], +): void { if (!node._eventHandlers) { node._eventHandlers = {} } @@ -135,7 +142,11 @@ function applyProp(node: DOMElement, key: string, value: unknown): void { } if (EVENT_HANDLER_PROPS.has(key)) { - setEventHandler(node, key, value) + setEventHandler( + node, + key as keyof EventHandlerProps, + value as EventHandlerProps[keyof EventHandlerProps], + ) return } @@ -441,7 +452,11 @@ const reconciler = createReconciler< } if (EVENT_HANDLER_PROPS.has(key)) { - setEventHandler(node, key, value) + setEventHandler( + node, + key as keyof EventHandlerProps, + value as EventHandlerProps[keyof EventHandlerProps], + ) continue } diff --git a/src/utils/auth.ts b/src/utils/auth.ts index a7b744373b..c640ed7a90 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1897,12 +1897,12 @@ export function getAccountInformation() { accountInfo.apiKeySource = apiKeySource } - // We don't know the organization if we're relying on an external API key or auth token + // 如果我们依赖外部 API 密钥或认证令牌,则不知道组织 if ( authTokenSource === 'claude.ai' || apiKeySource === '/login managed key' ) { - // Get organization name from OAuth account info + // 从 OAuth 账户信息获取组织名称 const orgName = getOauthAccountInfo()?.organizationName if (orgName) { accountInfo.organization = orgName diff --git a/src/utils/claudeInChrome/mcpServer.ts b/src/utils/claudeInChrome/mcpServer.ts index 02f0d629cf..58907afd87 100644 --- a/src/utils/claudeInChrome/mcpServer.ts +++ b/src/utils/claudeInChrome/mcpServer.ts @@ -2,6 +2,7 @@ import { type ClaudeForChromeContext, createClaudeForChromeMcpServer, type Logger, + type LoggerDetail, type PermissionMode, } from '@ant/claude-for-chrome-mcp' import { initializeAnalyticsSink } from '../../services/analytics/sink.js' @@ -276,19 +277,19 @@ export async function runClaudeInChromeMcpServer(): Promise { } class DebugLogger implements Logger { - silly(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'debug' }) + silly(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'debug' }) } - debug(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'debug' }) + debug(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'debug' }) } - info(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'info' }) + info(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'info' }) } - warn(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'warn' }) + warn(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'warn' }) } - error(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'error' }) + error(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'error' }) } } diff --git a/src/utils/computerUse/hostAdapter.ts b/src/utils/computerUse/hostAdapter.ts index e01ab30d78..c07c30d61e 100644 --- a/src/utils/computerUse/hostAdapter.ts +++ b/src/utils/computerUse/hostAdapter.ts @@ -1,6 +1,7 @@ import type { ComputerUseHostAdapter, Logger, + LoggerDetail, } from '@ant/computer-use-mcp/types' import { format } from 'util' import { logForDebugging } from '../debug.js' @@ -10,20 +11,20 @@ import { getChicagoEnabled, getChicagoSubGates } from './gates.js' import { requireComputerUseSwift } from './swiftLoader.js' class DebugLogger implements Logger { - silly(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'debug' }) + silly(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'debug' }) } - debug(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'debug' }) + debug(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'debug' }) } - info(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'info' }) + info(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'info' }) } - warn(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'warn' }) + warn(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'warn' }) } - error(message: string, ...args: unknown[]): void { - logForDebugging(format(message, ...args), { level: 'error' }) + error(message: string, detail?: LoggerDetail): void { + logForDebugging(format(message, detail ?? ''), { level: 'error' }) } }