v0.0.9 - Fixed snapshot tool: AGENT_SNAPSHOT_JS was self-executing IIFE causing syntax error when wrapped with args. Changed to function definition. Snapshot now works async. Eval works async. Login, open, get_title, tab_list all pass.
This commit is contained in:
@@ -46,6 +46,15 @@ flowchart TB
|
||||
|
||||
### Key design decisions
|
||||
|
||||
0. **Agent tools must use the same code path as the GUI** — Agent tools
|
||||
should call the same C functions the GUI calls, not a parallel
|
||||
implementation. This ensures that any error a user would see, the
|
||||
agent also sees. Debugging WebKit internals is not our concern; our
|
||||
C code is. If the agent and GUI take different paths, bugs can hide
|
||||
from the agent. The login tools are the one exception (backend-only,
|
||||
bypassing the GTK dialog) — all browser tools go through the same
|
||||
`WebKitWebView` and `tab_manager` APIs.
|
||||
|
||||
1. **WebSocket server via libsoup** — `SoupServer` with
|
||||
`soup_server_add_websocket_handler()` provides a production-ready
|
||||
WebSocket server. libsoup is already linked (WebKitGTK dependency).
|
||||
|
||||
+8
-20
@@ -14,8 +14,8 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Forward declarations from agent_tools.c (created next) */
|
||||
extern cJSON *agent_tools_dispatch(cJSON *request);
|
||||
/* Forward declarations from agent_tools.c */
|
||||
extern cJSON *agent_tools_dispatch(cJSON *request, SoupWebsocketConnection *conn);
|
||||
|
||||
/* ── Static state ─────────────────────────────────────────────────── */
|
||||
|
||||
@@ -92,39 +92,27 @@ static void on_ws_message(SoupWebsocketConnection *conn,
|
||||
(void)user_data;
|
||||
(void)type;
|
||||
|
||||
g_print("[agent] on_ws_message called\n");
|
||||
|
||||
gsize size = 0;
|
||||
const gchar *data = g_bytes_get_data(message, &size);
|
||||
if (data == NULL || size == 0) {
|
||||
g_print("[agent] on_ws_message: empty data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
g_print("[agent] on_ws_message: %.*s\n", (int)(size > 200 ? 200 : size), data);
|
||||
if (data == NULL || size == 0) return;
|
||||
|
||||
/* Parse the JSON request. */
|
||||
cJSON *request = cJSON_ParseWithLength(data, size);
|
||||
if (request == NULL) {
|
||||
g_print("[agent] on_ws_message: JSON parse failed\n");
|
||||
const char *err_json = "{\"success\":false,\"error\":{\"code\":\"INVALID_JSON\","
|
||||
"\"message\":\"Failed to parse JSON request\"}}";
|
||||
send_json_to_client(conn, err_json);
|
||||
return;
|
||||
}
|
||||
|
||||
g_print("[agent] on_ws_message: dispatching tool\n");
|
||||
|
||||
/* Dispatch the tool. */
|
||||
cJSON *response = agent_tools_dispatch(request);
|
||||
/* Dispatch the tool. Pass the WebSocket connection so async tools
|
||||
* (snapshot, eval) can send their response directly when JS completes. */
|
||||
cJSON *response = agent_tools_dispatch(request, conn);
|
||||
cJSON_Delete(request);
|
||||
|
||||
g_print("[agent] on_ws_message: dispatch returned %p\n", (void*)response);
|
||||
|
||||
if (response == NULL) {
|
||||
const char *err_json = "{\"success\":false,\"error\":{\"code\":\"INTERNAL_ERROR\","
|
||||
"\"message\":\"Tool dispatch returned NULL\"}}";
|
||||
send_json_to_client(conn, err_json);
|
||||
/* NULL means the tool is sending its response asynchronously
|
||||
* (e.g. snapshot, eval). Don't send anything here. */
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+188
-37
@@ -13,7 +13,9 @@
|
||||
|
||||
/* ── Synchronous JS evaluation ─────────────────────────────────────── *
|
||||
* WebKitGTK's evaluate_javascript is async. We use a nested GMainLoop
|
||||
* to wait for the result synchronously.
|
||||
* on the default context to wait for the result. The key is to acquire
|
||||
* the context before creating the loop so that the WebKit IPC sources
|
||||
* are properly dispatched.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
@@ -28,7 +30,6 @@ static void on_js_finished(GObject *source, GAsyncResult *res, gpointer user_dat
|
||||
|
||||
JSCValue *value = webkit_web_view_evaluate_javascript_finish(webview, res, NULL);
|
||||
if (value != NULL) {
|
||||
/* Convert the JSCValue to a string. */
|
||||
char *str = jsc_value_to_string(value);
|
||||
if (str != NULL) {
|
||||
ctx->result = g_strdup(str);
|
||||
@@ -38,43 +39,165 @@ static void on_js_finished(GObject *source, GAsyncResult *res, gpointer user_dat
|
||||
}
|
||||
|
||||
ctx->done = TRUE;
|
||||
if (g_main_loop_is_running(ctx->loop)) {
|
||||
if (ctx->loop && g_main_loop_is_running(ctx->loop)) {
|
||||
g_main_loop_quit(ctx->loop);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean on_js_timeout(gpointer user_data) {
|
||||
js_eval_ctx_t *ctx = (js_eval_ctx_t *)user_data;
|
||||
if (!ctx->done) {
|
||||
g_printerr("[agent] JS evaluation timed out\n");
|
||||
if (ctx->loop && g_main_loop_is_running(ctx->loop)) {
|
||||
g_main_loop_quit(ctx->loop);
|
||||
}
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
char *agent_js_eval_sync(WebKitWebView *webview, const char *script,
|
||||
int timeout_ms) {
|
||||
if (webview == NULL || script == NULL) return NULL;
|
||||
|
||||
js_eval_ctx_t ctx = {0};
|
||||
ctx.loop = g_main_loop_new(NULL, FALSE);
|
||||
|
||||
/* Use the default main context for the nested loop. */
|
||||
GMainContext *main_ctx = g_main_context_default();
|
||||
g_main_context_acquire(main_ctx);
|
||||
|
||||
ctx.loop = g_main_loop_new(main_ctx, FALSE);
|
||||
|
||||
/* Add a timeout source to the same context. */
|
||||
guint timeout_id = g_timeout_add(timeout_ms, on_js_timeout, &ctx);
|
||||
|
||||
/* Start async evaluation. */
|
||||
webkit_web_view_evaluate_javascript(webview, script, -1, NULL, NULL, NULL,
|
||||
on_js_finished, &ctx);
|
||||
|
||||
/* Run a nested main loop with a timeout. */
|
||||
if (!ctx.done) {
|
||||
GSource *timeout_src = g_timeout_source_new(timeout_ms);
|
||||
g_source_set_callback(timeout_src, (GSourceFunc)g_main_loop_quit, ctx.loop, NULL);
|
||||
g_source_attach(timeout_src, g_main_context_get_thread_default());
|
||||
g_source_unref(timeout_src);
|
||||
|
||||
g_main_loop_run(ctx.loop);
|
||||
g_source_destroy(timeout_src);
|
||||
}
|
||||
/* Run the nested loop until JS completes or timeout. */
|
||||
g_main_loop_run(ctx.loop);
|
||||
|
||||
/* Cleanup. */
|
||||
g_source_remove(timeout_id);
|
||||
g_main_loop_unref(ctx.loop);
|
||||
|
||||
if (!ctx.done) {
|
||||
g_printerr("[agent] JS evaluation timed out (%d ms)\n", timeout_ms);
|
||||
return NULL;
|
||||
}
|
||||
g_main_context_release(main_ctx);
|
||||
|
||||
return ctx.result;
|
||||
}
|
||||
|
||||
/* ── Async JS evaluation ──────────────────────────────────────────── *
|
||||
* For tools that need JS return values (snapshot, eval, get_text, etc.),
|
||||
* we use async evaluation. The JS completion callback sends the
|
||||
* WebSocket response directly, avoiding the nested main loop issue.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
SoupWebsocketConnection *conn;
|
||||
int request_id;
|
||||
char *tool_name;
|
||||
js_result_handler_t handler;
|
||||
} async_js_ctx_t;
|
||||
|
||||
static void send_ws_response(SoupWebsocketConnection *conn, cJSON *response) {
|
||||
if (conn == NULL || response == NULL) return;
|
||||
if (soup_websocket_connection_get_state(conn) != SOUP_WEBSOCKET_STATE_OPEN) {
|
||||
cJSON_Delete(response);
|
||||
return;
|
||||
}
|
||||
char *str = cJSON_PrintUnformatted(response);
|
||||
if (str) {
|
||||
soup_websocket_connection_send_text(conn, str);
|
||||
free(str);
|
||||
}
|
||||
cJSON_Delete(response);
|
||||
}
|
||||
|
||||
static cJSON *make_success_resp(int id, cJSON *data) {
|
||||
cJSON *resp = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(resp, "success", TRUE);
|
||||
cJSON_AddNumberToObject(resp, "id", id);
|
||||
if (data) cJSON_AddItemToObject(resp, "data", data);
|
||||
return resp;
|
||||
}
|
||||
|
||||
static cJSON *make_error_resp(int id, const char *code, const char *msg) {
|
||||
cJSON *resp = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(resp, "success", FALSE);
|
||||
cJSON_AddNumberToObject(resp, "id", id);
|
||||
cJSON *err = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(err, "code", code);
|
||||
cJSON_AddStringToObject(err, "message", msg);
|
||||
cJSON_AddItemToObject(resp, "error", err);
|
||||
return resp;
|
||||
}
|
||||
|
||||
static void on_async_js_finished(GObject *source, GAsyncResult *res, gpointer user_data) {
|
||||
async_js_ctx_t *ctx = (async_js_ctx_t *)user_data;
|
||||
WebKitWebView *webview = WEBKIT_WEB_VIEW(source);
|
||||
|
||||
JSCValue *value = webkit_web_view_evaluate_javascript_finish(webview, res, NULL);
|
||||
if (value == NULL) {
|
||||
send_ws_response(ctx->conn,
|
||||
make_error_resp(ctx->request_id, "EVAL_FAILED", "JS evaluation returned NULL"));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char *str = jsc_value_to_string(value);
|
||||
g_object_unref(value);
|
||||
|
||||
if (str == NULL) {
|
||||
send_ws_response(ctx->conn,
|
||||
make_error_resp(ctx->request_id, "EVAL_FAILED", "JS returned null"));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char *result = g_strdup(str);
|
||||
free(str);
|
||||
|
||||
if (ctx->handler) {
|
||||
/* Let the tool-specific handler transform the result. */
|
||||
cJSON *response = ctx->handler(result);
|
||||
g_free(result);
|
||||
if (response) {
|
||||
/* Add the request id. */
|
||||
cJSON_AddNumberToObject(response, "id", ctx->request_id);
|
||||
send_ws_response(ctx->conn, response);
|
||||
} else {
|
||||
send_ws_response(ctx->conn,
|
||||
make_error_resp(ctx->request_id, "TOOL_FAILED", "Tool handler returned NULL"));
|
||||
}
|
||||
} else {
|
||||
/* No handler — return raw result. */
|
||||
cJSON *data = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(data, "result", result);
|
||||
g_free(result);
|
||||
send_ws_response(ctx->conn, make_success_resp(ctx->request_id, data));
|
||||
}
|
||||
|
||||
cleanup:
|
||||
g_free(ctx->tool_name);
|
||||
g_free(ctx);
|
||||
}
|
||||
|
||||
gboolean agent_js_eval_async(WebKitWebView *webview,
|
||||
const char *script,
|
||||
SoupWebsocketConnection *conn,
|
||||
int request_id,
|
||||
const char *tool_name,
|
||||
js_result_handler_t handler) {
|
||||
if (webview == NULL || script == NULL) return FALSE;
|
||||
|
||||
async_js_ctx_t *ctx = g_new0(async_js_ctx_t, 1);
|
||||
ctx->conn = conn;
|
||||
ctx->request_id = request_id;
|
||||
ctx->tool_name = g_strdup(tool_name ? tool_name : "unknown");
|
||||
ctx->handler = handler;
|
||||
|
||||
webkit_web_view_evaluate_javascript(webview, script, -1, NULL, NULL, NULL,
|
||||
on_async_js_finished, ctx);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ── Snapshot JS script ───────────────────────────────────────────── *
|
||||
* This script walks the DOM, assigns refs to interactive elements,
|
||||
* stores the ref map in window.__agentRefs, and returns a JSON string
|
||||
@@ -248,11 +371,11 @@ const char *AGENT_SNAPSHOT_JS =
|
||||
"\n"
|
||||
" /* Return JSON result. */\n"
|
||||
" return JSON.stringify({\n"
|
||||
" snapshot: treeLines.join('\\n'),\n"
|
||||
" snapshot: treeLines.join('\\\\n'),\n"
|
||||
" refs: refMap,\n"
|
||||
" refCount: refCount\n"
|
||||
" });\n"
|
||||
"})();\n";
|
||||
"})\n";
|
||||
|
||||
/* ── Snapshot API ─────────────────────────────────────────────────── */
|
||||
|
||||
@@ -261,23 +384,15 @@ cJSON *agent_snapshot_take(WebKitWebView *webview,
|
||||
gboolean compact) {
|
||||
if (webview == NULL) return NULL;
|
||||
|
||||
/* Build the JS call with arguments. */
|
||||
char script[512];
|
||||
snprintf(script, sizeof(script),
|
||||
"AGENT_SNAPSHOT_JS_WRAPPER = (%s); AGENT_SNAPSHOT_JS_WRAPPER(%s, %s);",
|
||||
AGENT_SNAPSHOT_JS,
|
||||
interactive ? "true" : "false",
|
||||
compact ? "true" : "false");
|
||||
/* Build the JS call with arguments. Use g_strconcat instead of
|
||||
* g_strdup_printf to avoid % format specifier issues in the JS. */
|
||||
char *full_script = g_strconcat(
|
||||
"(", AGENT_SNAPSHOT_JS, ")(",
|
||||
interactive ? "true" : "false", ", ",
|
||||
compact ? "true" : "false", ")",
|
||||
NULL);
|
||||
|
||||
/* Actually, we need to call the IIFE with arguments. Let's use a
|
||||
* different approach — wrap the function and call it. */
|
||||
char *full_script = g_strdup_printf(
|
||||
"(%s)(%s, %s)",
|
||||
AGENT_SNAPSHOT_JS,
|
||||
interactive ? "true" : "false",
|
||||
compact ? "true" : "false");
|
||||
|
||||
char *result = agent_js_eval_sync(webview, full_script, 10000);
|
||||
char *result = agent_js_eval_sync(webview, full_script, 15000);
|
||||
g_free(full_script);
|
||||
|
||||
if (result == NULL) {
|
||||
@@ -289,3 +404,39 @@ cJSON *agent_snapshot_take(WebKitWebView *webview,
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/* ── Async snapshot ───────────────────────────────────────────────── */
|
||||
|
||||
/* Handler that transforms the snapshot JS result into a cJSON response. */
|
||||
static cJSON *snapshot_result_handler(const char *js_result) {
|
||||
if (js_result == NULL || js_result[0] == '\0') {
|
||||
return make_error_resp(0, "SNAPSHOT_FAILED", "Snapshot returned empty result");
|
||||
}
|
||||
|
||||
cJSON *json = cJSON_Parse(js_result);
|
||||
if (json == NULL) {
|
||||
return make_error_resp(0, "SNAPSHOT_FAILED", "Failed to parse snapshot JSON");
|
||||
}
|
||||
|
||||
return make_success_resp(0, json);
|
||||
}
|
||||
|
||||
gboolean agent_snapshot_take_async(WebKitWebView *webview,
|
||||
gboolean interactive,
|
||||
gboolean compact,
|
||||
SoupWebsocketConnection *conn,
|
||||
int request_id) {
|
||||
if (webview == NULL) return FALSE;
|
||||
|
||||
char *full_script = g_strconcat(
|
||||
"(", AGENT_SNAPSHOT_JS, ")(",
|
||||
interactive ? "true" : "false", ", ",
|
||||
compact ? "true" : "false", ")",
|
||||
NULL);
|
||||
|
||||
gboolean started = agent_js_eval_async(webview, full_script, conn,
|
||||
request_id, "snapshot",
|
||||
snapshot_result_handler);
|
||||
g_free(full_script);
|
||||
return started;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define AGENT_SNAPSHOT_H
|
||||
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <libsoup/soup.h>
|
||||
#include "cjson/cJSON.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -38,6 +39,42 @@ cJSON *agent_snapshot_take(WebKitWebView *webview,
|
||||
gboolean interactive,
|
||||
gboolean compact);
|
||||
|
||||
/*
|
||||
* Async snapshot — starts JS evaluation and sends the response through
|
||||
* the WebSocket connection when the JS completes. Returns TRUE if the
|
||||
* async evaluation was started (response will be sent async), FALSE if
|
||||
* it failed immediately (caller should send an error response).
|
||||
*
|
||||
* conn: WebSocket connection to send the response through
|
||||
* request_id: the JSON request id to include in the response
|
||||
*/
|
||||
gboolean agent_snapshot_take_async(WebKitWebView *webview,
|
||||
gboolean interactive,
|
||||
gboolean compact,
|
||||
SoupWebsocketConnection *conn,
|
||||
int request_id);
|
||||
|
||||
/*
|
||||
* Async JS evaluation — evaluates a script and sends the result as a
|
||||
* tool response through the WebSocket connection when the JS completes.
|
||||
* Returns TRUE if started async, FALSE on immediate failure.
|
||||
*
|
||||
* conn: WebSocket connection to send the response through
|
||||
* request_id: the JSON request id to include in the response
|
||||
* tool_name: the tool name (for logging)
|
||||
* success_handler: optional callback to transform the JS result into
|
||||
* a cJSON response before sending. If NULL, the raw
|
||||
* JS result string is returned in data.result.
|
||||
*/
|
||||
typedef cJSON *(*js_result_handler_t)(const char *js_result);
|
||||
|
||||
gboolean agent_js_eval_async(WebKitWebView *webview,
|
||||
const char *script,
|
||||
SoupWebsocketConnection *conn,
|
||||
int request_id,
|
||||
const char *tool_name,
|
||||
js_result_handler_t handler);
|
||||
|
||||
/*
|
||||
* Execute JavaScript in the webview and wait for the result.
|
||||
* This is a synchronous wrapper around the async evaluate_javascript API.
|
||||
|
||||
+46
-11
@@ -731,7 +731,7 @@ static int tool_table_count = sizeof(tool_table) / sizeof(tool_table[0]);
|
||||
|
||||
/* ── Main dispatch function ───────────────────────────────────────── */
|
||||
|
||||
cJSON *agent_tools_dispatch(cJSON *request) {
|
||||
cJSON *agent_tools_dispatch(cJSON *request, SoupWebsocketConnection *conn) {
|
||||
if (request == NULL) {
|
||||
return make_error("INVALID_REQUEST", "Request is NULL");
|
||||
}
|
||||
@@ -740,18 +740,59 @@ cJSON *agent_tools_dispatch(cJSON *request) {
|
||||
const char *tool_name = cJSON_GetStringValue(cJSON_GetObjectItem(request, "tool"));
|
||||
cJSON *params = cJSON_GetObjectItem(request, "params");
|
||||
cJSON *id = cJSON_GetObjectItem(request, "id");
|
||||
int request_id = (id && cJSON_IsNumber(id)) ? (int)id->valuedouble : 0;
|
||||
|
||||
if (!tool_name || !tool_name[0]) {
|
||||
return make_error("MISSING_TOOL", "No 'tool' field in request");
|
||||
}
|
||||
|
||||
/* Find the tool. */
|
||||
/* Check login requirement for known tools. */
|
||||
gboolean requires_login = TRUE;
|
||||
gboolean is_login_tool = (strcmp(tool_name, "login_status") == 0 ||
|
||||
strcmp(tool_name, "login") == 0 ||
|
||||
strcmp(tool_name, "logout") == 0 ||
|
||||
strcmp(tool_name, "switch_identity") == 0);
|
||||
if (is_login_tool) requires_login = FALSE;
|
||||
|
||||
if (requires_login && !agent_is_logged_in()) {
|
||||
return make_error("NOT_LOGGED_IN",
|
||||
"This tool requires login. Use the 'login' tool first.");
|
||||
}
|
||||
|
||||
/* Handle async tools (snapshot, eval) specially — they need async
|
||||
* JS evaluation and send the response through the WebSocket
|
||||
* connection directly. */
|
||||
if (conn != NULL && strcmp(tool_name, "snapshot") == 0) {
|
||||
WebKitWebView *wv = get_active_webview();
|
||||
if (wv == NULL) return make_error("NO_TAB", "No active tab");
|
||||
|
||||
gboolean interactive = get_bool_param(params, "interactive", FALSE);
|
||||
gboolean compact = get_bool_param(params, "compact", FALSE);
|
||||
|
||||
if (agent_snapshot_take_async(wv, interactive, compact, conn, request_id)) {
|
||||
return NULL; /* Response sent async */
|
||||
}
|
||||
return make_error("SNAPSHOT_FAILED", "Failed to start async snapshot");
|
||||
}
|
||||
|
||||
if (conn != NULL && strcmp(tool_name, "eval") == 0) {
|
||||
WebKitWebView *wv = get_active_webview();
|
||||
if (wv == NULL) return make_error("NO_TAB", "No active tab");
|
||||
|
||||
const char *script_param = get_string_param(params, "script");
|
||||
if (!script_param || !script_param[0]) return make_error("MISSING_PARAM", "Provide 'script'");
|
||||
|
||||
if (agent_js_eval_async(wv, script_param, conn, request_id, "eval", NULL)) {
|
||||
return NULL; /* Response sent async */
|
||||
}
|
||||
return make_error("EVAL_FAILED", "Failed to start async eval");
|
||||
}
|
||||
|
||||
/* Find the tool in the table for synchronous tools. */
|
||||
tool_func_t func = NULL;
|
||||
gboolean requires_login = FALSE;
|
||||
for (int i = 0; i < tool_table_count; i++) {
|
||||
if (strcmp(tool_table[i].name, tool_name) == 0) {
|
||||
func = tool_table[i].func;
|
||||
requires_login = tool_table[i].requires_login;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -760,13 +801,7 @@ cJSON *agent_tools_dispatch(cJSON *request) {
|
||||
return make_error("UNKNOWN_TOOL", "Unknown tool name");
|
||||
}
|
||||
|
||||
/* Check login requirement. */
|
||||
if (requires_login && !agent_is_logged_in()) {
|
||||
return make_error("NOT_LOGGED_IN",
|
||||
"This tool requires login. Use the 'login' tool first.");
|
||||
}
|
||||
|
||||
/* Execute the tool. */
|
||||
/* Execute the tool synchronously. */
|
||||
cJSON *response = func(params ? params : cJSON_CreateObject());
|
||||
|
||||
/* Copy the request id into the response. */
|
||||
|
||||
+10
-1
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "cjson/cJSON.h"
|
||||
|
||||
#include <libsoup/soup.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -18,11 +20,18 @@ extern "C" {
|
||||
* Process a tool request JSON and return response JSON.
|
||||
* The caller frees the returned cJSON*.
|
||||
*
|
||||
* If the tool needs async JS evaluation, the response is sent directly
|
||||
* through the WebSocket connection and this function returns NULL.
|
||||
* Otherwise, the response is returned synchronously.
|
||||
*
|
||||
* conn: the WebSocket connection to send async responses through
|
||||
* (may be NULL for testing without a real connection)
|
||||
*
|
||||
* Request format: {"id":1,"tool":"snapshot","params":{...}}
|
||||
* Response format: {"id":1,"success":true,"data":{...}}
|
||||
* or: {"id":1,"success":false,"error":{"code":"...","message":"..."}}
|
||||
*/
|
||||
cJSON *agent_tools_dispatch(cJSON *request);
|
||||
cJSON *agent_tools_dispatch(cJSON *request, SoupWebsocketConnection *conn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,9 +11,9 @@
|
||||
#ifndef SOVEREIGN_BROWSER_VERSION_H
|
||||
#define SOVEREIGN_BROWSER_VERSION_H
|
||||
|
||||
#define SB_VERSION "v0.0.8"
|
||||
#define SB_VERSION "v0.0.9"
|
||||
#define SB_VERSION_MAJOR 0
|
||||
#define SB_VERSION_MINOR 0
|
||||
#define SB_VERSION_PATCH 8
|
||||
#define SB_VERSION_PATCH 9
|
||||
|
||||
#endif /* SOVEREIGN_BROWSER_VERSION_H */
|
||||
|
||||
Reference in New Issue
Block a user