Files
claude-code/packages/builtin-tools/src/tools/PushNotificationTool/PushNotificationTool.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

134 lines
4.2 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'
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).`
},
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.' } }
},
})