Files
claude-code/packages/builtin-tools/src/tools/PushNotificationTool/PushNotificationTool.ts
claude-code-best 4fc95bd5a7 feat: Remote Control 条件工具注入 — PushNotification/SendUserFile/Brief 仅 bridge 启用时可用
- PushNotificationTool、SendUserFileTool 添加 isEnabled() 使用 isBridgeEnabled()
- BriefTool 的 isEnabled() 从 isBriefEnabled() 改为 isBridgeEnabled()
- ExecuteTool 添加 isEnabled() 兜底检查,不可用时返回友好错误
- useReplBridge bridge 首次连接时插入 system 消息通知模型新工具可用
- 移除 toolSearch 中 firstParty base URL 白名单检测,默认启用 tool search

Co-Authored-By: glm-5.1[1m] <zai-org@claude-code-best.win>
2026-05-09 09:45:52 +08:00

150 lines
4.5 KiB
TypeScript

import { feature } from 'bun:bundle'
import { z } from 'zod/v4'
import type { ToolResultBlockParam } from 'src/Tool.js'
import { buildTool } from 'src/Tool.js'
import { lazySchema } from 'src/utils/lazySchema.js'
import { logForDebugging } from 'src/utils/debug.js'
import { isBridgeEnabled } from 'src/bridge/bridgeEnabled.js'
const PUSH_NOTIFICATION_TOOL_NAME = 'PushNotification'
const inputSchema = lazySchema(() =>
z.strictObject({
title: z.string().describe('Title of the push notification.'),
body: z.string().describe('Body text of the push notification.'),
priority: z
.enum(['normal', 'high'])
.optional()
.describe(
'Notification priority. Use "high" for blockers or permission prompts.',
),
}),
)
type InputSchema = ReturnType<typeof inputSchema>
type PushInput = z.infer<InputSchema>
type PushOutput = { sent: boolean }
export const PushNotificationTool = buildTool({
name: PUSH_NOTIFICATION_TOOL_NAME,
searchHint: 'push notification mobile alert notify user',
maxResultSizeChars: 1_000,
strict: true,
get inputSchema(): InputSchema {
return inputSchema()
},
async description() {
return "Send a push notification to the user's mobile device"
},
async prompt() {
return `Send a push notification to the user's mobile device via Remote Control.
Use this when:
- A long-running task completes and the user may not be watching
- A permission prompt is waiting and you need user input
- Something urgent requires the user's attention
Requires Remote Control to be configured. Respects user notification settings (taskCompleteNotifEnabled, inputNeededNotifEnabled, agentPushNotifEnabled).`
},
isEnabled() {
return isBridgeEnabled()
},
isConcurrencySafe() {
return true
},
isReadOnly() {
return true
},
userFacingName() {
return 'Notify'
},
renderToolUseMessage(input: Partial<PushInput>) {
return `Push: ${input.title ?? '...'}`
},
mapToolResultToToolResultBlockParam(
content: PushOutput,
toolUseID: string,
): ToolResultBlockParam {
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: content.sent
? 'Notification sent.'
: 'Failed to send notification.',
}
},
async call(input: PushInput, context) {
const appState = context.getAppState()
// Try bridge delivery first (for remote/mobile viewers)
if (appState.replBridgeEnabled) {
if (feature('BRIDGE_MODE')) {
try {
const { getBridgeAccessToken, getBridgeBaseUrl } = await import(
'src/bridge/bridgeConfig.js'
)
const { getSessionId } = await import('src/bootstrap/state.js')
const token = getBridgeAccessToken()
const sessionId = getSessionId()
if (token && sessionId) {
const baseUrl = getBridgeBaseUrl()
const axios = (await import('axios')).default
const response = await axios.post(
`${baseUrl}/v1/sessions/${sessionId}/events`,
{
events: [
{
type: 'push_notification',
title: input.title,
body: input.body,
priority: input.priority ?? 'normal',
},
],
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01',
},
timeout: 10_000,
validateStatus: (s: number) => s < 500,
},
)
if (response.status >= 200 && response.status < 300) {
logForDebugging(
`[PushNotification] delivered via bridge session=${sessionId}`,
)
return { data: { sent: true } }
}
logForDebugging(
`[PushNotification] bridge delivery failed: status=${response.status}`,
)
}
} catch (e) {
logForDebugging(`[PushNotification] bridge delivery error: ${e}`)
}
}
}
// Fallback: no bridge available, push was not delivered to a remote device.
logForDebugging(
`[PushNotification] no bridge available, not delivered: ${input.title}`,
)
return {
data: {
sent: false,
error:
'No Remote Control bridge configured. Notification not delivered.',
},
}
},
})