mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
Feat/integrate lint preview (#285)
* feat: 适配 zed acp 协议 * docs: 完善 acp 文档 * feat: integrate feature branches + daemon/job 命令层级化 + 跨平台后台引擎 Cherry-picked from origin/lint/preview (637c908), excluding lint-only changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct detectMimeFromBase64 to decode raw bytes from base64 Cherry-picked from origin/lint/preview (ee36954). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: daemon 子进程 spawn 跨平台修复 + CliLaunchSpec 集中化重构 Cherry-picked from origin/lint/preview (c5f52cd), excluding lint-only formatting changes. - 新建 src/utils/cliLaunch.ts: 集中化 CLI 子进程启动层 - 修复 --daemon-worker=kind 等号格式解析 - 修复 daemon/bg fast path 缺少 setShellIfWindows() - 修复 checkPathExists 用 existsSync 替代 execSync('dir') - 7 个 spawn 站点迁移到 CliLaunchSpec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
141
src/hooks/usePipeMuteSync.ts
Normal file
141
src/hooks/usePipeMuteSync.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* usePipeMuteSync — Sync master's UI selection state to slave relay mute flags.
|
||||
*
|
||||
* Watches routeMode, selectedPipes, slave client registry, and send-override
|
||||
* changes. When a slave is deselected or routeMode switches to 'local', sends
|
||||
* relay_mute. When re-selected, sends relay_unmute. Also maintains the
|
||||
* master-side muted set for in-flight message filtering.
|
||||
*
|
||||
* Feature-gated by UDS_INBOX (conditional import in REPL.tsx).
|
||||
*/
|
||||
import { useEffect, useRef, useSyncExternalStore } from 'react'
|
||||
import { useAppState } from '../state/AppState.js'
|
||||
import { getPipeIpc } from '../utils/pipeTransport.js'
|
||||
import {
|
||||
setMasterMutedPipes,
|
||||
clearMasterMutedPipes,
|
||||
hasSendOverride,
|
||||
clearSendOverrides,
|
||||
subscribeSendOverride,
|
||||
getSendOverrideVersion,
|
||||
} from '../utils/pipeMuteState.js'
|
||||
import {
|
||||
getAllSlaveClients,
|
||||
subscribeToSlaveClientRegistry,
|
||||
getSlaveClientRegistryVersion,
|
||||
} from './useMasterMonitor.js'
|
||||
|
||||
type UsePipeMuteSyncDeps = {
|
||||
setToolUseConfirmQueue: (action: React.SetStateAction<Record<string, unknown>[]>) => void
|
||||
}
|
||||
|
||||
export function usePipeMuteSync({
|
||||
setToolUseConfirmQueue,
|
||||
}: UsePipeMuteSyncDeps): void {
|
||||
// Subscribe to individual scalars to avoid object-selector re-render churn
|
||||
// (AppState.tsx warns against object-returning selectors)
|
||||
const routeMode = useAppState(
|
||||
s => (getPipeIpc(s).routeMode as 'selected' | 'local') ?? 'selected',
|
||||
)
|
||||
const selectedPipes: string[] = useAppState(
|
||||
s => (getPipeIpc(s).selectedPipes as string[]) ?? [],
|
||||
)
|
||||
|
||||
// Subscribe to slave client registry changes
|
||||
const registryVersion = useSyncExternalStore(
|
||||
subscribeToSlaveClientRegistry,
|
||||
getSlaveClientRegistryVersion,
|
||||
getSlaveClientRegistryVersion,
|
||||
)
|
||||
|
||||
// Subscribe to send-override changes so mute recalculates after /send completes
|
||||
const sendOverrideVersion = useSyncExternalStore(
|
||||
subscribeSendOverride,
|
||||
getSendOverrideVersion,
|
||||
getSendOverrideVersion,
|
||||
)
|
||||
|
||||
const prevMutedRef = useRef<Set<string>>(new Set())
|
||||
|
||||
useEffect(() => {
|
||||
const slaves = getAllSlaveClients()
|
||||
|
||||
// Compute which slaves should be muted now
|
||||
const nextMuted = new Set<string>()
|
||||
if (routeMode === 'local') {
|
||||
// All connected slaves muted
|
||||
for (const name of slaves.keys()) {
|
||||
if (!hasSendOverride(name)) {
|
||||
nextMuted.add(name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// routeMode === 'selected': mute slaves NOT in selectedPipes
|
||||
const selectedSet = new Set(selectedPipes)
|
||||
for (const name of slaves.keys()) {
|
||||
if (!selectedSet.has(name) && !hasSendOverride(name)) {
|
||||
nextMuted.add(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Update master-side muted set FIRST (before sending control packets)
|
||||
setMasterMutedPipes(nextMuted)
|
||||
|
||||
const prevMuted = prevMutedRef.current
|
||||
|
||||
// Step 2: For newly muted slaves — abort pending permissions, then send relay_mute
|
||||
for (const name of nextMuted) {
|
||||
if (!prevMuted.has(name)) {
|
||||
// Abort pending permission prompts for this slave
|
||||
setToolUseConfirmQueue((queue: Record<string, unknown>[]) => {
|
||||
const toAbort = queue.filter(
|
||||
(item: Record<string, unknown>) => item.pipeName === name,
|
||||
)
|
||||
for (const item of toAbort) {
|
||||
try {
|
||||
;(item.onAbort as (() => void) | undefined)?.()
|
||||
} catch {
|
||||
// onAbort may throw if client disconnected — safe to ignore
|
||||
}
|
||||
}
|
||||
return queue.filter((item: Record<string, unknown>) => item.pipeName !== name)
|
||||
})
|
||||
|
||||
// Send relay_mute to slave
|
||||
const client = slaves.get(name)
|
||||
if (client?.connected) {
|
||||
try {
|
||||
client.send({ type: 'relay_mute' })
|
||||
} catch {
|
||||
// send may fail if socket is closing — non-fatal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: For newly unmuted slaves — send relay_unmute
|
||||
for (const name of prevMuted) {
|
||||
if (!nextMuted.has(name)) {
|
||||
const client = slaves.get(name)
|
||||
if (client?.connected) {
|
||||
try {
|
||||
client.send({ type: 'relay_unmute' })
|
||||
} catch {
|
||||
// non-fatal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevMutedRef.current = nextMuted
|
||||
}, [routeMode, selectedPipes, registryVersion, sendOverrideVersion, setToolUseConfirmQueue])
|
||||
|
||||
// Cleanup on unmount: clear all master-side mute state
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearMasterMutedPipes()
|
||||
clearSendOverrides()
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
Reference in New Issue
Block a user