mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
Merge branch 'main' into refactor/ink-v2
This commit is contained in:
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 883 KiB After Width: | Height: | Size: 907 KiB |
@@ -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<string>()
|
||||
return allApps.filter(app => {
|
||||
if (seen.has(app.bundleId)) return false
|
||||
seen.add(app.bundleId)
|
||||
return true
|
||||
})
|
||||
} catch {
|
||||
return []
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user