fix(acp): harden session-page connection and localize backend errors
- Session-page connect never triggers download/install; returns SdkNotInstalled immediately and prompts the user to install from Agent Settings instead - Binary agents now accept any cached version via find_best_cached_binary_for_agent so stale caches still connect - Bound Initialize handshake with a 60s timeout and convert it to AcpError::InitializeTimeout via a sentinel in run_connection - Spawn background task owns ConnectionManager map insertion and removes the entry on exit through an RAII guard that survives panics, preventing leaked stale entries - AcpError gains SdkNotInstalled and InitializeTimeout variants plus a stable code() identifier; AcpEvent::Error carries code so the frontend can render localized messages by key - Frontend preflight now runs for all connect sources; error event handler switches on code to show translated text for initialize_timeout, sdk_not_installed, platform_not_supported, process_exited, spawn_failed and download_failed - Remove ConnectionStatus::Downloading enum variant, all frontend branches, and i18n strings; drop obsolete autoLinkFailedTitle, autoLinkPreflightFailed, preflightCheckFailedDefault and preflightFailedTitle keys across 10 locales - Add backendErrors.* translations in 10 languages - Diagnostic logging: always log agent stderr plus binary path/size/args/env keys and Initialize timing; gate stdin/stdout JSON-RPC tracing behind CODEG_ACP_DEBUG to avoid persisting user content into OS log files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@ function normalizeErrorMessage(error: unknown): string {
|
||||
return String(error)
|
||||
}
|
||||
|
||||
function isExpectedAutoLinkError(error: unknown): boolean {
|
||||
function isExpectedConnectError(error: unknown): boolean {
|
||||
if (!error || typeof error !== "object") return false
|
||||
return (error as { alerted?: unknown }).alerted === true
|
||||
}
|
||||
@@ -77,12 +77,10 @@ export function useConnectionLifecycle({
|
||||
const modeLoading =
|
||||
!hasCachedSelectors &&
|
||||
(status === "connecting" ||
|
||||
status === "downloading" ||
|
||||
(isInteractiveStatus && !effectiveSelectorsReady))
|
||||
const configOptionsLoading =
|
||||
!hasCachedSelectors &&
|
||||
(status === "connecting" ||
|
||||
status === "downloading" ||
|
||||
(isInteractiveStatus && !effectiveSelectorsReady))
|
||||
// Gate for send button: block until the backend session is fully
|
||||
// initialized (selectorsReady from the real backend event, not cache).
|
||||
@@ -141,9 +139,7 @@ export function useConnectionLifecycle({
|
||||
const s = statusRef.current
|
||||
if (!s || s === "disconnected" || s === "error") {
|
||||
connConnectRef
|
||||
.current(agentTypeRef.current, workingDir, sessionIdRef.current, {
|
||||
source: "auto_link",
|
||||
})
|
||||
.current(agentTypeRef.current, workingDir, sessionIdRef.current)
|
||||
.then(() => {
|
||||
if (!cancelled) {
|
||||
setLastAutoConnectError(null)
|
||||
@@ -157,7 +153,7 @@ export function useConnectionLifecycle({
|
||||
message: normalizeErrorMessage(e),
|
||||
})
|
||||
}
|
||||
if (!isExpectedAutoLinkError(e)) {
|
||||
if (!isExpectedConnectError(e)) {
|
||||
console.error("[ConnLifecycle] auto-connect:", e)
|
||||
}
|
||||
})
|
||||
@@ -170,7 +166,7 @@ export function useConnectionLifecycle({
|
||||
// Manage task status for connection progress
|
||||
const taskIdRef = useRef<string | null>(null)
|
||||
useEffect(() => {
|
||||
if (status === "connecting" || status === "downloading") {
|
||||
if (status === "connecting") {
|
||||
if (!taskIdRef.current) {
|
||||
const id = `acp-connect-${Date.now()}`
|
||||
taskIdRef.current = id
|
||||
@@ -271,10 +267,8 @@ export function useConnectionLifecycle({
|
||||
touchActivity(contextKey)
|
||||
if (!status || status === "disconnected" || status === "error") {
|
||||
setLastAutoConnectError(null)
|
||||
connConnect(agentType, workingDir, sessionId, {
|
||||
source: "auto_link",
|
||||
}).catch((e: unknown) => {
|
||||
if (!isExpectedAutoLinkError(e)) {
|
||||
connConnect(agentType, workingDir, sessionId).catch((e: unknown) => {
|
||||
if (!isExpectedConnectError(e)) {
|
||||
console.error("[ConnLifecycle] connect:", e)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
useConnectionStore,
|
||||
getCachedSelectors,
|
||||
type ConnectionState,
|
||||
type ConnectOptions,
|
||||
type LiveMessage,
|
||||
type PendingPermission,
|
||||
type PendingQuestion,
|
||||
@@ -45,8 +44,7 @@ export interface UseConnectionReturn {
|
||||
connect: (
|
||||
agentType: AgentType,
|
||||
workingDir?: string,
|
||||
sessionId?: string,
|
||||
options?: ConnectOptions
|
||||
sessionId?: string
|
||||
) => Promise<void>
|
||||
disconnect: () => Promise<void>
|
||||
sendPrompt: (blocks: PromptInputBlock[]) => Promise<void>
|
||||
@@ -96,12 +94,8 @@ export function useConnection(contextKey: string): UseConnectionReturn {
|
||||
const error = connection?.error ?? null
|
||||
|
||||
const connect = useCallback(
|
||||
(
|
||||
agentType: AgentType,
|
||||
workingDir?: string,
|
||||
sessionId?: string,
|
||||
options?: ConnectOptions
|
||||
) => actions.connect(contextKey, agentType, workingDir, sessionId, options),
|
||||
(agentType: AgentType, workingDir?: string, sessionId?: string) =>
|
||||
actions.connect(contextKey, agentType, workingDir, sessionId),
|
||||
[actions, contextKey]
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user