Skip to content

Commit 9ef47a4

Browse files
committed
feat(onboarding): Windows 环境检测 + 一键安装 Git/Node + 聊天错误卡片升级
- Onboarding 恢复 Windows 平台环境检测(Git Bash/WSL 必需、Node.js 可选), 通过 proma-api 的 /installers/manifest 拉取清单,支持一键从 OSS 下载 官方安装包并自动拉起安装程序,sha256 校验 + OSS→官方源自动降级 - macOS/Linux 保持极简 Onboarding 不变 - Agent 模式 preflight 错误(Windows shell 缺失、渠道不存在、API Key 解密 失败、SDK binary 缺失)从 console-only 升级为富文本错误卡片,带 「打开环境检测」「打开渠道设置」等结构化 recovery action 按钮 - 新增 EnvironmentCheckDialog 供错误卡片跳转复用 - 扩展 ErrorCode 枚举 + RecoveryAction.payload 字段 - 新增 runtime:reinit IPC 让用户安装完工具后手动刷新状态
1 parent 451b84f commit 9ef47a4

17 files changed

Lines changed: 1410 additions & 109 deletions

File tree

apps/electron/src/main/ipc.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { ipcMain, nativeTheme, shell, dialog, BrowserWindow, app } from 'electron'
88
import { join } from 'node:path'
99
import { existsSync } from 'node:fs'
10-
import { IPC_CHANNELS, CHANNEL_IPC_CHANNELS, CHAT_IPC_CHANNELS, AGENT_IPC_CHANNELS, ENVIRONMENT_IPC_CHANNELS, PROXY_IPC_CHANNELS, GITHUB_RELEASE_IPC_CHANNELS, SYSTEM_PROMPT_IPC_CHANNELS, MEMORY_IPC_CHANNELS, CHAT_TOOL_IPC_CHANNELS, FEISHU_IPC_CHANNELS, DINGTALK_IPC_CHANNELS, WECHAT_IPC_CHANNELS } from '@proma/shared'
10+
import { IPC_CHANNELS, CHANNEL_IPC_CHANNELS, CHAT_IPC_CHANNELS, AGENT_IPC_CHANNELS, ENVIRONMENT_IPC_CHANNELS, INSTALLER_IPC_CHANNELS, PROXY_IPC_CHANNELS, GITHUB_RELEASE_IPC_CHANNELS, SYSTEM_PROMPT_IPC_CHANNELS, MEMORY_IPC_CHANNELS, CHAT_TOOL_IPC_CHANNELS, FEISHU_IPC_CHANNELS, DINGTALK_IPC_CHANNELS, WECHAT_IPC_CHANNELS } from '@proma/shared'
1111
import { USER_PROFILE_IPC_CHANNELS, SETTINGS_IPC_CHANNELS, QUICK_TASK_IPC_CHANNELS, APP_ICON_IPC_CHANNELS } from '../types'
1212
import type { QuickTaskSubmitInput } from '../types'
1313
import type {
@@ -46,6 +46,9 @@ import type {
4646
FileEntry,
4747
FileSearchResult,
4848
EnvironmentCheckResult,
49+
InstallerManifest,
50+
InstallerDownloadRequest,
51+
InstallerDownloadResult,
4952
ProxyConfig,
5053
SystemProxyDetectResult,
5154
GitHubRelease,
@@ -84,7 +87,7 @@ import type {
8487
SDKMessage,
8588
} from '@proma/shared'
8689
import type { UserProfile, AppSettings } from '../types'
87-
import { getRuntimeStatus, getGitRepoStatus } from './lib/runtime-init'
90+
import { getRuntimeStatus, getGitRepoStatus, reinitializeRuntime } from './lib/runtime-init'
8891
import { registerUpdaterIpc } from './lib/updater/updater-ipc'
8992
import {
9093
listChannels,
@@ -121,6 +124,12 @@ import { getTutorialContent, createWelcomeConversation } from './lib/tutorial-se
121124
import { getUserProfile, updateUserProfile } from './lib/user-profile-service'
122125
import { getSettings, updateSettings } from './lib/settings-service'
123126
import { checkEnvironment } from './lib/environment-checker'
127+
import { fetchInstallerManifest, findInstallerSource } from './lib/installer-manifest'
128+
import {
129+
cancelInstallerDownload,
130+
downloadInstaller,
131+
launchInstaller,
132+
} from './lib/installer-downloader'
124133
import { getProxySettings, saveProxySettings } from './lib/proxy-settings-service'
125134
import { detectSystemProxy } from './lib/system-proxy-detector'
126135
import {
@@ -244,6 +253,14 @@ export function registerIpcHandlers(): void {
244253
}
245254
)
246255

256+
// 重新初始化运行时(用户安装完 Git/Node 后触发,Windows 场景常用)
257+
ipcMain.handle(
258+
IPC_CHANNELS.REINIT_RUNTIME,
259+
async (): Promise<RuntimeStatus> => {
260+
return reinitializeRuntime()
261+
}
262+
)
263+
247264
// 获取指定目录的 Git 仓库状态
248265
ipcMain.handle(
249266
IPC_CHANNELS.GET_GIT_REPO_STATUS,
@@ -748,6 +765,46 @@ export function registerIpcHandlers(): void {
748765
}
749766
)
750767

768+
// ===== 第三方安装包(Git / Node.js)相关 =====
769+
770+
ipcMain.handle(
771+
INSTALLER_IPC_CHANNELS.MANIFEST,
772+
async (): Promise<InstallerManifest> => {
773+
return fetchInstallerManifest()
774+
}
775+
)
776+
777+
ipcMain.handle(
778+
INSTALLER_IPC_CHANNELS.DOWNLOAD,
779+
async (event, req: InstallerDownloadRequest): Promise<InstallerDownloadResult> => {
780+
const manifest = await fetchInstallerManifest()
781+
const source = findInstallerSource(manifest, req.id, req.arch)
782+
if (!source) {
783+
throw new Error(`未找到安装包:id=${req.id}, arch=${req.arch}`)
784+
}
785+
const window = BrowserWindow.fromWebContents(event.sender)
786+
if (!window) {
787+
throw new Error('发起下载的窗口已关闭')
788+
}
789+
const key = `${req.id}:${req.arch}`
790+
return downloadInstaller(source, key, window)
791+
}
792+
)
793+
794+
ipcMain.handle(
795+
INSTALLER_IPC_CHANNELS.CANCEL,
796+
async (_event, key: string): Promise<boolean> => {
797+
return cancelInstallerDownload(key)
798+
}
799+
)
800+
801+
ipcMain.handle(
802+
INSTALLER_IPC_CHANNELS.LAUNCH,
803+
async (_event, filePath: string): Promise<void> => {
804+
await launchInstaller(filePath)
805+
}
806+
)
807+
751808
// ===== 代理配置相关 =====
752809

753810
// 获取代理配置

apps/electron/src/main/lib/agent-orchestrator.ts

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -766,44 +766,85 @@ export class AgentOrchestrator {
766766
// 0.5 清除上一轮中断标记
767767
try { updateAgentSessionMeta(sessionId, { stoppedByUser: false }) } catch { /* 会话可能已删除 */ }
768768

769+
// 环境 / 配置类错误的统一上报:持久化为 TypedError 消息,由 SDKMessageRenderer 渲染
770+
const reportPreflightError = (typedError: TypedError) => {
771+
const errorContent = typedError.title
772+
? `${typedError.title}: ${typedError.message}`
773+
: typedError.message
774+
const errorSDKMsg: SDKMessage = {
775+
type: 'assistant',
776+
message: {
777+
content: [{ type: 'text', text: errorContent }],
778+
},
779+
parent_tool_use_id: null,
780+
error: { message: typedError.message, errorType: typedError.code },
781+
_createdAt: Date.now(),
782+
_errorCode: typedError.code,
783+
_errorTitle: typedError.title,
784+
_errorDetails: typedError.details,
785+
_errorCanRetry: typedError.canRetry,
786+
_errorActions: typedError.actions,
787+
} as unknown as SDKMessage
788+
try { appendSDKMessages(sessionId, [errorSDKMsg]) } catch (e) {
789+
console.error('[Agent 编排] 持久化 preflight error 失败:', e)
790+
}
791+
callbacks.onError(errorContent)
792+
callbacks.onComplete([], { startedAt: input.startedAt })
793+
}
794+
769795
// 1. Windows 平台:检查 Shell 环境可用性
770796
if (process.platform === 'win32') {
771797
const runtimeStatus = getRuntimeStatus()
772798
const shellStatus = runtimeStatus?.shell
773799

774800
if (shellStatus && !shellStatus.gitBash?.available && !shellStatus.wsl?.available) {
775-
const errorMsg = `Windows 平台需要 Git Bash 或 WSL 环境才能运行 Agent。
776-
777-
当前状态:
778-
- Git Bash: ${shellStatus.gitBash?.error || '未检测到'}
779-
- WSL: ${shellStatus.wsl?.error || '未检测到'}
780-
781-
解决方案:
782-
1. 安装 Git for Windows(推荐): https://git-scm.com/download/win
783-
2. 或启用 WSL: https://learn.microsoft.com/zh-cn/windows/wsl/install
784-
785-
安装完成后请重启应用。`
786-
787-
callbacks.onError(errorMsg)
788-
callbacks.onComplete([], { startedAt: input.startedAt })
801+
reportPreflightError({
802+
code: 'windows_shell_missing',
803+
title: 'Windows 环境未就绪',
804+
message:
805+
'需要 Git Bash 或 WSL 才能运行 Agent。建议安装 Git for Windows(自带 Git Bash),安装完成后点「打开环境检测」刷新状态。',
806+
details: [
807+
`Git Bash: ${shellStatus.gitBash?.error || '未检测到'}`,
808+
`WSL: ${shellStatus.wsl?.error || '未检测到'}`,
809+
],
810+
actions: [
811+
{ key: 'e', label: '打开环境检测', action: 'open_environment_check' },
812+
{ key: 'g', label: '去官方下载 Git', action: 'open_external', payload: 'https://git-scm.com/download/win' },
813+
],
814+
canRetry: false,
815+
})
789816
return
790817
}
791818
}
792819

793820
// 2. 获取渠道信息并解密 API Key
794821
const channel = getChannelById(channelId)
795822
if (!channel) {
796-
callbacks.onError('渠道不存在')
797-
callbacks.onComplete([], { startedAt: input.startedAt })
823+
reportPreflightError({
824+
code: 'channel_not_found',
825+
title: '渠道不存在',
826+
message: '当前会话引用的渠道已被删除或不可用,请在设置中重新选择。',
827+
actions: [
828+
{ key: 's', label: '打开渠道设置', action: 'open_channel_settings' },
829+
],
830+
canRetry: false,
831+
})
798832
return
799833
}
800834

801835
let apiKey: string
802836
try {
803837
apiKey = decryptApiKey(channelId)
804838
} catch {
805-
callbacks.onError('解密 API Key 失败')
806-
callbacks.onComplete([], { startedAt: input.startedAt })
839+
reportPreflightError({
840+
code: 'api_key_decrypt_failed',
841+
title: 'API Key 解密失败',
842+
message: '无法解密此渠道的 API Key,可能是系统密钥环异常。请到设置中重新填写 API Key。',
843+
actions: [
844+
{ key: 's', label: '打开渠道设置', action: 'open_channel_settings' },
845+
],
846+
canRetry: false,
847+
})
807848
return
808849
}
809850

@@ -879,10 +920,33 @@ export class AgentOrchestrator {
879920
const cliPath = resolveSDKCliPath()
880921

881922
if (!existsSync(cliPath)) {
882-
const errMsg = `SDK native binary 不存在: ${cliPath}。请确保已安装对应平台的 @anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch} optional dependency`
883-
console.error(`[Agent 编排] ${errMsg}`)
884-
callbacks.onError(errMsg)
885-
callbacks.onComplete([], { startedAt: streamStartedAt })
923+
const subpkg = `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}`
924+
console.error(`[Agent 编排] SDK native binary 不存在: ${cliPath}`)
925+
reportPreflightError({
926+
code: 'claude_binary_not_found',
927+
title: 'Claude 核心未就绪',
928+
message:
929+
'应用安装包里缺少 Claude Agent SDK 的核心可执行文件(claude.exe)。这通常是打包时未包含当前平台的 SDK 组件导致。请重新下载最新安装包,或提交 issue 告知我们。',
930+
details: [
931+
`缺失文件: ${cliPath}`,
932+
`需要的子包: ${subpkg}`,
933+
],
934+
actions: [
935+
{
936+
key: 'd',
937+
label: '下载最新安装包',
938+
action: 'open_external',
939+
payload: 'https://proma.cool/download',
940+
},
941+
{
942+
key: 'i',
943+
label: '报告问题',
944+
action: 'open_external',
945+
payload: 'https://github.com/ErlichLiu/Proma/issues/new',
946+
},
947+
],
948+
canRetry: false,
949+
})
886950
return
887951
}
888952

0 commit comments

Comments
 (0)