fix: remove mid-plan as-any casts

This commit is contained in:
James Feng 2026-06-07 15:33:40 +08:00
parent 33a6217702
commit c130eaad4b
11 changed files with 109 additions and 34 deletions

View File

@ -18,7 +18,7 @@ let pump: ReturnType<typeof setInterval> | undefined
let pending = 0
function drainTick(cu: ReturnType<typeof requireComputerUseSwift>): void {
;(cu as any)?._drainMainRunLoop?.()
cu._drainMainRunLoop?.()
}
function retain(): void {

View File

@ -26,7 +26,7 @@ export function registerEscHotkey(onEscape: () => void): boolean {
if (process.platform !== 'darwin') return false
if (registered) return true
const cu = requireComputerUseSwift()
if (!(cu as any).hotkey?.registerEscape(onEscape)) {
if (!cu.hotkey?.registerEscape(onEscape)) {
// CGEvent.tapCreate failed — typically missing Accessibility permission.
// CU still works, just without ESC abort. Mirrors Cowork's escAbort.ts:81.
logForDebugging('[cu-esc] registerEscape returned false', { level: 'warn' })
@ -41,7 +41,7 @@ export function registerEscHotkey(onEscape: () => void): boolean {
export function unregisterEscHotkey(): void {
if (!registered) return
try {
(requireComputerUseSwift() as any).hotkey?.unregister()
requireComputerUseSwift().hotkey?.unregister()
} finally {
releasePump()
registered = false
@ -51,5 +51,5 @@ export function unregisterEscHotkey(): void {
export function notifyExpectedEscape(): void {
if (!registered) return
(requireComputerUseSwift() as any).hotkey?.notifyExpectedEscape()
requireComputerUseSwift().hotkey?.notifyExpectedEscape()
}

View File

@ -40,6 +40,7 @@ import { CLI_CU_CAPABILITIES, CLI_HOST_BUNDLE_ID } from './common.js'
import { validateHwnd } from './win32/shared.js'
import { loadPlatform } from './platforms/index.js'
import type { Platform } from './platforms/index.js'
import type { WindowAction } from './platforms/types.js'
// ---------------------------------------------------------------------------
// Helpers for HWND-bound mode
@ -62,6 +63,19 @@ function isBound(): boolean {
return getBoundHwndStr() !== null
}
function isWindowAction(action: string): action is WindowAction {
return (
action === 'minimize' ||
action === 'maximize' ||
action === 'restore' ||
action === 'close' ||
action === 'focus' ||
action === 'move_offscreen' ||
action === 'move_resize' ||
action === 'get_rect'
)
}
/**
* Get the non-client area offset (title bar height, left border width).
* Returns {dx, dy} to subtract from window coords client coords.
@ -207,7 +221,7 @@ export function createCrossPlatformExecutor(opts: {
`[computer-use] cross-platform executor for ${process.platform}`,
)
return {
const executor: ComputerExecutor = {
capabilities: {
...CLI_CU_CAPABILITIES,
hostBundleId: CLI_HOST_BUNDLE_ID,
@ -491,12 +505,12 @@ $i = New-Object MUp+INPUT; $i.type=0; $i.mi.dwFlags=0x0004; [MUp]::SendInput(1,
await sleep(16)
}
// mouseDown
await (this as any).mouseDown()
await executor.mouseDown()
await sleep(50)
await platform.input.moveMouse(to.x, to.y)
await sleep(16)
// mouseUp
await (this as any).mouseUp()
await executor.mouseUp()
},
async scroll(x: number, y: number, dx: number, dy: number): Promise<void> {
@ -553,7 +567,8 @@ $i = New-Object MUp+INPUT; $i.type=0; $i.mi.dwFlags=0x0004; [MUp]::SendInput(1,
// ── Window management (Windows only) ──────────────────────────────
async manageWindow(action: string, opts?): Promise<boolean> {
if (!platform.windowManagement) return false
const result = platform.windowManagement.manageWindow(action as any, opts)
if (!isWindowAction(action)) return false
const result = platform.windowManagement.manageWindow(action, opts)
// Invalidate NC offset cache on window state change
_ncOffset = null
_ncOffsetHwnd = null
@ -1138,6 +1153,7 @@ $i = New-Object MUp+INPUT; $i.type=0; $i.mi.dwFlags=0x0004; [MUp]::SendInput(1,
}
},
}
return executor
}
/**

View File

@ -36,7 +36,11 @@ class DebugLogger implements Logger {
function checkAccessibilityJXA(): boolean {
try {
const result = Bun.spawnSync({
cmd: ['osascript', '-e', 'tell application "System Events" to get name of every process whose background only is false'],
cmd: [
'osascript',
'-e',
'tell application "System Events" to get name of every process whose background only is false',
],
stdout: 'pipe',
stderr: 'pipe',
})
@ -80,7 +84,7 @@ export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
ensureOsPermissions: async () => {
if (process.platform !== 'darwin') return { granted: true }
const cu = requireComputerUseSwift()
const tcc = (cu as any).tcc
const tcc = cu.tcc
// Native Swift .node module provides tcc.checkAccessibility/checkScreenRecording.
// When absent (decompiled/reverse-engineered build), fall back to JXA probes.
if (tcc) {

View File

@ -1,19 +1,32 @@
import type { ComputerUseAPI } from '@ant/computer-use-swift'
let cached: ComputerUseAPI | undefined
export type ComputerUseSwiftAPI = ComputerUseAPI & {
hotkey?: {
registerEscape(onEscape: () => void): boolean
unregister(): void
notifyExpectedEscape(): void
}
tcc?: {
checkAccessibility(): boolean
checkScreenRecording(): boolean
}
_drainMainRunLoop?: () => void
}
let cached: ComputerUseSwiftAPI | undefined
/**
* macOS-only loader for @ant/computer-use-swift.
* Non-darwin platforms should use src/utils/computerUse/platforms/ instead.
*/
export function requireComputerUseSwift(): ComputerUseAPI {
export function requireComputerUseSwift(): ComputerUseSwiftAPI {
if (cached) return cached
// eslint-disable-next-line @typescript-eslint/no-require-imports
const mod = require('@ant/computer-use-swift')
if (mod.ComputerUseAPI && typeof mod.ComputerUseAPI === 'function') {
cached = new mod.ComputerUseAPI() as ComputerUseAPI
cached = new mod.ComputerUseAPI() as ComputerUseSwiftAPI
} else {
cached = mod as ComputerUseAPI
cached = mod as ComputerUseSwiftAPI
}
return cached
}

View File

@ -23,6 +23,10 @@ interface BridgeResponse {
error?: string
}
type FlushableWritable = Writable & {
flush?: () => void
}
let bridgeProc: ReturnType<typeof Bun.spawn> | null = null
let requestId = 0
const pendingRequests = new Map<
@ -131,10 +135,10 @@ export async function call<T = unknown>(
try {
const stdin = bridgeProc!.stdin
if (stdin) {
const writable = stdin as unknown as Writable
const writable = stdin as unknown as FlushableWritable
writable.write(JSON.stringify(req) + '\n')
if (typeof (writable as any).flush === 'function') {
(writable as any).flush()
if (typeof writable.flush === 'function') {
writable.flush()
}
}
} catch (err) {

View File

@ -8,11 +8,17 @@
*/
import * as fs from 'fs/promises'
import type { Dirent } from 'fs'
import * as path from 'path'
import { logForDebugging } from '../debug.js'
import type { EnvironmentKind } from '../teleport/environments.js'
import type { TurnStartTime } from './types.js'
type RecursiveDirent = Dirent & {
parentPath?: string
path?: string
}
/** Shared debug logger for file persistence modules */
export function logDebug(message: string): void {
logForDebugging(`[file-persistence] ${message}`)
@ -63,13 +69,14 @@ export async function findModifiedFiles(
turnStartTime: TurnStartTime,
outputsDir: string,
): Promise<string[]> {
const turnStartMs = turnStartTime.turnStartTime
// Use recursive flag to get all entries in one call
let entries: Awaited<ReturnType<typeof fs.readdir>> | any[]
let entries: RecursiveDirent[]
try {
entries = await fs.readdir(outputsDir, {
withFileTypes: true,
recursive: true,
}) as any[]
})
} catch {
// Directory doesn't exist or is not accessible
return []
@ -113,7 +120,7 @@ export async function findModifiedFiles(
// Filter to files modified since turn start
const modifiedFiles: string[] = []
for (const result of statResults) {
if (result && result.mtimeMs >= (turnStartTime as any as number)) {
if (result && result.mtimeMs >= turnStartMs) {
modifiedFiles.push(result.filePath)
}
}

View File

@ -1,5 +1,6 @@
import { feature } from 'bun:bundle'
import type { BetaMessageStreamParams } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import type { Dirent } from 'fs'
import { readdir, readFile, stat } from 'fs/promises'
import memoize from 'lodash-es/memoize.js'
import { join } from 'path'
@ -228,9 +229,9 @@ export async function getErrorLogByIndex(
* @private
*/
async function loadLogList(path: string): Promise<LogOption[]> {
let files: Awaited<ReturnType<typeof readdir>>
let files: Dirent<string>[]
try {
files = (await readdir(path, { withFileTypes: true })) as any
files = await readdir(path, { withFileTypes: true })
} catch {
logError(new Error(`No logs found at ${path}`))
return []

View File

@ -25,6 +25,14 @@ const measures = new Map<
{ name: string; startTime: number; duration: number }
>()
declare global {
var __performanceShimInstalled: boolean | undefined
}
type PerformanceWithUndiciResourceTiming = Performance & {
markResourceTiming(...args: unknown[]): void
}
function now(): number {
return original.now()
}
@ -124,7 +132,10 @@ function clearMeasures(name?: string): void {
// Plain object shim — must NOT inherit from Performance.prototype because
// native getters (onresourcetimingbufferfull, timeOrigin, toJSON) check
// that `this` is an actual JSC Performance instance and throw otherwise.
const shim = {
const originalWithResourceTiming =
original as PerformanceWithUndiciResourceTiming
const shim: PerformanceWithUndiciResourceTiming = {
now,
mark,
measure: measure as typeof performance.measure,
@ -137,21 +148,21 @@ const shim = {
(() => {}) as typeof performance.setResourceTimingBufferSize,
// Node.js v22 undici internal calls this after every fetch — must exist to
// avoid TypeError: markResourceTiming is not a function
markResourceTiming: (() => {}) as any,
markResourceTiming: () => {},
// Delegate read-only properties to the original
get timeOrigin() {
return original.timeOrigin
},
get onresourcetimingbufferfull() {
return (original as any).onresourcetimingbufferfull
return originalWithResourceTiming.onresourcetimingbufferfull
},
set onresourcetimingbufferfull(_v: any) {
set onresourcetimingbufferfull(_v: Performance['onresourcetimingbufferfull']) {
// no-op — prevent accumulation
},
toJSON() {
return original.toJSON()
},
} as unknown as typeof performance
} as unknown as PerformanceWithUndiciResourceTiming
/**
* Install the shim onto globalThis.performance. Safe to call multiple times.
@ -159,8 +170,8 @@ const shim = {
* native Performance reference.
*/
export function installPerformanceShim(): void {
if ((globalThis as any).__performanceShimInstalled) return
;(globalThis as any).__performanceShimInstalled = true
if (globalThis.__performanceShimInstalled) return
globalThis.__performanceShimInstalled = true
globalThis.performance = shim
}

View File

@ -33,10 +33,10 @@ export function validateInputForSettingsFileEdit(
const updatedContent = getUpdatedContent()
const afterValidation = validateSettingsFileContent(updatedContent)
if (!afterValidation.isValid) {
if (afterValidation.isValid === false) {
return {
result: false,
message: `Claude Code settings.json validation failed after edit:\n${(afterValidation as any).error}\n\nFull schema:\n${(afterValidation as any).fullSchema}\nIMPORTANT: Do not update the env unless explicitly instructed to do so.`,
message: `Claude Code settings.json validation failed after edit:\n${afterValidation.error}\n\nFull schema:\n${afterValidation.fullSchema}\nIMPORTANT: Do not update the env unless explicitly instructed to do so.`,
errorCode: 10,
}
}

View File

@ -16,6 +16,19 @@ const RENDERED_AS_SENTINEL = new Set([
const searchTextCache = new WeakMap<RenderableMessage, string>()
function isTextContentBlock(
block: unknown,
): block is { type: 'text'; text: string } {
return (
typeof block === 'object' &&
block !== null &&
'type' in block &&
block.type === 'text' &&
'text' in block &&
typeof block.text === 'string'
)
}
/** Flatten a RenderableMessage to lowercased searchable text. WeakMap-
* cached messages are append-only and immutable so a hit is always
* valid. Lowercased at cache time: the only caller immediately
@ -38,7 +51,7 @@ function computeSearchText(msg: RenderableMessage): string {
raw = RENDERED_AS_SENTINEL.has(c) ? '' : c
} else {
const parts: string[] = []
for (const b of (c ?? [])) {
for (const b of c ?? []) {
if (b.type === 'text') {
if (!RENDERED_AS_SENTINEL.has(b.text)) parts.push(b.text)
} else if (b.type === 'tool_result') {
@ -84,7 +97,9 @@ function computeSearchText(msg: RenderableMessage): string {
// (AttachmentMessage.tsx <Ansi>{m.content}</Ansi>). Visible but
// unsearchable without this — [ dump finds it, / doesn't.
if (msg.attachment!.type === 'relevant_memories') {
raw = (msg.attachment!.memories ?? []).map((m: { content: string }) => m.content).join('\n')
raw = (msg.attachment!.memories ?? [])
.map((m: { content: string }) => m.content)
.join('\n')
} else if (
// Mid-turn prompts — queued while an agent is running. Render via
// UserTextMessage (AttachmentMessage.tsx:~348). stickyPromptText
@ -97,7 +112,11 @@ function computeSearchText(msg: RenderableMessage): string {
raw =
typeof p === 'string'
? p
: (p as any[]).flatMap(b => (b.type === 'text' ? [b.text] : [])).join('\n')
: Array.isArray(p)
? p
.flatMap(b => (isTextContentBlock(b) ? [b.text] : []))
.join('\n')
: ''
}
break
}