cc-haha/desktop/sidecars/claude-sidecar.ts
程序员阿江(Relakkes) 96a5842a01 fix(native): support Windows arm64 desktop startup (#954)
Add Windows ARM64 desktop release packaging, verify architecture-specific sidecar/native files in package smoke, and give the Electron sidecar more startup time plus early diagnostics for slow Windows ARM launches.

Tested: bun test electron/services/sidecarManager.test.ts
Tested: bun test scripts/quality-gate/package-smoke/index.test.ts scripts/release-update-metadata.test.ts scripts/pr/release-workflow.test.ts
Tested: bun run check:native
Tested: bun run check:policy
Not-tested: full bun run verify / coverage; this was a local issue-fix handoff, not PR-ready validation.
Confidence: high
Scope-risk: moderate
2026-07-02 18:27:55 +08:00

192 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Claude Code 桌面端合并 sidecar 入口。
*
* 历史上 server / cli / IM adapters 是各自独立的进程。每个 bun-compile
* 二进制都要带一份 ~55MB 的 bun runtime光这一项就重复占了 100MB+。
* 把所有运行模式合并到同一个二进制里runtime 只保留一份;调用方通过
* 第一个 positional 参数选择模式:
*
* claude-sidecar server --app-root <path> --host 127.0.0.1 --port 12345
* claude-sidecar cli --app-root <path> [其它 CLI 参数...]
* claude-sidecar adapters --app-root <path> [--feishu] [--telegram] [--wechat] [--dingtalk] [--whatsapp]
*
* 任何模式都必须先做 process.env / process.argv 设置,再 await 进入相应的
* 子模块树。原因src/server/index.ts、src/entrypoints/cli.tsx、以及
* adapters/feishu/index.ts 等顶层都会立即读 process.argv / process.env
* 必须在它们求值前 splice 掉 --app-root、mode、--feishu/--telegram 这些
* launcher-only 参数。
*/
import { parseLauncherArgs, resolveSidecarInvocation } from './launcherRouting'
const rawArgs = process.argv.slice(2)
const invocation = resolveSidecarInvocation(rawArgs)
if (!invocation.mode) {
console.error('claude-sidecar: missing mode argument (expected "server", "cli" or "adapters")')
process.exit(2)
}
const mode = invocation.mode
const restArgs = invocation.restArgs
if (mode === 'adapters') {
await runAdapters(restArgs)
} else {
const { appRoot, args } = parseLauncherArgs(restArgs, invocation.defaultAppRoot)
process.env.CLAUDE_APP_ROOT = appRoot
process.env.CALLER_DIR ||= process.cwd()
process.argv = [process.argv[0]!, process.argv[1]!, ...args]
await import('../../preload.ts')
if (mode === 'server') {
console.log(`[claude-sidecar] starting server mode (${process.platform}/${process.arch})`)
const { startServer } = await import('../../src/server/index.ts')
startServer()
} else if (mode === 'cli') {
await import('../../src/entrypoints/cli.tsx')
} else {
console.error(`claude-sidecar: unknown mode "${mode}" (expected "server", "cli" or "adapters")`)
process.exit(2)
}
}
async function runAdapters(rawArgs: string[]): Promise<void> {
// adapters 模式的参数解析独立于 server/cli —— 这里只接受 --feishu /
// --telegram / --wechat / --dingtalk / --whatsapp 选择启用哪个适配器,再加可选的 --app-root透传给
// adapters/common/config.ts 内的 process.env 读取)。
let appRoot: string | null = process.env.CLAUDE_APP_ROOT ?? null
let enableFeishu = false
let enableTelegram = false
let enableWechat = false
let enableDingtalk = false
let enableWhatsApp = false
for (let i = 0; i < rawArgs.length; i++) {
const arg = rawArgs[i]
if (arg === '--app-root') {
appRoot = rawArgs[i + 1] ?? null
i += 1
continue
}
if (arg === '--feishu') {
enableFeishu = true
continue
}
if (arg === '--telegram') {
enableTelegram = true
continue
}
if (arg === '--wechat') {
enableWechat = true
continue
}
if (arg === '--dingtalk') {
enableDingtalk = true
continue
}
if (arg === '--whatsapp') {
enableWhatsApp = true
continue
}
console.warn(`claude-sidecar adapters: ignoring unknown arg "${arg}"`)
}
if (!enableFeishu && !enableTelegram && !enableWechat && !enableDingtalk && !enableWhatsApp) {
console.error(
'claude-sidecar adapters: must enable at least one of --feishu / --telegram / --wechat / --dingtalk / --whatsapp',
)
process.exit(2)
}
if (appRoot) {
process.env.CLAUDE_APP_ROOT = appRoot
}
process.env.CALLER_DIR ||= process.cwd()
await import('../../preload.ts')
// 在 import adapter 之前先用同一份 loadConfig() 检查凭据。adapter 的
// top-level 代码里已经有 if (!cred) process.exit(1),但那会把整个
// 进程拖死 —— 包括另一个本来正常的 adapter。这里提前 gate 一下,
// 缺凭据的 adapter 直接跳过、不 import。
const { loadConfig } = await import('../../adapters/common/config.ts')
const config = loadConfig()
let started = 0
if (enableFeishu) {
if (!config.feishu.appId || !config.feishu.appSecret) {
console.warn(
'[claude-sidecar] --feishu requested but FEISHU_APP_ID / FEISHU_APP_SECRET missing in env or ~/.claude/adapters.json — skipping',
)
} else {
console.log('[claude-sidecar] starting Feishu adapter')
// 副作用 importfeishu/index.ts 顶层会自动 new WSClient + start()
await import('../../adapters/feishu/index.ts')
started += 1
}
}
if (enableTelegram) {
if (!config.telegram.botToken) {
console.warn(
'[claude-sidecar] --telegram requested but TELEGRAM_BOT_TOKEN missing in env or ~/.claude/adapters.json — skipping',
)
} else {
console.log('[claude-sidecar] starting Telegram adapter')
// 副作用 importtelegram/index.ts 顶层会自动 bot.start()
await import('../../adapters/telegram/index.ts')
started += 1
}
}
if (enableWechat) {
if (!config.wechat.accountId || !config.wechat.botToken) {
console.warn(
'[claude-sidecar] --wechat requested but no QR-bound WeChat account found in env or ~/.claude/adapters.json — skipping',
)
} else {
console.log('[claude-sidecar] starting WeChat adapter')
await import('../../adapters/wechat/index.ts')
started += 1
}
}
if (enableDingtalk) {
if (!config.dingtalk.clientId || !config.dingtalk.clientSecret) {
console.warn(
'[claude-sidecar] --dingtalk requested but DINGTALK_CLIENT_ID / DINGTALK_CLIENT_SECRET missing in env or ~/.claude/adapters.json — skipping',
)
} else {
console.log('[claude-sidecar] starting DingTalk adapter')
await import('../../adapters/dingtalk/index.ts')
started += 1
}
}
if (enableWhatsApp) {
const { hasWhatsAppAuth } = await import('../../adapters/whatsapp/session.ts')
if (!hasWhatsAppAuth(config.whatsapp.authDir)) {
console.warn(
'[claude-sidecar] --whatsapp requested but no QR-linked WhatsApp account found in env or ~/.claude/adapters.json — skipping',
)
} else {
console.log('[claude-sidecar] starting WhatsApp adapter')
await import('../../adapters/whatsapp/index.ts')
started += 1
}
}
if (started === 0) {
console.error(
'[claude-sidecar] no adapter could be started — check credentials in env or ~/.claude/adapters.json',
)
process.exit(1)
}
// 让进程保持存活:两个 adapter 都通过 long-lived WebSocketLark WSClient
// / grammY long-polling持有 event loop自然不会退出。这里不需要额外
// setInterval 兜底。两个 adapter 自己注册的 SIGINT handler 都会触发。
}