mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +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>
134 lines
4.2 KiB
TypeScript
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.' } }
|
|
},
|
|
})
|