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:
xintaofei
2026-04-12 03:36:08 +08:00
parent 5bda7d06e9
commit 1c1738298b
25 changed files with 595 additions and 255 deletions

View File

@@ -84,7 +84,7 @@ export function ChatInput({
const t = useTranslations("Folder.chat.chatInput")
const isConnected = status === "connected"
const isPrompting = status === "prompting"
const isConnecting = status === "connecting" || status === "downloading"
const isConnecting = status === "connecting"
return (
<div className="p-4 pt-0">

View File

@@ -125,7 +125,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
}
@@ -310,8 +310,7 @@ const ConversationTabView = memo(function ConversationTabView({
useEffect(() => {
connStatusRef.current = connStatus
}, [connStatus])
const isConnecting =
connStatus === "connecting" || connStatus === "downloading"
const isConnecting = connStatus === "connecting"
const connectionModes = useMemo(
() => conn.modes?.available_modes ?? [],
[conn.modes?.available_modes]
@@ -756,15 +755,13 @@ const ConversationTabView = memo(function ConversationTabView({
const s = connStatusRef.current
const doConnect = () => {
if (!workingDirForConnection) return
connConnect(nextAgentType, workingDirForConnection, undefined, {
source: "auto_link",
})
connConnect(nextAgentType, workingDirForConnection, undefined)
.then(() => {
setAgentConnectError(null)
})
.catch((e) => {
setAgentConnectError(normalizeErrorMessage(e))
if (!isExpectedAutoLinkError(e)) {
if (!isExpectedConnectError(e)) {
console.error("[ConversationTabView] switch agent:", e)
}
})

View File

@@ -19,7 +19,6 @@ import { cn } from "@/lib/utils"
type ConnectionStatusLabelKey =
| "connected"
| "connecting"
| "downloading"
| "prompting"
| "error"
@@ -32,10 +31,6 @@ const STATUS_STYLE: Record<
className: "opacity-100 animate-pulse",
labelKey: "connecting",
},
downloading: {
className: "opacity-100 animate-pulse",
labelKey: "downloading",
},
prompting: {
className: "opacity-100 animate-pulse",
labelKey: "prompting",

View File

@@ -1106,19 +1106,12 @@ export function useConnectionStore(): ConnectionStoreApi {
// ── Actions context (unchanged interface) ──
export type ConnectSource = "manual" | "auto_link"
export interface ConnectOptions {
source?: ConnectSource
}
export interface AcpActionsValue {
connect(
contextKey: string,
agentType: AgentType,
workingDir?: string,
sessionId?: string,
options?: ConnectOptions
sessionId?: string
): Promise<void>
disconnect(contextKey: string): Promise<void>
disconnectAll(): Promise<void>
@@ -1211,7 +1204,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
// Keys whose disconnect was requested while connect was still in flight
const abandonedKeysRef = useRef(new Set<string>())
type AutoLinkBlockState =
type ConnectBlockState =
| { kind: "none"; reason: "" }
| {
kind: "missing_config" | "disabled" | "unavailable" | "sdk_missing"
@@ -1236,8 +1229,8 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
[t]
)
const resolveAutoLinkBlockState = useCallback(
(agent: AcpAgentStatus | null): AutoLinkBlockState => {
const resolveConnectBlockState = useCallback(
(agent: AcpAgentStatus | null): ConnectBlockState => {
if (!agent) {
return { kind: "missing_config", reason: t("blocked.missingConfig") }
}
@@ -1714,23 +1707,54 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
}
case "error": {
flushStreamingQueue()
dispatch({ type: "ERROR", contextKey, message: e.message })
pushAlertRef.current("error", t("eventErrorTitle"), e.message)
// Send OS notification for agent errors
{
const nc = storeRef.current.connections.get(contextKey)
if (nc) {
const agentLabel = AGENT_LABELS[nc.agentType]
const fn = folderNameRef.current
const title = fn ? `${fn} - Codeg` : "Codeg"
sendSystemNotification(
title,
t("notificationError", {
const nc = storeRef.current.connections.get(contextKey)
const agentLabel = nc
? AGENT_LABELS[nc.agentType]
: (e.agent_type as string)
// Localize backend errors via their stable `code` identifier.
// Unknown codes fall back to the raw English message so we
// never swallow a useful stack trace.
const localizedMessage = (() => {
switch (e.code) {
case "initialize_timeout":
return t("backendErrors.initializeTimeout", {
agent: agentLabel,
})
case "sdk_not_installed":
return t("blocked.sdkMissing", { agent: agentLabel })
case "platform_not_supported":
return t("blocked.unavailable", { agent: agentLabel })
case "process_exited":
return t("backendErrors.processExited", { agent: agentLabel })
case "spawn_failed":
return t("backendErrors.spawnFailed", {
agent: agentLabel,
message: e.message,
})
).catch(() => {})
case "download_failed":
return t("backendErrors.downloadFailed", {
agent: agentLabel,
message: e.message,
})
default:
return e.message
}
})()
dispatch({ type: "ERROR", contextKey, message: localizedMessage })
pushAlertRef.current("error", t("eventErrorTitle"), localizedMessage)
// Send OS notification for agent errors
if (nc) {
const fn = folderNameRef.current
const title = fn ? `${fn} - Codeg` : "Codeg"
sendSystemNotification(
title,
t("notificationError", {
agent: agentLabel,
message: localizedMessage,
})
).catch(() => {})
}
break
}
@@ -1821,11 +1845,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
for (const [contextKey, conn] of storeRef.current.connections) {
if (contextKey === currentActiveKey) continue
if (currentOpenTabKeys.has(contextKey)) continue
if (
conn.status === "prompting" ||
conn.status === "connecting" ||
conn.status === "downloading"
) {
if (conn.status === "prompting" || conn.status === "connecting") {
continue
}
if (conn.status !== "connected") continue
@@ -1865,60 +1885,54 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
contextKey: string,
agentType: AgentType,
workingDir?: string,
sessionId?: string,
options?: ConnectOptions
sessionId?: string
) => {
const source = options?.source ?? "manual"
const isAutoLink = source === "auto_link"
if (connectingKeysRef.current.has(contextKey)) return
connectingKeysRef.current.add(contextKey)
try {
if (isAutoLink) {
let configuredAgent: AcpAgentStatus | null = null
try {
configuredAgent = await acpGetAgentStatus(agentType)
} catch (error) {
const reason = t("unableReadAgentConfig", {
message: normalizeErrorMessage(error),
})
const autoLinkFailedTitle = t("autoLinkFailedTitle", {
agent: AGENT_LABELS[agentType],
})
pushAlertRef.current(
"error",
autoLinkFailedTitle,
`${reason}\n${t("agentsSetupHint")}`,
[buildOpenAgentsSettingsAction(agentType)]
)
throw createAlertedError(reason)
}
// Preflight: read agent status and block if the SDK / binary is
// not installed. The session page must never trigger a download
// or install — if the agent is not ready, prompt the user to
// install it from Agent Settings instead.
let configuredAgent: AcpAgentStatus | null = null
try {
configuredAgent = await acpGetAgentStatus(agentType)
} catch (error) {
const reason = t("unableReadAgentConfig", {
message: normalizeErrorMessage(error),
})
const failedTitle = t("connectFailedTitle", {
agent: AGENT_LABELS[agentType],
})
pushAlertRef.current(
"error",
failedTitle,
`${reason}\n${t("agentsSetupHint")}`,
[buildOpenAgentsSettingsAction(agentType)]
)
throw createAlertedError(reason)
}
const blocked = resolveAutoLinkBlockState(configuredAgent)
if (blocked.kind !== "none") {
const autoLinkFailedTitle = t("autoLinkFailedTitle", {
agent: AGENT_LABELS[agentType],
})
const detail =
blocked.kind === "sdk_missing"
? t("withSetupHint", {
message: blocked.reason,
hint: t("agentsSetupHint"),
})
: `${blocked.reason}\n${t("agentsSetupHint")}`
pushAlertRef.current(
"error",
blocked.kind === "sdk_missing"
? blocked.reason
: autoLinkFailedTitle,
detail,
[buildOpenAgentsSettingsAction(agentType)]
)
throw createAlertedError(
blocked.kind === "sdk_missing" ? blocked.reason : blocked.reason
)
}
const blocked = resolveConnectBlockState(configuredAgent)
if (blocked.kind !== "none") {
const failedTitle = t("connectFailedTitle", {
agent: AGENT_LABELS[agentType],
})
const detail =
blocked.kind === "sdk_missing"
? t("withSetupHint", {
message: blocked.reason,
hint: t("agentsSetupHint"),
})
: `${blocked.reason}\n${t("agentsSetupHint")}`
pushAlertRef.current(
"error",
blocked.kind === "sdk_missing" ? blocked.reason : failedTitle,
detail,
[buildOpenAgentsSettingsAction(agentType)]
)
throw createAlertedError(blocked.reason)
}
const existing = storeRef.current.connections.get(contextKey)
@@ -1970,11 +1984,37 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
} catch (err) {
if (!isAlertedError(err)) {
const message = normalizeErrorMessage(err)
pushAlertRef.current(
"error",
t("connectFailedTitle", { agent: agentType }),
message
)
const agentLabel = AGENT_LABELS[agentType]
// Backend safety net: if the agent turned out to be not
// installed (e.g. the binary was removed between preflight
// and spawn), surface the same install prompt with a direct
// "Open Agent Settings" action. Title is localized via the
// same i18n key the preflight path uses.
//
// INVARIANT: `AcpError::SdkNotInstalled` renders its payload
// unchanged, and both producers
// (`src-tauri/src/commands/acp.rs::verify_agent_installed`
// and `src-tauri/src/acp/connection.rs::build_agent` Binary
// branch) format the message with the literal English
// substring "is not installed". Do NOT translate those two
// format strings — this branch matches on them as a stable
// identifier, since `AcpError::Serialize` flattens to a bare
// message string and does not expose the error `code` for
// synchronous Tauri command rejections.
if (message.includes("is not installed")) {
pushAlertRef.current(
"error",
t("blocked.sdkMissing", { agent: agentLabel }),
t("agentsSetupHint"),
[buildOpenAgentsSettingsAction(agentType)]
)
} else {
pushAlertRef.current(
"error",
t("connectFailedTitle", { agent: agentLabel }),
message
)
}
}
throw err
} finally {
@@ -1987,7 +2027,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
consumeBufferedEvents,
dispatch,
handleMappedEvent,
resolveAutoLinkBlockState,
resolveConnectBlockState,
t,
waitForListenerReady,
]

View File

@@ -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)
}
})

View File

@@ -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]
)

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "متصل",
"connecting": "جارٍ الاتصال...",
"downloading": "جارٍ التنزيل...",
"prompting": "جارٍ الرد...",
"error": "خطأ في الاتصال",
"disconnected": "غير متصل",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} غير متاح على المنصة الحالية.",
"sdkMissing": "لم يتم تثبيت SDK الخاص بـ {agent}"
},
"backendErrors": {
"initializeTimeout": "انتهت مهلة مصافحة اتصال {agent} (لا استجابة بعد 60 ثانية). افتح الإعدادات للتحقق من إعدادات الوكيل والشبكة.",
"processExited": "انتهت عملية {agent} بشكل غير متوقع.",
"spawnFailed": "تعذر بدء تشغيل {agent}: {message}",
"downloadFailed": "فشل تنزيل {agent}: {message}"
},
"unableReadAgentConfig": "تعذر قراءة إعدادات الوكيل: {message}",
"autoLinkFailedTitle": "فشل الربط التلقائي لـ {agent}",
"preflightCheckFailedDefault": "فشلت فحوصات ما قبل التشغيل. تحقق من إعدادات الوكلاء.",
"preflightFailedTitle": "فشل فحص ما قبل التشغيل لـ {agent}",
"autoLinkPreflightFailed": "فشل فحص ما قبل التشغيل للربط التلقائي: {message}",
"connectFailedTitle": "فشل اتصال {agent}",
"toolFallbackTitle": "أداة",
"eventErrorTitle": "خطأ الوكيل",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "Verbunden",
"connecting": "Verbinde...",
"downloading": "Lade herunter...",
"prompting": "Antworte...",
"error": "Verbindungsfehler",
"disconnected": "Getrennt",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} ist auf der aktuellen Plattform nicht verfügbar.",
"sdkMissing": "{agent} SDK ist nicht installiert"
},
"backendErrors": {
"initializeTimeout": "Der Verbindungs-Handshake von {agent} hat nach 60 Sekunden das Zeitlimit überschritten. Öffnen Sie die Einstellungen, um Agenten- und Netzwerkkonfiguration zu prüfen.",
"processExited": "{agent}-Prozess wurde unerwartet beendet.",
"spawnFailed": "{agent} konnte nicht gestartet werden: {message}",
"downloadFailed": "{agent}-Download fehlgeschlagen: {message}"
},
"unableReadAgentConfig": "Agenten-Konfiguration kann nicht gelesen werden: {message}",
"autoLinkFailedTitle": "{agent} Auto-Link fehlgeschlagen",
"preflightCheckFailedDefault": "Preflight-Prüfungen fehlgeschlagen. Prüfen Sie die Agenten-Einstellungen.",
"preflightFailedTitle": "{agent} Preflight fehlgeschlagen",
"autoLinkPreflightFailed": "Auto-Link-Preflight fehlgeschlagen: {message}",
"connectFailedTitle": "{agent} Verbindung fehlgeschlagen",
"toolFallbackTitle": "Werkzeug",
"eventErrorTitle": "Agentenfehler",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "Connected",
"connecting": "Connecting...",
"downloading": "Downloading...",
"prompting": "Responding...",
"error": "Connection error",
"disconnected": "Disconnected",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} is unavailable on the current platform.",
"sdkMissing": "{agent} SDK is not installed"
},
"backendErrors": {
"initializeTimeout": "{agent} connection handshake timed out after 60 seconds. Please open Settings to check Agent configuration and network settings.",
"processExited": "{agent} process exited unexpectedly.",
"spawnFailed": "Failed to start {agent}: {message}",
"downloadFailed": "{agent} download failed: {message}"
},
"unableReadAgentConfig": "Unable to read Agent config: {message}",
"autoLinkFailedTitle": "{agent} auto-link failed",
"preflightCheckFailedDefault": "Preflight checks failed. Check Agent settings.",
"preflightFailedTitle": "{agent} preflight failed",
"autoLinkPreflightFailed": "Auto-link preflight failed: {message}",
"connectFailedTitle": "{agent} connection failed",
"toolFallbackTitle": "Tool",
"eventErrorTitle": "Agent Error",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "Conectado",
"connecting": "Conectando...",
"downloading": "Descargando...",
"prompting": "Respondiendo...",
"error": "Error de conexión",
"disconnected": "Desconectado",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} no está disponible en la plataforma actual.",
"sdkMissing": "El SDK de {agent} no está instalado"
},
"backendErrors": {
"initializeTimeout": "Se agotó el tiempo del handshake de conexión de {agent} (sin respuesta tras 60 segundos). Abre Ajustes para revisar la configuración del agente y de la red.",
"processExited": "El proceso de {agent} terminó inesperadamente.",
"spawnFailed": "No se pudo iniciar {agent}: {message}",
"downloadFailed": "La descarga de {agent} falló: {message}"
},
"unableReadAgentConfig": "No se puede leer la configuración del agente: {message}",
"autoLinkFailedTitle": "Falló el autovínculo de {agent}",
"preflightCheckFailedDefault": "Fallaron las comprobaciones previas. Revisa los ajustes de agentes.",
"preflightFailedTitle": "Falló la verificación previa de {agent}",
"autoLinkPreflightFailed": "Falló la verificación previa del autovínculo: {message}",
"connectFailedTitle": "Falló la conexión de {agent}",
"toolFallbackTitle": "Herramienta",
"eventErrorTitle": "Error del agente",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "Connecté",
"connecting": "Connexion...",
"downloading": "Téléchargement...",
"prompting": "Réponse...",
"error": "Erreur de connexion",
"disconnected": "Déconnecté",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} n'est pas disponible sur la plateforme actuelle.",
"sdkMissing": "Le SDK de {agent} n'est pas installé"
},
"backendErrors": {
"initializeTimeout": "Le handshake de connexion de {agent} a expiré (aucune réponse après 60 secondes). Ouvrez Paramètres pour vérifier la configuration de l'agent et du réseau.",
"processExited": "Le processus {agent} s'est arrêté de manière inattendue.",
"spawnFailed": "Impossible de démarrer {agent} : {message}",
"downloadFailed": "Échec du téléchargement de {agent} : {message}"
},
"unableReadAgentConfig": "Impossible de lire la configuration de l'agent : {message}",
"autoLinkFailedTitle": "Échec de l'auto-liaison de {agent}",
"preflightCheckFailedDefault": "Les vérifications préalables ont échoué. Vérifiez les paramètres des agents.",
"preflightFailedTitle": "Échec de la vérification préalable de {agent}",
"autoLinkPreflightFailed": "Échec de la vérification préalable de l'auto-liaison : {message}",
"connectFailedTitle": "Échec de la connexion de {agent}",
"toolFallbackTitle": "Outil",
"eventErrorTitle": "Erreur de l'agent",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "接続済み",
"connecting": "接続中...",
"downloading": "ダウンロード中...",
"prompting": "応答中...",
"error": "接続エラー",
"disconnected": "未接続",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} は現在のプラットフォームでは利用できません。",
"sdkMissing": "{agent} SDK がインストールされていません"
},
"backendErrors": {
"initializeTimeout": "{agent} の接続ハンドシェイクがタイムアウトしました60 秒以内に応答なし)。設定を開いてエージェントとネットワーク設定を確認してください。",
"processExited": "{agent} プロセスが予期せず終了しました。",
"spawnFailed": "{agent} の起動に失敗しました: {message}",
"downloadFailed": "{agent} のダウンロードに失敗しました: {message}"
},
"unableReadAgentConfig": "エージェント設定を読み取れません: {message}",
"autoLinkFailedTitle": "{agent} の自動リンクに失敗しました",
"preflightCheckFailedDefault": "事前チェックに失敗しました。エージェント設定を確認してください。",
"preflightFailedTitle": "{agent} の事前チェックに失敗しました",
"autoLinkPreflightFailed": "自動リンクの事前チェックに失敗しました: {message}",
"connectFailedTitle": "{agent} の接続に失敗しました",
"toolFallbackTitle": "ツール",
"eventErrorTitle": "エージェントエラー",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "연결됨",
"connecting": "연결 중...",
"downloading": "다운로드 중...",
"prompting": "응답 중...",
"error": "연결 오류",
"disconnected": "연결 끊김",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent}은(는) 현재 플랫폼에서 사용할 수 없습니다.",
"sdkMissing": "{agent} SDK가 설치되어 있지 않습니다"
},
"backendErrors": {
"initializeTimeout": "{agent} 연결 핸드셰이크가 시간 초과되었습니다(60초 동안 응답 없음). 설정을 열어 에이전트 및 네트워크 구성을 확인하세요.",
"processExited": "{agent} 프로세스가 예기치 않게 종료되었습니다.",
"spawnFailed": "{agent} 시작 실패: {message}",
"downloadFailed": "{agent} 다운로드 실패: {message}"
},
"unableReadAgentConfig": "에이전트 구성을 읽을 수 없습니다: {message}",
"autoLinkFailedTitle": "{agent} 자동 연결 실패",
"preflightCheckFailedDefault": "사전 점검에 실패했습니다. 에이전트 설정을 확인하세요.",
"preflightFailedTitle": "{agent} 사전 점검 실패",
"autoLinkPreflightFailed": "자동 연결 사전 점검 실패: {message}",
"connectFailedTitle": "{agent} 연결 실패",
"toolFallbackTitle": "도구",
"eventErrorTitle": "에이전트 오류",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "Conectado",
"connecting": "Conectando...",
"downloading": "Baixando...",
"prompting": "Respondendo...",
"error": "Erro de conexão",
"disconnected": "Desconectado",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} está indisponível na plataforma atual.",
"sdkMissing": "O SDK de {agent} não está instalado"
},
"backendErrors": {
"initializeTimeout": "O handshake de conexão de {agent} expirou (sem resposta após 60 segundos). Abra Configurações para verificar a configuração do agente e da rede.",
"processExited": "O processo de {agent} encerrou inesperadamente.",
"spawnFailed": "Falha ao iniciar {agent}: {message}",
"downloadFailed": "Falha no download de {agent}: {message}"
},
"unableReadAgentConfig": "Não foi possível ler a configuração do agente: {message}",
"autoLinkFailedTitle": "Falha no vínculo automático de {agent}",
"preflightCheckFailedDefault": "As verificações de pré-voo falharam. Verifique as configurações de agentes.",
"preflightFailedTitle": "Pré-voo de {agent} falhou",
"autoLinkPreflightFailed": "Pré-voo do vínculo automático falhou: {message}",
"connectFailedTitle": "Falha na conexão de {agent}",
"toolFallbackTitle": "Ferramenta",
"eventErrorTitle": "Erro do agente",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "已连接",
"connecting": "连接中...",
"downloading": "下载中...",
"prompting": "响应中...",
"error": "连接异常",
"disconnected": "未连接",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} 当前平台不可用。",
"sdkMissing": "{agent} SDK 尚未安装"
},
"backendErrors": {
"initializeTimeout": "{agent} 连接握手超时60 秒未响应),请前往设置页面检查智能体和网络配置。",
"processExited": "{agent} 进程意外退出。",
"spawnFailed": "启动 {agent} 失败:{message}",
"downloadFailed": "{agent} 下载失败:{message}"
},
"unableReadAgentConfig": "无法读取 Agent 配置:{message}",
"autoLinkFailedTitle": "{agent} 自动链接失败",
"preflightCheckFailedDefault": "预检查未通过,请检查 Agent 配置。",
"preflightFailedTitle": "{agent} 预检查失败",
"autoLinkPreflightFailed": "自动链接预检查失败:{message}",
"connectFailedTitle": "{agent} 连接失败",
"toolFallbackTitle": "工具",
"eventErrorTitle": "Agent 错误",

