Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
53d2988
ci(wsl): registrar el lab W3 en main para poder despacharlo
sosan Oct 24, 2026
e6c3537
fix(w3-lab): usar windows-2022 (windows-latest ya no trae WSL)
sosan Oct 24, 2026
19a9a4c
fix(w3-lab): windows-latest + sonda --version + exit 0 (igual a la rama)
sosan Oct 24, 2026
29e4e4c
fix(w3-lab): sha256 a fichero (PS7 .Content es byte[]) — igual a la rama
sosan Oct 24, 2026
12f578c
fix(w3-lab): veredicto global + evidencia always() (igual a la rama)
sosan Oct 25, 2026
5840755
fix(w3-lab): volcado del import al log (igual a la rama)
sosan Oct 25, 2026
4601676
fix(w3-lab): import con directorio creado (igual a la rama)
sosan Oct 25, 2026
527aa38
fix(w3-lab): Gap 1 con testigo (igual a la rama)
sosan Oct 25, 2026
560468b
fix(w3-lab): senal de fichero + .wslconfig (igual a la rama)
sosan Oct 25, 2026
7c28615
fix(w3-lab): configurar WSL del runner (igual a la rama)
sosan Oct 25, 2026
4b95f66
fix(w3-lab): shift antes del exec (igual a la rama)
sosan Oct 25, 2026
df7ad95
fix(w3-lab): background + EXIT aparte (igual a la rama)
sosan Oct 25, 2026
e4b11e5
fix(w3-lab): Start-Job para el launch (igual a la rama)
sosan Oct 25, 2026
923734f
fix(w3-lab): sleep 1 + diagnostico en vivo (igual a la rama)
sosan Oct 25, 2026
767529b
fix(w3-lab): background dentro de la distro (igual a la rama)
sosan Oct 25, 2026
e0be03d
fix(w3-lab): redireccion en una linea (igual a la rama)
sosan Oct 25, 2026
0ef12fd
feat(w3-lab): Gap 1b (igual a la rama)
sosan Oct 25, 2026
6ea6651
fix(w3-lab): script a fichero en Gap 1b (igual a la rama)
sosan Oct 25, 2026
db11ff8
fix(w3-lab): vmIdleTimeout=-1 (igual a la rama)
sosan Oct 25, 2026
6f77526
fix(w3-lab): veredicto global solo por Gap 2 (igual a la rama)
sosan Oct 25, 2026
c58cd47
fix(w3-lab): Gap 1b continue-on-error (igual a la rama)
sosan Oct 25, 2026
a25dafd
fix(w3-lab): Stop-Process tolerante (igual a la rama)
sosan Oct 25, 2026
bfd1464
fix(w3-lab): caso empty como string (igual a la rama)
sosan Oct 25, 2026
80827c4
feat(w3-lab): probar Ubuntu con systemd (igual a la rama)
sosan Oct 25, 2026
07f7763
feat(w3-lab): Ubuntu por wsl --install (igual a la rama)
sosan Oct 26, 2026
89b9200
fix(w3-lab): desactivar OOBE de Ubuntu (igual a la rama)
sosan Oct 26, 2026
4d81a05
fix(w3-lab): restaurar list-after (igual a la rama)
sosan Oct 26, 2026
5fb40f9
Merge branch 'egdev6:main' into main
sosan Oct 27, 2026
76d2b2a
Merge branch 'egdev6:main' into main
sosan Oct 28, 2026
7d22295
feat(wsl): adaptador WSL (Fase A+B) + lab W3 de evidencia — issue #90
sosan Oct 30, 2026
234fb11
Merge branch 'egdev6:main' into main
sosan Oct 30, 2026
6efc038
Merge branch 'main' into feat/wsl-adapter
sosan Oct 30, 2026
6612082
fix(wsl): corregir borrow de runner en test, retoques de review y i18…
sosan Oct 30, 2026
f34a340
merge: integrar main del upstream (notarización macOS, i18n y fijacio…
sosan Oct 30, 2026
f48a6f8
fix(wsl): aplicar los 3 hallazgos de la revisión PR #220
sosan Oct 30, 2026
cb075d1
docs(i18n): regenerar el inventario tras los cambios de src-tauri/src
sosan Oct 30, 2026
00112c9
merge: integrar main del upstream (tira X11 #221, notarización, i18n)…
sosan Oct 31, 2026
dc33859
merge: integrar main del upstream (widget #214/#227/#228, X11 #215, m…
sosan Nov 2, 2026
f57145b
fix(wsl): identidad de instancia en el pidfile y rutas POSIX (#90)
sosan Nov 2, 2026
232d64a
docs(i18n): regenerar el inventario tras los mensajes POSIX/identidad
sosan Nov 2, 2026
38dfaf8
feat(wsl): resolver sesiones por el adaptador — SessionResolver (#91)
sosan Oct 31, 2026
f1113cd
feat(wsl): resolver sesiones de los proveedores por entorno (#91)
sosan Nov 1, 2026
2cc164d
fix(wsl): lecturas del entorno WSL en hilos propios + sello de la dis…
sosan Nov 1, 2026
3e22657
docs(wsl): espacio en el doc de Arc del SessionResolver (review 2º pase)
sosan Nov 1, 2026
7b06652
fix(wsl): método del trait en scope y Result en el sello (CI real)
sosan Nov 2, 2026
733f52e
style(wsl): rustfmt en el import del trait en lib.rs
sosan Nov 2, 2026
f419119
style: lints de clippy del alcance del milestone
sosan Nov 2, 2026
fb03338
fix(wsl): rutas POSIX en el resolvedor y caché del home por distro (#91)
sosan Nov 3, 2026
028c8f1
docs(i18n): regenerar el inventario tras caché y POSIX del resolvedor
sosan Nov 3, 2026
580d16c
Merge remote-tracking branch 'remoted/main' into wsl-adapter-90
sosan Nov 3, 2026
f4b7deb
Merge branch 'wsl-adapter-90' into wsl-sessions
sosan Nov 3, 2026
301bd35
fix(wsl): ligar la comprobación de identidad al TERM y al KILL (#90)
sosan Nov 3, 2026
311cc24
docs(i18n): regenerar el inventario tras el KILL ligado a identidad
sosan Nov 3, 2026
2ba012c
Merge branch 'wsl-adapter-90' into wsl-sessions
sosan Nov 3, 2026
47446da
Merge remote-tracking branch 'remoted/main' into wsl-sessions
sosan Nov 3, 2026
778f88f
fix(wsl): rutas POSIX en cuentas adicionales y validación de la explí…
sosan Nov 4, 2026
33f9d4c
docs(i18n): regenerar el inventario tras POSIX de cuentas y explícita…
sosan Nov 4, 2026
1f66c83
fix(wsl): rutas del guest con separador POSIX en todo el recorrido (#91)
sosan Nov 4, 2026
ff452ba
docs(i18n): regenerar el inventario tras guest_relative (#91)
sosan Nov 4, 2026
abeb792
fix(wsl): proveedores sin CLI y layout por defecto en WSL (#91)
sosan Nov 4, 2026
a3e02c8
docs(i18n): regenerar el inventario tras retirar el mensaje de "sin l…
sosan Nov 4, 2026
479e29d
Merge remote-tracking branch 'remoted/main' into wsl-sessions
sosan Nov 5, 2026
47460fd
fix(wsl): layout host para cuentas por defecto y fuentes únicas (#91)
sosan Nov 6, 2026
44d7a02
docs(i18n): regenerar el inventario tras layout host y fuentes únicas…
sosan Nov 6, 2026
567a000
Merge remote-tracking branch 'origin/main' into HEAD
egdev6 Nov 6, 2026
7c9227b
docs(i18n): regenerar el inventario tras integrar main (#239)
egdev6 Nov 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions docs/i18n-inventory.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# i18n inventory (egdev6/quorb#66)

Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `252ab0dd90465b16a55ad09173b55d0a4c277f80` and `src-tauri/src/` = `7c0f27b6432901d4f1a27613be3b45a1681d3997`.
Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `252ab0dd90465b16a55ad09173b55d0a4c277f80` and `src-tauri/src/` = `6a1422a6e9721fec93fc0a572f566d057cd902a0`.
Any commit whose `git rev-parse <commit>:src` and `<commit>:src-tauri/src` match has these exact texts.
Do not edit by hand: regenerate, or verify with `node scripts/i18n-inventory.mjs --check`.

Expand All @@ -19,8 +19,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
| Area | Distinct texts | Sites |
|---|---:|---:|
| React (`src/`) | 958 | 1066 |
| Rust (`src-tauri/src/`) | 496 | 601 |
| Total | 1416 | 1667 |
| Rust (`src-tauri/src/`) | 506 | 622 |
| Total | 1426 | 1688 |

## src-tauri/src/channel.rs

Expand Down Expand Up @@ -149,6 +149,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- No hay archivo para el efecto propio de este aviso
- No se pudo aplicar «siempre encima»: {detail}
- Surface clip UI callback was dropped
- WSL: {e:?}
- WSL: {e}
- Ya hay una apertura de terminal en curso para esta cuenta. Si no se abrió, inténtalo de nuevo.
- {label} ({text}): combinación no válida ({e})
- {label} ({text}): en Wayland el registro se hace vía X11/XWayland y puede no tener efecto global; el atajo puede no responder.
Expand Down Expand Up @@ -229,8 +231,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Team
- Token rechazado. «Renovar sesión» abre Claude Code para renovarlo; Quorb se actualiza solo.
- Uso extra (mes)
- WSL: {e:?}
- créditos
- {e}. Inicia sesión en Claude Code.
- {m}. Inicia sesión en Claude Code.

## src-tauri/src/providers/codex.rs

Expand All @@ -246,10 +250,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Sesión · {} h
- Ventana corta
- Ventana larga
- WSL: {e:?}
- auth.json sin access_token
- auth.json sin tokens. Ejecuta «codex login».
- créditos
- {e}. Ejecuta «codex login».
- {m}. Ejecuta «codex login».
- {} días

## src-tauri/src/providers/copilot.rs
Expand Down Expand Up @@ -302,8 +308,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Respuesta inesperada: {e}
- Sesión de Gemini rechazada. Abre Gemini CLI para renovarla.
- Sin cuotas por modelo en la respuesta (¿cuenta con API key en vez de login de Google?)
- WSL: {e:?}
- model:{name}
- {e}. Inicia sesión en Gemini CLI con tu cuenta de Google.
- {m}. Inicia sesión en Gemini CLI con tu cuenta de Google.

## src-tauri/src/providers/grok.rs

Expand All @@ -316,9 +324,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Sesión caducada. Ejecuta «grok login» para renovarla.
- Uso de créditos
- Uso semanal
- WSL: {e:?}
- auth.json no es un objeto
- auth.json sin credenciales válidas. Ejecuta «grok login».
- {e}. Ejecuta «grok login».
- {m}. Ejecuta «grok login».

## src-tauri/src/providers/minimax.rs

Expand All @@ -341,6 +351,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a

- Error de red: {e}
- JSON inválido en {}: {e}
- La carpeta de la cuenta {} no es válida en modo WSL: {problem}
- No encuentro credenciales en: {}
- No existe {}
- No puedo leer {}: {e}
Expand Down Expand Up @@ -653,8 +664,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a

## src-tauri/src/wsl/mod.rs

- credentials_path en modo WSL debe ser una ruta absoluta de la distro (empieza por '/'), se recibió: {p}
- credentials_path en modo WSL usa separadores POSIX ('/'), se recibió: {p}
- La ruta en modo WSL debe ser absoluta del guest (empieza por '/'), se recibió: {path}
- La ruta en modo WSL no puede llevar separadores de Windows (\\). Pega la ruta del guest (por ejemplo /home/usuario/...) en su lugar: {path}
- la ruta del proveedor dentro del modo debe ser relativa, se recibió: {rel}
- la ruta del proveedor dentro del modo usa separadores POSIX ('/'), se recibió: {rel}

Expand Down Expand Up @@ -687,6 +698,14 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- wsl.exe no devolvió el usuario por defecto de la distro
- wsl.exe no soporta --version (build antigua, sin WSL2)

## src-tauri/src/wsl/session.rs

- No encuentro credenciales en {distro}: {}
- No encuentro credenciales en {}
- No puedo leer {display}: {e}
- No puedo leer {}: {e}
- WSL: {e:?}

## src/Settings.tsx

- %APPDATA%\\Cursor\\User\\globalStorage\\state.vscdb
Expand Down
90 changes: 83 additions & 7 deletions src-tauri/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ fn normalize_hex(s: &str) -> Option<String> {
}
}

/// Entorno de sesión de los proveedores CLIs (#91, #90).
///
/// Serializa como string (`"host"` por defecto, `"wsl"` para usar la
/// distro `wsl_distro`). Deserializar es tolerante: cualquier valor
/// desconocido se trata como `host`.
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SessionEnvConfig {
#[default]
Host,
Wsl,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default, rename_all = "camelCase")]
pub struct AppConfig {
Expand Down Expand Up @@ -77,6 +90,11 @@ pub struct AppConfig {
pub provider_order: Vec<String>,
/// Círculos ocultos en la tira (se siguen consultando y avisando).
pub hidden_providers: Vec<String>,
/// Entorno de sesión: `host` (Windows/macOS) o `wsl` (la distro elegida).
pub session_env: SessionEnvConfig,
/// Distro WSL elegida cuando `session_env` es `wsl`. Vacía = la que
/// marque el usuario; sin distro no hay entorno WSL válido.
pub wsl_distro: String,
pub alerts: AlertsConfig,
pub shortcuts: ShortcutsConfig,
pub claude: SubscriptionConfig,
Expand Down Expand Up @@ -261,13 +279,22 @@ impl Account {
/// Fichero de sesión que deja el CLI dentro de su carpeta.
pub fn credentials_file(&self, home: &Path) -> Option<std::path::PathBuf> {
let dir = self.session_dir(home);
match self.provider.as_str() {
"claude" => Some(dir.join(".credentials.json")),
"codex" => Some(dir.join("auth.json")),
"gemini" => Some(dir.join(".gemini").join("oauth_creds.json")),
"grok" => Some(dir.join("auth.json")),
_ => None,
}
credentials_file_name(self.provider.as_str()).map(|name| dir.join(name))
}
}

/// Nombre (relativo) del fichero de sesión de un proveedor.
///
/// Una sola fuente de verdad: lo usan `Account::credentials_file` (host) y la
/// resolución de sesiones en WSL, para que ambas compongan la misma ruta
/// (#91). `None` = el proveedor no tiene login por CLI.
pub fn credentials_file_name(provider: &str) -> Option<&'static str> {
match provider {
"claude" => Some(".credentials.json"),
"codex" => Some("auth.json"),
"gemini" => Some(".gemini/oauth_creds.json"),
"grok" => Some("auth.json"),
_ => None,
}
}

Expand All @@ -289,6 +316,8 @@ impl Default for AppConfig {
auto_update: true,
provider_order: vec![],
hidden_providers: vec![],
session_env: SessionEnvConfig::default(),
wsl_distro: String::new(),
alerts: AlertsConfig::default(),
claude: SubscriptionConfig {
enabled: true,
Expand Down Expand Up @@ -577,6 +606,24 @@ impl Default for NineRouterConfig {
}

impl AppConfig {
/// El `SessionEnv` del módulo wsl que este config describe (#91).
///
/// `host` → sesión del host; `wsl` con distro no vacía → la distro
/// elegida. `wsl` sin distro todavía no es un entorno válido: se
/// trata como host hasta que el usuario elija una distro en Ajustes
/// (la resolución nunca inventa una).
pub fn session_env(&self) -> crate::wsl::SessionEnv {
match self.session_env {
SessionEnvConfig::Host => crate::wsl::SessionEnv::Host,
SessionEnvConfig::Wsl if self.wsl_distro.trim().is_empty() => {
crate::wsl::SessionEnv::Host
}
SessionEnvConfig::Wsl => crate::wsl::SessionEnv::Wsl {
distro: self.wsl_distro.trim().to_string(),
},
}
}

/// Corrige valores fuera de rango.
pub fn sanitized(mut self) -> Self {
let locale = self.locale.trim().to_ascii_lowercase();
Expand Down Expand Up @@ -735,6 +782,35 @@ mod tests {
assert_eq!(json.get("showResets"), Some(&serde_json::json!(true)));
}

/// #91: el entorno por defecto es host — una config antigua (sin los
/// campos nuevos) sigue resolviendo sesión del host.
#[test]
fn session_env_defaults_to_host_in_legacy_config() {
let cfg: AppConfig = serde_json::from_str(r#"{}"#).unwrap();
assert_eq!(cfg.session_env(), crate::wsl::SessionEnv::Host);
}

/// #91: `wsl` con distro elegida resuelve esa distro; `wsl` sin distro
/// todavía no es un entorno válido y se trata como host (la resolución
/// nunca inventa una distro).
#[test]
fn session_env_maps_wsl_with_distro_and_guards_empty_distro() {
let mut cfg = AppConfig {
session_env: SessionEnvConfig::Wsl,
wsl_distro: "Ubuntu-24.04".into(),
..Default::default()
};
assert_eq!(
cfg.session_env(),
crate::wsl::SessionEnv::Wsl {
distro: "Ubuntu-24.04".into()
}
);

cfg.wsl_distro = " ".into();
assert_eq!(cfg.session_env(), crate::wsl::SessionEnv::Host);
}

#[test]
fn reset_visibility_roundtrips_and_live_patch_preserves_other_fields() {
let mut cfg = AppConfig::default();
Expand Down
81 changes: 77 additions & 4 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use tauri::{
};
use tauri::{PhysicalPosition, PhysicalSize};
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
use wsl::WslAdapter;

const WIDGET: &str = "widget";
const SETTINGS: &str = "settings";
Expand Down Expand Up @@ -231,6 +232,11 @@ struct AppState {
config_path: PathBuf,
http: reqwest::Client,
history: history::History,
/// Home del default user de cada distro WSL, resuelto una sola vez
/// (el proceso de sesión se consulta cada 15 s y `resolve_home` lanza
/// dos procesos wsl.exe). Se invalida solo al cambiar la distro
/// configurada (#91, revisión del mantenedor 2026-11-02).
wsl_homes: wsl::WslHomeCache,
/// Single-flight para aperturas de login CLI en curso. Protege contra
/// doble clic en «Iniciar sesión» (HealthStep/CliConnect) y contra reenvíos
/// duplicados: mientras un `provider` esté en este set, una segunda llamada
Expand Down Expand Up @@ -506,10 +512,62 @@ fn set_secret(app: AppHandle, key: String, value: String) -> Result<(), String>
Ok(())
}

/// Resolvedor de sesiones del entorno configurado (#91): `None` en host (el
/// comportamiento de siempre, incluido el respaldo del Keychain de macOS), y
/// en WSL el resolvedor de la distro elegida, con el home de su default user
/// resuelto por el adaptador (`resolve_home`, F7).
///
/// `Err` cuando el adaptador no puede resolver el home (WSL sin instalar,
/// distro ausente): el llamador decide cómo reflejarlo; nunca se cae a una
/// cuenta de otro entorno.
///
/// `resolve_home` lanza dos procesos WSL y va en un hilo propio
/// (`spawn_blocking`), igual que las lecturas del resolvedor: nada de esto
/// bloquea el executor async (revisión PR #220). El home se resuelve UNA
/// vez por distro (`homes`) y se reutiliza en los ticks de `session_stamp`
/// (cada 15 s), para no mantener la VM de WSL activa (revisión del
/// mantenedor, 2026-11-02).
async fn session_resolver(
cfg: &AppConfig,
adapter: std::sync::Arc<wsl::RealWslAdapter>,
homes: &wsl::WslHomeCache,
) -> Result<Option<wsl::SessionResolver>, String> {
match cfg.session_env() {
wsl::SessionEnv::Host => Ok(None),
wsl::SessionEnv::Wsl { distro } => {
let home = match homes.home_for(&distro) {
Some(h) => h,
None => {
let a = adapter.clone();
let d = distro.clone();
let home = tokio::task::spawn_blocking(move || a.resolve_home(&d))
.await
.map_err(|e| format!("WSL: {e}"))?
.map_err(|e| format!("WSL: {e:?}"))?;
homes.insert(&distro, home.clone());
home
}
};
Ok(Some(wsl::SessionResolver::for_wsl(adapter, distro, home)))
}
}
}

#[tauri::command]
async fn fetch_usage(state: State<'_, AppState>) -> Result<Vec<UsageReport>, String> {
let cfg = state.config.lock().unwrap().clone();
let reports = providers::fetch_all(&cfg, &state.http).await;
let adapter = std::sync::Arc::new(wsl::RealWslAdapter::new());
let reports =
match session_resolver(&cfg, adapter.clone(), &state.wsl_homes).await {
// Host: el comportamiento de siempre (sin resolvedor).
Ok(None) => providers::fetch_all(&cfg, &state.http, None).await,
// WSL: las sesiones de los CLIs se resuelven y se leen dentro de la distro.
Ok(Some(resolver)) => providers::fetch_all(&cfg, &state.http, Some(&resolver)).await,
// El entorno configurado no se puede resolver: fallo explícito en el
// orbe, nunca una lectura silenciosa del otro entorno (#91).
Err(e) => vec![UsageReport::new("wsl", "WSL", model::Kind::Subscription)
.fail(model::Status::Error, e)],
};
state
.history
.record(&reports, chrono::Utc::now().timestamp());
Expand Down Expand Up @@ -713,10 +771,22 @@ fn overlay_done(app: AppHandle) {
}

/// Huella de los ficheros de sesión de los CLIs (ver `providers::session_stamp`).
///
/// `Result` porque es un comando `async`: Tauri exige que los commands async
/// con `State` devuelvan `Result` (el futuro vive más que el borrow).
#[tauri::command]
fn session_stamp(state: State<'_, AppState>) -> u64 {
async fn session_stamp(state: State<'_, AppState>) -> Result<u64, String> {
let cfg = state.config.lock().unwrap().clone();
providers::session_stamp(&cfg)
let adapter = std::sync::Arc::new(wsl::RealWslAdapter::new());
Ok(
match session_resolver(&cfg, adapter.clone(), &state.wsl_homes).await {
// WSL: la huella se lee dentro de la distro (misma fuente que los fetch).
Ok(resolver) => providers::session_stamp(&cfg, resolver.as_ref()).await,
// El entorno no se puede resolver: sin sello (0), el widget espera al
// próximo refresco en vez de observar cuentas de otro entorno (#91).
Err(_) => 0,
},
)
}

/// Comprueba la conexión de una cuenta (principal o adicional) con la config guardada,
Expand All @@ -727,7 +797,9 @@ async fn check_provider(
id: String,
) -> Result<Option<UsageReport>, String> {
let cfg = state.config.lock().unwrap().clone();
Ok(providers::fetch_one(&cfg, &id, &state.http).await)
let adapter = std::sync::Arc::new(wsl::RealWslAdapter::new());
let resolver = session_resolver(&cfg, adapter.clone(), &state.wsl_homes).await?;
Ok(providers::fetch_one(&cfg, &id, &state.http, resolver.as_ref()).await)
}

/// ¿Está instalado el CLI de login de Claude, Codex y Gemini?
Expand Down Expand Up @@ -1100,6 +1172,7 @@ pub fn run() {
http,
history,
login_in_flight: Mutex::new(std::collections::HashSet::new()),
wsl_homes: wsl::WslHomeCache::default(),
});
build_tray(app, locale)?;
Ok(())
Expand Down
Loading
Loading