2026-02-10 04:29:48 +01:00
// retoor <retoor@molodetz.nl>
# include "tool.h"
# include "db.h"
# include "http_client.h"
# include "agent.h"
# include "r_config.h"
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <openssl/sha.h>
# include <sqlite3.h>
extern db_handle db_open ( const char * path ) ;
extern void db_close ( db_handle db ) ;
extern r_status_t db_execute ( db_handle db , const char * sql , struct json_object * * result ) ;
static void compute_url_hash ( const char * url , char * output ) {
unsigned char hash [ SHA256_DIGEST_LENGTH ] ;
SHA256 ( ( unsigned char * ) url , strlen ( url ) , hash ) ;
for ( int i = 0 ; i < SHA256_DIGEST_LENGTH ; i + + ) {
2026-04-03 10:42:43 +02:00
snprintf ( output + ( i * 2 ) , 3 , " %02x " , hash [ i ] ) ;
2026-02-10 04:29:48 +01:00
}
output [ 64 ] = ' \0 ' ;
}
static char * research_dispatcher_execute ( tool_t * self , struct json_object * args ) {
( void ) self ;
struct json_object * urls_obj , * batch_id_obj ;
if ( ! json_object_object_get_ex ( args , " urls " , & urls_obj ) | |
! json_object_object_get_ex ( args , " batch_id " , & batch_id_obj ) ) return strdup ( " Error: missing urls or batch_id " ) ;
const char * batch_id = json_object_get_string ( batch_id_obj ) ;
db_handle db = db_open ( NULL ) ;
int count = json_object_array_length ( urls_obj ) ;
int dispatched = 0 ;
for ( int i = 0 ; i < count ; i + + ) {
const char * url = json_object_get_string ( json_object_array_get_idx ( urls_obj , i ) ) ;
char hash [ 65 ] ;
compute_url_hash ( url , hash ) ;
char * sql = sqlite3_mprintf ( " INSERT OR IGNORE INTO research_tasks (url_hash, url, status, batch_id) VALUES (%Q, %Q, 'pending', %Q) " ,
hash , url , batch_id ) ;
db_execute ( db , sql , NULL ) ;
sqlite3_free ( sql ) ;
dispatched + + ;
}
db_close ( db ) ;
char buf [ 128 ] ;
snprintf ( buf , sizeof ( buf ) , " Dispatched %d URLs to research_tasks table for batch %s. " , dispatched , batch_id ) ;
return strdup ( buf ) ;
}
static char * fetch_and_scrape_execute ( tool_t * self , struct json_object * args ) {
( void ) self ;
struct json_object * url_obj ;
if ( ! json_object_object_get_ex ( args , " url " , & url_obj ) ) return strdup ( " Error: missing url " ) ;
const char * url = json_object_get_string ( url_obj ) ;
char hash [ 65 ] ;
compute_url_hash ( url , hash ) ;
db_handle db = db_open ( NULL ) ;
// Atomic Lock / Claim
char * lock_sql = sqlite3_mprintf ( " UPDATE research_tasks SET status = 'processing', updated_at = CURRENT_TIMESTAMP WHERE url_hash = %Q AND (status = 'pending' OR (status = 'processing' AND updated_at < datetime('now', '-10 minutes'))) " , hash ) ;
db_execute ( db , lock_sql , NULL ) ;
sqlite3_free ( lock_sql ) ;
http_client_handle http = http_client_create ( NULL ) ;
char * content = NULL ;
r_status_t st = http_get ( http , url , & content ) ;
http_client_destroy ( http ) ;
if ( st ! = R_SUCCESS | | ! content ) {
char * fail_sql = sqlite3_mprintf ( " UPDATE research_tasks SET status = 'failed' WHERE url_hash = %Q " , hash ) ;
db_execute ( db , fail_sql , NULL ) ;
sqlite3_free ( fail_sql ) ;
db_close ( db ) ;
return strdup ( " Error: Failed to fetch URL content. " ) ;
}
// Truncate for summary (simplified for MVP)
char summary [ 2048 ] ;
strncpy ( summary , content , 2047 ) ;
summary [ 2047 ] = ' \0 ' ;
char * update_sql = sqlite3_mprintf ( " UPDATE research_tasks SET status = 'completed', summary = %Q, updated_at = CURRENT_TIMESTAMP WHERE url_hash = %Q " , summary , hash ) ;
db_execute ( db , update_sql , NULL ) ;
sqlite3_free ( update_sql ) ;
db_close ( db ) ;
free ( content ) ;
return strdup ( " URL fetched, scraped, and summary stored in research_tasks. " ) ;
}
static char * suggest_subtask_execute ( tool_t * self , struct json_object * args ) {
( void ) self ;
struct json_object * url_obj , * reason_obj ;
if ( ! json_object_object_get_ex ( args , " url " , & url_obj ) | |
! json_object_object_get_ex ( args , " reason " , & reason_obj ) ) return strdup ( " Error: missing url or reason " ) ;
fprintf ( stderr , " \033 [1;34m[Escalation] Worker suggests new subtask: %s (Reason: %s) \033 [0m \n " ,
json_object_get_string ( url_obj ) , json_object_get_string ( reason_obj ) ) ;
return strdup ( " Subtask suggestion logged and escalated to Manager. " ) ;
}
static struct json_object * dispatch_desc ( void ) {
struct json_object * root = json_object_new_object ( ) ;
json_object_object_add ( root , " type " , json_object_new_string ( " function " ) ) ;
struct json_object * f = json_object_new_object ( ) ;
json_object_object_add ( f , " name " , json_object_new_string ( " research_dispatcher " ) ) ;
json_object_object_add ( f , " description " , json_object_new_string ( " Manager tool: Fan-out a list of URLs into the research_tasks queue. " ) ) ;
struct json_object * p = json_object_new_object ( ) ;
json_object_object_add ( p , " type " , json_object_new_string ( " object " ) ) ;
struct json_object * props = json_object_new_object ( ) ;
struct json_object * u = json_object_new_object ( ) ;
json_object_object_add ( u , " type " , json_object_new_string ( " array " ) ) ;
json_object_object_add ( u , " items " , json_object_new_object ( ) ) ;
json_object_object_add ( json_object_object_get ( u , " items " ) , " type " , json_object_new_string ( " string " ) ) ;
json_object_object_add ( props , " urls " , u ) ;
struct json_object * b = json_object_new_object ( ) ;
json_object_object_add ( b , " type " , json_object_new_string ( " string " ) ) ;
json_object_object_add ( props , " batch_id " , b ) ;
json_object_object_add ( p , " properties " , props ) ;
struct json_object * req = json_object_new_array ( ) ;
json_object_array_add ( req , json_object_new_string ( " urls " ) ) ;
json_object_array_add ( req , json_object_new_string ( " batch_id " ) ) ;
json_object_object_add ( p , " required " , req ) ;
json_object_object_add ( p , " additionalProperties " , json_object_new_boolean ( 0 ) ) ;
json_object_object_add ( f , " parameters " , p ) ;
r_config_handle cfg = r_config_get_instance ( ) ;
if ( r_config_use_strict ( cfg ) ) json_object_object_add ( f , " strict " , json_object_new_boolean ( 1 ) ) ;
json_object_object_add ( root , " function " , f ) ;
return root ;
}
static struct json_object * fetch_desc ( void ) {
struct json_object * root = json_object_new_object ( ) ;
json_object_object_add ( root , " type " , json_object_new_string ( " function " ) ) ;
struct json_object * f = json_object_new_object ( ) ;
json_object_object_add ( f , " name " , json_object_new_string ( " fetch_and_scrape " ) ) ;
json_object_object_add ( f , " description " , json_object_new_string ( " Worker tool: Fetch a single URL, scrape it, and save the summary to the database. " ) ) ;
struct json_object * p = json_object_new_object ( ) ;
json_object_object_add ( p , " type " , json_object_new_string ( " object " ) ) ;
struct json_object * props = json_object_new_object ( ) ;
struct json_object * u = json_object_new_object ( ) ;
json_object_object_add ( u , " type " , json_object_new_string ( " string " ) ) ;
json_object_object_add ( props , " url " , u ) ;
json_object_object_add ( p , " properties " , props ) ;
struct json_object * req = json_object_new_array ( ) ;
json_object_array_add ( req , json_object_new_string ( " url " ) ) ;
json_object_object_add ( p , " required " , req ) ;
json_object_object_add ( p , " additionalProperties " , json_object_new_boolean ( 0 ) ) ;
json_object_object_add ( f , " parameters " , p ) ;
r_config_handle cfg = r_config_get_instance ( ) ;
if ( r_config_use_strict ( cfg ) ) json_object_object_add ( f , " strict " , json_object_new_boolean ( 1 ) ) ;
json_object_object_add ( root , " function " , f ) ;
return root ;
}
static struct json_object * suggest_desc ( void ) {
struct json_object * root = json_object_new_object ( ) ;
json_object_object_add ( root , " type " , json_object_new_string ( " function " ) ) ;
struct json_object * f = json_object_new_object ( ) ;
json_object_object_add ( f , " name " , json_object_new_string ( " suggest_subtask " ) ) ;
json_object_object_add ( f , " description " , json_object_new_string ( " Worker tool: Suggest a new URL discovered within a page for future research. Escalates to Manager. " ) ) ;
struct json_object * p = json_object_new_object ( ) ;
json_object_object_add ( p , " type " , json_object_new_string ( " object " ) ) ;
struct json_object * props = json_object_new_object ( ) ;
struct json_object * u = json_object_new_object ( ) ;
json_object_object_add ( u , " type " , json_object_new_string ( " string " ) ) ;
json_object_object_add ( props , " url " , u ) ;
struct json_object * r = json_object_new_object ( ) ;
json_object_object_add ( r , " type " , json_object_new_string ( " string " ) ) ;
json_object_object_add ( props , " reason " , r ) ;
json_object_object_add ( p , " properties " , props ) ;
struct json_object * req = json_object_new_array ( ) ;
json_object_array_add ( req , json_object_new_string ( " url " ) ) ;
json_object_array_add ( req , json_object_new_string ( " reason " ) ) ;
json_object_object_add ( p , " required " , req ) ;
json_object_object_add ( p , " additionalProperties " , json_object_new_boolean ( 0 ) ) ;
json_object_object_add ( f , " parameters " , p ) ;
r_config_handle cfg = r_config_get_instance ( ) ;
if ( r_config_use_strict ( cfg ) ) json_object_object_add ( f , " strict " , json_object_new_boolean ( 1 ) ) ;
json_object_object_add ( root , " function " , f ) ;
return root ;
}
static const tool_vtable_t dispatch_vtable = { . get_description = dispatch_desc , . execute = research_dispatcher_execute } ;
static const tool_vtable_t fetch_vtable = { . get_description = fetch_desc , . execute = fetch_and_scrape_execute } ;
static const tool_vtable_t suggest_vtable = { . get_description = suggest_desc , . execute = suggest_subtask_execute } ;
static tool_t dispatch_tool = { . vtable = & dispatch_vtable , . name = " research_dispatcher " } ;
static tool_t fetch_tool = { . vtable = & fetch_vtable , . name = " fetch_and_scrape " } ;
static tool_t suggest_tool = { . vtable = & suggest_vtable , . name = " suggest_subtask " } ;
tool_t * tool_research_dispatcher_create ( void ) { return & dispatch_tool ; }
tool_t * tool_fetch_and_scrape_create ( void ) { return & fetch_tool ; }
tool_t * tool_suggest_subtask_create ( void ) { return & suggest_tool ; }