View File

@@ -851,7 +851,6 @@
"connection": {
"connected": "已連線",
"connecting": "連線中...",
"downloading": "下載中...",
"prompting": "回應中...",
"error": "連線異常",
"disconnected": "未連線",
@@ -1422,11 +1421,13 @@
"unavailable": "{agent} 目前平台不可用。",
"sdkMissing": "{agent} SDK 尚未安裝"
},
"backendErrors": {
"initializeTimeout": "{agent} 連線交握逾時60 秒未回應),請前往設定頁面檢查智能體與網路設定。",
"processExited": "{agent} 處理程序意外結束。",
"spawnFailed": "啟動 {agent} 失敗:{message}",
"downloadFailed": "{agent} 下載失敗:{message}"
},
"unableReadAgentConfig": "無法讀取 Agent 設定:{message}",
"autoLinkFailedTitle": "{agent} 自動連結失敗",
"preflightCheckFailedDefault": "預檢查未通過,請檢查 Agent 設定。",
"preflightFailedTitle": "{agent} 預檢查失敗",
"autoLinkPreflightFailed": "自動連結預檢查失敗:{message}",
"connectFailedTitle": "{agent} 連線失敗",
"toolFallbackTitle": "工具",
"eventErrorTitle": "Agent 錯誤",

View File

@@ -256,7 +256,6 @@ export const AGENT_COLORS: Record<AgentType, string> = {
// ACP connection status (matches Rust ConnectionStatus)
export type ConnectionStatus =
| "connecting"
| "downloading"
| "connected"
| "prompting"
| "disconnected"
@@ -442,7 +441,14 @@ export type AcpEvent =
connection_id: string
status: ConnectionStatus
}
| { type: "error"; connection_id: string; message: string }
| {
type: "error"
connection_id: string
message: string
agent_type: string
/** Stable backend error identifier for localization (e.g. "initialize_timeout"). */
code: string | null
}
| {
type: "available_commands"
connection_id: string