mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-08-01 16:43:37 +08:00
fix(desktop): forward Windows PowerShell choice to agent sidecar
- serverRuntime injects CLAUDE_CODE_POWERSHELL_PATH from the user's chosen shell (readDesktopTerminalConfig + resolveDesktopTerminalShell) on Windows, so the agent PowerShellTool honors the same shell as the UI terminal. Best-effort: never blocks startup, never overrides an explicit env var, only forwards pwsh/powershell selections (not cmd/custom). Regression from the Tauri build. - document that notificationPermissionState reflects OS capability, not macOS authorization (no Electron API exists; the 'failed' lifecycle is the real signal) instead of calling the non-existent systemPreferences.getNotificationSettings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f070d16d38
commit
7a882d1858
@ -23,6 +23,15 @@ export function validateNotificationOptions(value: unknown): value is DesktopNot
|
|||||||
&& (record.extra === undefined || (typeof record.extra === 'object' && record.extra !== null && !Array.isArray(record.extra)))
|
&& (record.extra === undefined || (typeof record.extra === 'object' && record.extra !== null && !Array.isArray(record.extra)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Electron has no API to read the macOS user authorization status from the
|
||||||
|
// main process (`systemPreferences.getNotificationSettings()` does not exist; the
|
||||||
|
// only option is the native `macos-notification-state` module, which we avoid to
|
||||||
|
// keep the cross-platform build dependency-free). `Notification.isSupported()`
|
||||||
|
// reflects OS capability, NOT whether the user granted permission, so this can
|
||||||
|
// report 'granted' even when the user denied notifications in System Settings.
|
||||||
|
// The authoritative delivery signal is the 'failed' lifecycle event handled in
|
||||||
|
// sendDesktopNotification(); on macOS, events only emit correctly for code-signed
|
||||||
|
// builds (unsigned/ad-hoc builds emit 'failed').
|
||||||
export function notificationPermissionState(
|
export function notificationPermissionState(
|
||||||
NotificationClass: ElectronNotificationConstructor,
|
NotificationClass: ElectronNotificationConstructor,
|
||||||
): NotificationPermissionState {
|
): NotificationPermissionState {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
formatStartupError,
|
formatStartupError,
|
||||||
killSidecar,
|
killSidecar,
|
||||||
mergeProxyEnv,
|
mergeProxyEnv,
|
||||||
|
POWERSHELL_PATH_OVERRIDE_ENV,
|
||||||
proxyUrlFromElectronProxyRules,
|
proxyUrlFromElectronProxyRules,
|
||||||
pushStartupLog,
|
pushStartupLog,
|
||||||
reserveLocalPort,
|
reserveLocalPort,
|
||||||
@ -12,8 +13,10 @@ import {
|
|||||||
SERVER_CONTROL_HOST,
|
SERVER_CONTROL_HOST,
|
||||||
spawnSidecar,
|
spawnSidecar,
|
||||||
waitForServer,
|
waitForServer,
|
||||||
|
windowsPowerShellOverride,
|
||||||
type SidecarChild,
|
type SidecarChild,
|
||||||
} from './sidecarManager'
|
} from './sidecarManager'
|
||||||
|
import { readDesktopTerminalConfig, resolveDesktopTerminalShell } from './terminal'
|
||||||
|
|
||||||
type ServerRuntimeOptions = {
|
type ServerRuntimeOptions = {
|
||||||
desktopRoot: string
|
desktopRoot: string
|
||||||
@ -157,17 +160,33 @@ export class ElectronServerRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async resolveSidecarBaseEnvOnce(): Promise<NodeJS.ProcessEnv> {
|
private async resolveSidecarBaseEnvOnce(): Promise<NodeJS.ProcessEnv> {
|
||||||
if (!this.resolveSystemProxy) return process.env
|
if (!this.resolveSystemProxy) return this.applyPowerShellOverride(process.env)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rules = await this.resolveSystemProxy('https://auth.openai.com/')
|
const rules = await this.resolveSystemProxy('https://auth.openai.com/')
|
||||||
return mergeProxyEnv(
|
return this.applyPowerShellOverride(mergeProxyEnv(
|
||||||
process.env,
|
process.env,
|
||||||
proxyUrlFromElectronProxyRules(rules),
|
proxyUrlFromElectronProxyRules(rules),
|
||||||
)
|
))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[desktop] failed to resolve system proxy for sidecars', error)
|
console.error('[desktop] failed to resolve system proxy for sidecars', error)
|
||||||
return process.env
|
return this.applyPowerShellOverride(process.env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On Windows, forward the user's chosen PowerShell to the agent sidecar so its
|
||||||
|
// PowerShellTool honors the same shell as the UI terminal (regression from the
|
||||||
|
// Tauri build, where this lived in src-tauri/src/lib.rs). Best-effort: never
|
||||||
|
// block sidecar startup, and never override an explicitly set env var.
|
||||||
|
private applyPowerShellOverride(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||||
|
if (process.platform !== 'win32' || env[POWERSHELL_PATH_OVERRIDE_ENV]) return env
|
||||||
|
try {
|
||||||
|
const shell = resolveDesktopTerminalShell('win32', readDesktopTerminalConfig(env))
|
||||||
|
const override = windowsPowerShellOverride(shell, 'win32')
|
||||||
|
if (override) return { ...env, [POWERSHELL_PATH_OVERRIDE_ENV]: override }
|
||||||
|
} catch {
|
||||||
|
// Misconfigured custom shell etc. — fall through to the unmodified env.
|
||||||
|
}
|
||||||
|
return env
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
proxyUrlFromElectronProxyRules,
|
proxyUrlFromElectronProxyRules,
|
||||||
pushStartupLog,
|
pushStartupLog,
|
||||||
resolveHostTriple,
|
resolveHostTriple,
|
||||||
|
windowsPowerShellOverride,
|
||||||
type SidecarChild,
|
type SidecarChild,
|
||||||
} from './sidecarManager'
|
} from './sidecarManager'
|
||||||
|
|
||||||
@ -148,4 +149,17 @@ describe('Electron sidecar manager', () => {
|
|||||||
expect(spawnSyncFn).toHaveBeenCalledWith('taskkill', ['/F', '/T', '/PID', '777'], { stdio: 'ignore' })
|
expect(spawnSyncFn).toHaveBeenCalledWith('taskkill', ['/F', '/T', '/PID', '777'], { stdio: 'ignore' })
|
||||||
expect(spawnAsync).not.toHaveBeenCalled()
|
expect(spawnAsync).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('forwards a PowerShell shell choice to the sidecar only on Windows', () => {
|
||||||
|
expect(windowsPowerShellOverride('pwsh.exe', 'win32')).toBe('pwsh.exe')
|
||||||
|
expect(windowsPowerShellOverride('powershell.exe', 'win32')).toBe('powershell.exe')
|
||||||
|
expect(windowsPowerShellOverride('C:\\tools\\PowerShell\\pwsh.exe', 'win32')).toBe('C:\\tools\\PowerShell\\pwsh.exe')
|
||||||
|
// non-PowerShell selections must not be reported as a PowerShell override
|
||||||
|
expect(windowsPowerShellOverride('cmd.exe', 'win32')).toBeNull()
|
||||||
|
expect(windowsPowerShellOverride('C:\\bin\\bash.exe', 'win32')).toBeNull()
|
||||||
|
expect(windowsPowerShellOverride(null, 'win32')).toBeNull()
|
||||||
|
// never applies off Windows
|
||||||
|
expect(windowsPowerShellOverride('pwsh', 'darwin')).toBeNull()
|
||||||
|
expect(windowsPowerShellOverride('powershell.exe', 'linux')).toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -140,6 +140,28 @@ export function mergeProxyEnv(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The agent's PowerShellTool reads this env var to honor the user's chosen shell
|
||||||
|
// (mirrors src/utils/shell/powershellDetection.ts). Without it the agent would
|
||||||
|
// re-autodetect PowerShell instead of using the shell the user picked in the UI.
|
||||||
|
export const POWERSHELL_PATH_OVERRIDE_ENV = 'CLAUDE_CODE_POWERSHELL_PATH'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a resolved Windows shell path to a PowerShell override for the sidecar env.
|
||||||
|
* Returns the path only on Windows when it points at pwsh/powershell, so that a
|
||||||
|
* cmd.exe or non-PowerShell custom shell selection does not get misreported as a
|
||||||
|
* PowerShell override. Matches the consumer's isPowerShellExecutablePath check.
|
||||||
|
*/
|
||||||
|
export function windowsPowerShellOverride(
|
||||||
|
shellPath: string | null | undefined,
|
||||||
|
platform: NodeJS.Platform = process.platform,
|
||||||
|
): string | null {
|
||||||
|
if (platform !== 'win32') return null
|
||||||
|
const trimmed = shellPath?.trim()
|
||||||
|
if (!trimmed) return null
|
||||||
|
const base = trimmed.split(/[\\/]/).pop()?.toLowerCase().replace(/\.exe$/, '')
|
||||||
|
return base === 'pwsh' || base === 'powershell' ? trimmed : null
|
||||||
|
}
|
||||||
|
|
||||||
export function buildSidecarEnv(baseEnv: NodeJS.ProcessEnv, h5DistDir: string): NodeJS.ProcessEnv {
|
export function buildSidecarEnv(baseEnv: NodeJS.ProcessEnv, h5DistDir: string): NodeJS.ProcessEnv {
|
||||||
const env: NodeJS.ProcessEnv = {
|
const env: NodeJS.ProcessEnv = {
|
||||||
...baseEnv,
|
...baseEnv,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user