Files
claude-code/src/hooks/usePipePermissionForward.ts
claude-code-best c8d08d235b 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 from 637c908 dropped 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 from 637c908 dropped 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>
2026-04-16 20:59:29 +08:00

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,
])
}