v0.0.7 - Fixed agent login flow: browser starts normally with GTK dialog, no blocking wait. Agent server is available throughout — agent can log in at any time (during dialog or after browser is running). Agent login takes priority over dialog if both occur.

This commit is contained in:
Laan Tungir
2026-07-11 21:12:10 -04:00
parent d64bd45f54
commit fc0e412cc5
4 changed files with 52 additions and 46 deletions
+5 -3
View File
@@ -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):
+1 -1
View File
@@ -1 +1 @@
0.0.6
0.0.7
+44 -40
View File
@@ -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();
+2 -2
View File
@@ -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 */