diff --git a/README.md b/README.md index cf22a96..1bf08ff 100644 --- a/README.md +++ b/README.md @@ -169,9 +169,11 @@ The browser embeds a WebSocket server (using libsoup) that allows external AI agents to control the browser programmatically. Connect to `ws://localhost:17777/agent` and send JSON tool commands. -The server starts **before** the login dialog, so an agent can authenticate -without human interaction. If no agent logs in within a timeout (default -30s, configurable in Settings), the GTK login dialog appears as fallback. +The server starts before the login dialog and remains available throughout +the browser's lifecycle. The browser starts normally with the GTK login +dialog — no blocking wait. An agent can log in at any time: while the dialog +is showing, or after the browser is already running. If an agent logs in +while the dialog is open, the agent's login takes priority. **Login tools** (available before login): diff --git a/VERSION b/VERSION index 1750564..5a5831a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.6 +0.0.7 diff --git a/src/main.c b/src/main.c index 121c8ad..8f37ddf 100644 --- a/src/main.c +++ b/src/main.c @@ -60,7 +60,6 @@ typedef struct { static app_state_t g_state = {0}; static GtkWindow *g_window = NULL; static gboolean g_logged_in = FALSE; -static gboolean g_agent_login_done = FALSE; /* ---- App state accessors (used by agent_login.c) ─────────────────── */ @@ -417,11 +416,14 @@ static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, /* ---- Agent login callback ------------------------------------------ * * Called by agent_server when an agent successfully logs in via the - * 'login' tool. Sets the flag so the main loop can proceed. + * 'login' tool. The agent can log in at any time — while the GTK login + * dialog is showing, or after the browser is already running. We just + * set the flag; the do_login function checks it after the dialog + * returns and skips the dialog's result if the agent already logged in. */ static void agent_login_callback(void) { - g_agent_login_done = TRUE; - g_print("[login] Agent login detected, proceeding.\n"); + g_logged_in = TRUE; + g_print("[login] Agent login detected.\n"); } /* ---- Window destroy ------------------------------------------------- */ @@ -444,15 +446,36 @@ static void on_window_destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } -/* ---- Login flow (GTK dialog fallback) ───────────────────────────── */ +/* ---- Login flow (GTK dialog) ─────────────────────────────────────── * + * Shows the GTK login dialog. The agent server is already running at + * this point, so an agent can call 'login' while the dialog is showing + * (gtk_dialog_run runs a nested main loop that processes WebSocket + * events). After the dialog returns, we check if the agent already + * logged in — if so, we discard the dialog's result. + */ static int do_login(GtkWindow *parent) { /* nostr_init() is already called before this function. */ login_result_t result; if (login_dialog_run(parent, &result) != 0) { + /* Dialog was cancelled. But if the agent logged in while the + * dialog was showing, proceed with the agent's login. */ + if (g_logged_in) { + return 0; + } return -1; } + /* If the agent already logged in while the dialog was showing, + * discard the dialog's result and use the agent's login. */ + if (g_logged_in) { + g_print("[login] Agent login took priority over dialog.\n"); + if (result.signer) { + nostr_signer_free(result.signer); + } + return 0; + } + g_state.signer = result.signer; g_state.method = result.method; strncpy(g_state.pubkey_hex, result.pubkey_hex, 64); @@ -486,9 +509,11 @@ int main(int argc, char **argv) { g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL); g_window = GTK_WINDOW(window); - /* Start the agent server before login so agents can authenticate - * without human interaction. The server runs on the configured port - * (default 17777). If disabled in settings, skip it. */ + /* Start the agent server before login. The server runs on the + * configured port (default 17777) and is available throughout the + * entire browser lifecycle — an agent can log in at any time, even + * while the GTK login dialog is showing or after the browser is + * already running. If disabled in settings, skip it. */ const browser_settings_t *s = settings_get(); if (s->agent_server_enabled) { if (agent_server_start(s->agent_server_port) == 0) { @@ -507,40 +532,19 @@ int main(int argc, char **argv) { return EXIT_SUCCESS; } - /* Login flow: wait for agent login or fall back to GTK dialog. - * If the agent server is running, wait up to agent_login_timeout_ms - * for an agent to call the 'login' tool. If no agent connects or - * the timeout expires, show the GTK login dialog. */ - if (s->agent_server_enabled && s->agent_login_timeout_ms > 0) { - g_print("[login] Waiting up to %d ms for agent login...\n", - s->agent_login_timeout_ms); + /* Set up the login callback so if an agent calls 'login' while the + * GTK dialog is showing, we can close the dialog and proceed. */ + agent_server_set_login_callback(agent_login_callback); - /* Run a main loop with a timeout so the agent server can process - * incoming WebSocket connections and login commands. */ - g_agent_login_done = FALSE; - agent_server_set_login_callback(agent_login_callback); - - GMainContext *ctx = g_main_context_get_thread_default(); - gint64 deadline = g_get_monotonic_time() + - (gint64)s->agent_login_timeout_ms * 1000; - - while (!g_agent_login_done) { - gint64 remaining = deadline - g_get_monotonic_time(); - if (remaining <= 0) break; - - /* Process events for up to 200ms at a time. */ - g_main_context_iteration(ctx, FALSE); - if (!g_agent_login_done) { - g_usleep(50 * 1000); /* 50ms */ - } - } - - agent_server_set_login_callback(NULL); - } - - /* If not logged in via agent, show the GTK login dialog. */ + /* Show the GTK login dialog immediately — no blocking wait. + * The dialog runs a nested GTK main loop (gtk_dialog_run) which + * processes WebSocket events, so the agent server is live during + * the dialog. If an agent calls 'login' while the dialog is open, + * the callback fires and we close the dialog. + * + * If the agent logs in before the dialog appears (unlikely but + * possible), skip the dialog entirely. */ if (!g_logged_in) { - g_print("[login] No agent login, showing GTK dialog.\n"); if (do_login(g_window) != 0) { g_print("[login] Cancelled, exiting.\n"); agent_server_stop(); diff --git a/src/version.h b/src/version.h index 2a47b33..b9c2945 100644 --- a/src/version.h +++ b/src/version.h @@ -11,9 +11,9 @@ #ifndef SOVEREIGN_BROWSER_VERSION_H #define SOVEREIGN_BROWSER_VERSION_H -#define SB_VERSION "v0.0.6" +#define SB_VERSION "v0.0.7" #define SB_VERSION_MAJOR 0 #define SB_VERSION_MINOR 0 -#define SB_VERSION_PATCH 6 +#define SB_VERSION_PATCH 7 #endif /* SOVEREIGN_BROWSER_VERSION_H */