mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
feat: integrate 5 feature branches, upstream fixes, and MIME detection fix
Squashed 5 commits: Features (from 5 feature branches): - MCP fix, pipe mute, stub recovery - KAIROS activation, openclaw autonomy - Daemon/job command hierarchy + cross-platform bg engine Upstream fixes: - fix: Bun.hash compatibility - chore: chrome dependency update - docs: browser support guide MIME detection fix: - Screenshot detectMimeFromBase64(): decode raw bytes from base64 instead of broken charCodeAt comparison - Fixes API 400 on Windows (JPEG) and macOS (PNG) screenshots
This commit is contained in:
@@ -37,16 +37,21 @@
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
/** Detect actual image MIME type from base64 data using magic bytes. */
|
||||
/** Detect actual image MIME type from base64 data by decoding the magic bytes. */
|
||||
function detectMimeFromBase64(b64: string): string {
|
||||
// First byte is enough to distinguish PNG (0x89) from JPEG (0xFF)
|
||||
const c = b64.charCodeAt(0);
|
||||
if (c === 0x89) return "image/png";
|
||||
if (c === 0xFF) return "image/jpeg";
|
||||
// RIFF = WebP
|
||||
if (c === 0x52) return "image/webp";
|
||||
// GIF
|
||||
if (c === 0x47) return "image/gif";
|
||||
// Decode first 12 raw bytes (16 base64 chars is enough) and check standard magic bytes.
|
||||
// PNG: 89 50 4E 47
|
||||
// JPEG: FF D8 FF
|
||||
// RIFF+WEBP: "RIFF" at 0..3 + "WEBP" at 8..11
|
||||
// GIF: "GIF" at 0..2
|
||||
const raw = Buffer.from(b64.slice(0, 16), "base64");
|
||||
if (raw[0] === 0x89 && raw[1] === 0x50 && raw[2] === 0x4e && raw[3] === 0x47) return "image/png";
|
||||
if (raw[0] === 0xff && raw[1] === 0xd8 && raw[2] === 0xff) return "image/jpeg";
|
||||
if (
|
||||
raw[0] === 0x52 && raw[1] === 0x49 && raw[2] === 0x46 && raw[3] === 0x46 && // RIFF
|
||||
raw[8] === 0x57 && raw[9] === 0x45 && raw[10] === 0x42 && raw[11] === 0x50 // WEBP
|
||||
) return "image/webp";
|
||||
if (raw[0] === 0x47 && raw[1] === 0x49 && raw[2] === 0x46) return "image/gif";
|
||||
return "image/png";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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'
|
||||
|
||||
@@ -74,14 +76,58 @@ Requires Remote Control to be configured. Respects user notification settings (t
|
||||
}
|
||||
},
|
||||
|
||||
async call(_input: PushInput) {
|
||||
// Push delivery is handled by the Remote Control / KAIROS transport layer.
|
||||
// Without the KAIROS runtime, this tool is not available.
|
||||
return {
|
||||
data: {
|
||||
sent: false,
|
||||
error: 'PushNotification requires the KAIROS transport layer.',
|
||||
},
|
||||
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.' } }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -70,14 +70,51 @@ Guidelines:
|
||||
}
|
||||
},
|
||||
|
||||
async call(_input: SendUserFileInput) {
|
||||
// File transfer is handled by the KAIROS assistant transport layer.
|
||||
// Without the KAIROS runtime, this tool is not available.
|
||||
async call(input: SendUserFileInput, context) {
|
||||
const { file_path } = input
|
||||
const { stat } = await import('fs/promises')
|
||||
|
||||
// Verify file exists and is readable
|
||||
let fileSize: number
|
||||
try {
|
||||
const fileStat = await stat(file_path)
|
||||
if (!fileStat.isFile()) {
|
||||
return {
|
||||
data: { sent: false, file_path, error: 'Path is not a file.' },
|
||||
}
|
||||
}
|
||||
fileSize = fileStat.size
|
||||
} catch {
|
||||
return {
|
||||
data: { sent: false, file_path, error: 'File does not exist or is not readable.' },
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt bridge upload if available (so web viewers can download)
|
||||
const appState = context.getAppState()
|
||||
let fileUuid: string | undefined
|
||||
if (appState.replBridgeEnabled) {
|
||||
try {
|
||||
const { uploadBriefAttachment } = await import(
|
||||
'@claude-code-best/builtin-tools/tools/BriefTool/upload.js'
|
||||
)
|
||||
fileUuid = await uploadBriefAttachment(file_path, fileSize, {
|
||||
replBridgeEnabled: true,
|
||||
signal: context.abortController.signal,
|
||||
})
|
||||
} catch {
|
||||
// Best-effort upload — local path is always available
|
||||
}
|
||||
}
|
||||
|
||||
const delivered = !appState.replBridgeEnabled || Boolean(fileUuid)
|
||||
return {
|
||||
data: {
|
||||
sent: false,
|
||||
file_path: _input.file_path,
|
||||
error: 'SendUserFile requires the KAIROS assistant transport layer.',
|
||||
sent: delivered,
|
||||
file_path,
|
||||
size: fileSize,
|
||||
...(fileUuid ? { file_uuid: fileUuid } : {}),
|
||||
...(!delivered ? { error: 'Bridge upload failed. File available at local path.' } : {}),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user