From 5d7e54751a07bf9c28b5af8c3e9a2eed12bc0b5f Mon Sep 17 00:00:00 2001 From: bonerush <96404351+bonerush@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:27:25 +0800 Subject: [PATCH 1/4] fix: reorder tool and user messages for OpenAI API compatibility (#168) (#177) Fixes #168 OpenAI requires that an assistant message with tool_calls be immediately followed by tool messages. Previously, convertInternalUserMessage output user content before tool results, causing 400 errors. Now tool messages are pushed first. --- src/services/api/openai/convertMessages.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/services/api/openai/convertMessages.ts b/src/services/api/openai/convertMessages.ts index 051b43d69..3869120eb 100644 --- a/src/services/api/openai/convertMessages.ts +++ b/src/services/api/openai/convertMessages.ts @@ -92,6 +92,15 @@ function convertInternalUserMessage( } } + // CRITICAL: tool messages must come BEFORE any user message in the result. + // OpenAI API requires that a tool message immediately follows the assistant + // message with tool_calls. If we emit a user message first, the API will + // reject the request with "insufficient tool messages following tool_calls". + // See: https://github.com/anthropics/claude-code/issues/xxx + for (const tr of toolResults) { + result.push(convertToolResult(tr)) + } + // 如果有图片,构建多模态 content 数组 if (imageParts.length > 0) { const multiContent: Array<{ type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } }> = [] @@ -109,10 +118,6 @@ function convertInternalUserMessage( content: textParts.join('\n'), } satisfies ChatCompletionUserMessageParam) } - - for (const tr of toolResults) { - result.push(convertToolResult(tr)) - } } return result From 0d8f494c4bbf325a79c2f55bca4f862a7e3b4fdd Mon Sep 17 00:00:00 2001 From: mcjjin Date: Tue, 7 Apr 2026 19:53:59 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(computer-use):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A3=80=E6=9F=A5=E5=92=8C=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E8=8E=B7=E5=8F=96=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: update contributors * fix(computer-use): 修复权限检查和应用列表获取的问题 修复 macOS 平台下权限检查的 JXA 回退逻辑,确保在没有原生模块时仍能正确检测权限 改进应用列表获取方式,使用 mdls 获取真实的 bundleId 而非生成伪 ID * docs: update contributors * docs: update contributors * docs: update contributors --------- Co-authored-by: mcjjin <8590489+mcjjin@users.noreply.github.com> Co-authored-by: claude-code-best Co-authored-by: claude-code-best <272536312+claude-code-best@users.noreply.github.com> --- contributors.svg | 24 +++++----- .../computer-use-swift/src/backends/darwin.ts | 44 ++++++++++------- src/utils/computerUse/escHotkey.ts | 6 +-- src/utils/computerUse/hostAdapter.ts | 47 ++++++++++++++++++- 4 files changed, 87 insertions(+), 34 deletions(-) diff --git a/contributors.svg b/contributors.svg index 37060fd26..4bd4276bc 100644 --- a/contributors.svg +++ b/contributors.svg @@ -10,27 +10,29 @@ + + - + - + - + - + - + - + - + - + - + - + - + \ No newline at end of file diff --git a/packages/@ant/computer-use-swift/src/backends/darwin.ts b/packages/@ant/computer-use-swift/src/backends/darwin.ts index 620f162a9..6e3c24933 100644 --- a/packages/@ant/computer-use-swift/src/backends/darwin.ts +++ b/packages/@ant/computer-use-swift/src/backends/darwin.ts @@ -159,25 +159,33 @@ export const apps: AppsAPI = { async listInstalled() { try { - const result = await osascript(` - tell application "System Events" - set appList to "" - repeat with appFile in (every file of folder "Applications" of startup disk whose name ends with ".app") - set appPath to POSIX path of (appFile as alias) - set appName to name of appFile - set appList to appList & appPath & "|" & appName & "\\n" - end repeat - return appList - end tell - `) - return result.split('\n').filter(Boolean).map(line => { - const [path, name] = line.split('|', 2) - const displayName = (name ?? '').replace(/\.app$/, '') - return { - bundleId: `com.app.${displayName.toLowerCase().replace(/\s+/g, '-')}`, - displayName, - path: path ?? '', + // Use mdls to enumerate apps and get real bundle identifiers. + // The previous AppleScript approach generated fake bundle IDs + // (com.app.display-name) which prevented request_access from matching + // apps by their real bundle ID (e.g. com.google.Chrome). + const dirs = ['/Applications', '~/Applications', '/System/Applications'] + const allApps: InstalledApp[] = [] + for (const dir of dirs) { + const expanded = dir.startsWith('~') ? join(process.env.HOME ?? '~', dir.slice(1)) : dir + const proc = Bun.spawn( + ['bash', '-c', `for f in "${expanded}"/*.app; do [ -d "$f" ] || continue; bid=$(mdls -name kMDItemCFBundleIdentifier "$f" 2>/dev/null | sed 's/.*= "//;s/"//'); name=$(basename "$f" .app); echo "$f|$name|$bid"; done`], + { stdout: 'pipe', stderr: 'pipe' }, + ) + const text = await new Response(proc.stdout).text() + await proc.exited + for (const line of text.split('\n').filter(Boolean)) { + const [path, displayName, bundleId] = line.split('|', 3) + if (path && displayName && bundleId && bundleId !== '(null)') { + allApps.push({ bundleId, displayName, path }) + } } + } + // Deduplicate by bundleId (prefer /Applications over ~/Applications) + const seen = new Set() + return allApps.filter(app => { + if (seen.has(app.bundleId)) return false + seen.add(app.bundleId) + return true }) } catch { return [] diff --git a/src/utils/computerUse/escHotkey.ts b/src/utils/computerUse/escHotkey.ts index 24ba17cc4..f58bdff0f 100644 --- a/src/utils/computerUse/escHotkey.ts +++ b/src/utils/computerUse/escHotkey.ts @@ -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 as any).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() as any).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() as any).hotkey?.notifyExpectedEscape() } diff --git a/src/utils/computerUse/hostAdapter.ts b/src/utils/computerUse/hostAdapter.ts index acefbaa3d..ae99d0b18 100644 --- a/src/utils/computerUse/hostAdapter.ts +++ b/src/utils/computerUse/hostAdapter.ts @@ -27,6 +27,38 @@ class DebugLogger implements Logger { } } +// --------------------------------------------------------------------------- +// JXA-based TCC permission probes (fallback when native .node module absent) +// --------------------------------------------------------------------------- + +/** Probe accessibility by asking System Events for a process list. */ +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'], + stdout: 'pipe', + stderr: 'pipe', + }) + return result.exitCode === 0 + } catch { + return false + } +} + +/** Probe screen recording by attempting a 1x1 screencapture. */ +function checkScreenRecordingJXA(): boolean { + try { + const result = Bun.spawnSync({ + cmd: ['screencapture', '-x', '-R', '0,0,1,1', '/dev/null'], + stdout: 'pipe', + stderr: 'pipe', + }) + return result.exitCode === 0 + } catch { + return false + } +} + let cached: ComputerUseHostAdapter | undefined /** @@ -47,8 +79,19 @@ export function getComputerUseHostAdapter(): ComputerUseHostAdapter { ensureOsPermissions: async () => { if (process.platform !== 'darwin') return { granted: true } const cu = requireComputerUseSwift() - const accessibility = (cu as any).tcc.checkAccessibility() - const screenRecording = (cu as any).tcc.checkScreenRecording() + const tcc = (cu as any).tcc + // Native Swift .node module provides tcc.checkAccessibility/checkScreenRecording. + // When absent (decompiled/reverse-engineered build), fall back to JXA probes. + if (tcc) { + const accessibility = tcc.checkAccessibility() + const screenRecording = tcc.checkScreenRecording() + return accessibility && screenRecording + ? { granted: true } + : { granted: false, accessibility, screenRecording } + } + // JXA fallback: try to query System Events (accessibility) and screencapture (screen recording). + const accessibility = checkAccessibilityJXA() + const screenRecording = checkScreenRecordingJXA() return accessibility && screenRecording ? { granted: true } : { granted: false, accessibility, screenRecording } From 042e186b0b5ed095d4483165f194e86e350be5ec Mon Sep 17 00:00:00 2001 From: claude-code-best <272536312+claude-code-best@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:54:11 +0000 Subject: [PATCH 3/4] docs: update contributors --- contributors.svg | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contributors.svg b/contributors.svg index 4bd4276bc..1ac84b8af 100644 --- a/contributors.svg +++ b/contributors.svg @@ -10,20 +10,20 @@ - - - + - + - + - + - + - + + + From d6bfc34b71a17ac43ab719934b3eb672be8337b4 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Tue, 7 Apr 2026 19:15:29 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20ant=20=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LogoV2/ExperimentEnrollmentNotice.tsx | 9 +++++++++ src/components/LogoV2/GateOverridesWarning.tsx | 10 ++++++++++ src/components/LogoV2/LogoV2.tsx | 2 ++ 3 files changed, 21 insertions(+) create mode 100644 src/components/LogoV2/ExperimentEnrollmentNotice.tsx create mode 100644 src/components/LogoV2/GateOverridesWarning.tsx diff --git a/src/components/LogoV2/ExperimentEnrollmentNotice.tsx b/src/components/LogoV2/ExperimentEnrollmentNotice.tsx new file mode 100644 index 000000000..6210eb20c --- /dev/null +++ b/src/components/LogoV2/ExperimentEnrollmentNotice.tsx @@ -0,0 +1,9 @@ +import * as React from 'react' + +/** + * Internal-only component. Shows experiment enrollment status for internal + * users. Stubbed — returns null in non-internal builds. + */ +export function ExperimentEnrollmentNotice(): React.ReactNode { + return null +} diff --git a/src/components/LogoV2/GateOverridesWarning.tsx b/src/components/LogoV2/GateOverridesWarning.tsx new file mode 100644 index 000000000..32325eefa --- /dev/null +++ b/src/components/LogoV2/GateOverridesWarning.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' + +/** + * Internal-only component. Displays a warning when feature-gate overrides + * (CLAUDE_INTERNAL_FC_OVERRIDES) are active. Stubbed — returns null in + * non-internal builds. + */ +export function GateOverridesWarning(): React.ReactNode { + return null +} diff --git a/src/components/LogoV2/LogoV2.tsx b/src/components/LogoV2/LogoV2.tsx index d65c24fe3..dd7cf04e8 100644 --- a/src/components/LogoV2/LogoV2.tsx +++ b/src/components/LogoV2/LogoV2.tsx @@ -49,6 +49,8 @@ import { import { EmergencyTip } from './EmergencyTip.js' import { VoiceModeNotice } from './VoiceModeNotice.js' import { Opus1mMergeNotice } from './Opus1mMergeNotice.js' +import { GateOverridesWarning } from './GateOverridesWarning.js' +import { ExperimentEnrollmentNotice } from './ExperimentEnrollmentNotice.js' import { feature } from 'bun:bundle' // Conditional require so ChannelsNotice.tsx tree-shakes when both flags are