mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 06:15:51 +00:00
* 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>
197 lines
6.5 KiB
TypeScript
197 lines
6.5 KiB
TypeScript
/**
|
|
* usePipePermissionForward — Forward slave permission requests to master UI.
|
|
*
|
|
* Subscribes to slave pipe messages via subscribePipeEntries, and:
|
|
* 1. permission_request → enqueue into toolUseConfirmQueue for master approval
|
|
* 2. permission_cancel → remove from queue
|
|
* 3. stream/error/done → display as system messages
|
|
*/
|
|
import { feature } from 'bun:bundle'
|
|
import { useEffect } from 'react'
|
|
import type { Tool, ToolUseContext } from '../Tool.js'
|
|
import type { MessageType } from '../types/message.js'
|
|
|
|
type Deps = {
|
|
store: { getState: () => any }
|
|
tools: Tool<any, any>[]
|
|
setMessages: (action: React.SetStateAction<MessageType[]>) => void
|
|
setToolUseConfirmQueue: (action: React.SetStateAction<any[]>) => void
|
|
getToolUseContext: (...args: any[]) => ToolUseContext
|
|
mainLoopModel: string
|
|
}
|
|
|
|
export function usePipePermissionForward({
|
|
store,
|
|
tools,
|
|
setMessages,
|
|
setToolUseConfirmQueue,
|
|
getToolUseContext,
|
|
mainLoopModel,
|
|
}: Deps): void {
|
|
useEffect(() => {
|
|
if (!feature('UDS_INBOX')) return
|
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
const { subscribePipeEntries, getSlaveClient } =
|
|
require('./useMasterMonitor.js') as typeof import('./useMasterMonitor.js')
|
|
const { getPipeIpc } =
|
|
require('../utils/pipeTransport.js') as typeof import('../utils/pipeTransport.js')
|
|
const { createAssistantMessage, createSystemMessage } =
|
|
require('../utils/messages.js') as typeof import('../utils/messages.js')
|
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
|
|
return subscribePipeEntries(
|
|
(pipeName: string, entry: { type: string; content: string }) => {
|
|
const content = entry.content.trim()
|
|
const pipeIpcState = getPipeIpc(store.getState())
|
|
const peerInfo = (pipeIpcState.discoveredPipes ?? []).find(
|
|
(pipe: { pipeName: string }) => pipe.pipeName === pipeName,
|
|
)
|
|
const isLan = peerInfo?.ip && peerInfo.ip !== pipeIpcState.localIp
|
|
const displayRole = peerInfo
|
|
? isLan
|
|
? `${peerInfo.role} ${peerInfo.hostname}/${peerInfo.ip}`
|
|
: peerInfo.role
|
|
: pipeName
|
|
|
|
if (entry.type === 'permission_request') {
|
|
try {
|
|
const payload = JSON.parse(content)
|
|
const tool = tools.find(
|
|
candidate => candidate.name === payload.toolName,
|
|
)
|
|
const client = getSlaveClient(pipeName)
|
|
if (!client) return
|
|
|
|
if (!tool) {
|
|
client.send({
|
|
type: 'permission_response',
|
|
data: JSON.stringify({
|
|
requestId: payload.requestId,
|
|
behavior: 'deny',
|
|
feedback: `Tool "${payload.toolName}" is not available in main.`,
|
|
}),
|
|
})
|
|
return
|
|
}
|
|
|
|
const assistantMessage = createAssistantMessage({ content: '' })
|
|
const toolUseContext = getToolUseContext(
|
|
[],
|
|
[],
|
|
new AbortController(),
|
|
mainLoopModel,
|
|
)
|
|
setToolUseConfirmQueue((queue: any[]) => [
|
|
...queue,
|
|
{
|
|
assistantMessage,
|
|
tool,
|
|
description: payload.description,
|
|
input: payload.input,
|
|
toolUseContext,
|
|
toolUseID: `pipe:${payload.requestId}`,
|
|
pipeName,
|
|
permissionResult: payload.permissionResult,
|
|
permissionPromptStartTimeMs:
|
|
payload.permissionPromptStartTimeMs,
|
|
workerBadge: {
|
|
name: `${displayRole} / ${pipeName}`,
|
|
color: 'cyan',
|
|
},
|
|
onUserInteraction() {},
|
|
onAbort() {
|
|
client.send({
|
|
type: 'permission_response',
|
|
data: JSON.stringify({
|
|
requestId: payload.requestId,
|
|
behavior: 'deny',
|
|
feedback: 'Permission request was aborted in main.',
|
|
}),
|
|
})
|
|
},
|
|
onAllow(
|
|
updatedInput: any,
|
|
permissionUpdates: any,
|
|
feedback: any,
|
|
contentBlocks: any,
|
|
) {
|
|
client.send({
|
|
type: 'permission_response',
|
|
data: JSON.stringify({
|
|
requestId: payload.requestId,
|
|
behavior: 'allow',
|
|
updatedInput,
|
|
permissionUpdates,
|
|
feedback,
|
|
contentBlocks,
|
|
}),
|
|
})
|
|
},
|
|
onReject(feedback: any, contentBlocks: any) {
|
|
client.send({
|
|
type: 'permission_response',
|
|
data: JSON.stringify({
|
|
requestId: payload.requestId,
|
|
behavior: 'deny',
|
|
feedback,
|
|
contentBlocks,
|
|
}),
|
|
})
|
|
},
|
|
async recheckPermission() {},
|
|
},
|
|
])
|
|
} catch {
|
|
// Malformed permission request — ignore
|
|
}
|
|
return
|
|
}
|
|
|
|
if (entry.type === 'permission_cancel') {
|
|
try {
|
|
const payload = JSON.parse(content)
|
|
setToolUseConfirmQueue((queue: any[]) =>
|
|
queue.filter(
|
|
(item: any) => item.toolUseID !== `pipe:${payload.requestId}`,
|
|
),
|
|
)
|
|
} catch {
|
|
// Malformed — ignore
|
|
}
|
|
return
|
|
}
|
|
|
|
let message: any = null
|
|
|
|
if (entry.type === 'stream' && content) {
|
|
message = createSystemMessage(
|
|
`[${displayRole} / ${pipeName}] ${content}`,
|
|
'warning',
|
|
)
|
|
} else if (entry.type === 'error') {
|
|
message = createSystemMessage(
|
|
`[${displayRole} / ${pipeName}] Error: ${content || 'no output'}`,
|
|
'error',
|
|
)
|
|
} else if (entry.type === 'done') {
|
|
message = createSystemMessage(
|
|
`[${displayRole} / ${pipeName}] Completed`,
|
|
'warning',
|
|
)
|
|
}
|
|
|
|
if (message) {
|
|
setMessages((prev: MessageType[]) => [...prev, message])
|
|
}
|
|
},
|
|
)
|
|
}, [
|
|
getToolUseContext,
|
|
mainLoopModel,
|
|
setMessages,
|
|
setToolUseConfirmQueue,
|
|
store,
|
|
tools,
|
|
])
|
|
}
|