Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31439652aa | ||
|
|
ad99f74bc0 | ||
|
|
cf9c300fdf | ||
|
|
97585890b9 | ||
|
|
7b3d36a797 | ||
|
|
49579b17a4 | ||
|
|
17083de47d | ||
|
|
33884046af | ||
|
|
7e69819be5 |
@@ -51,11 +51,11 @@ Agents learn capabilities through skills — Nostr events that any agent can di
|
||||
|
||||
Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers.
|
||||
|
||||
## Current Status — v0.0.35
|
||||
## Current Status — v0.0.41
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.0.35 — Improve NIP-17 send reliability with re-entrancy guard, auth handshake retry window, and send-path debug logs
|
||||
> Last release update: v0.0.41 — Fix startup relay-state log snapshot before main loop
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
|
||||
+3722
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -35,7 +35,7 @@ static pthread_mutex_t g_context_part_names_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
#define AGENT_DEBOUNCE_CACHE_SIZE 256
|
||||
#define AGENT_HISTORY_TURNS 12
|
||||
#define AGENT_HISTORY_QUERY_LIMIT 200
|
||||
#define AGENT_ADOPTED_SKILLS_CACHE_TTL_SECONDS 30
|
||||
#define AGENT_ADOPTED_SKILLS_CACHE_TTL_SECONDS 300
|
||||
#define AGENT_ADOPTED_SKILLS_MAX 24
|
||||
#define AGENT_ADOPTED_SKILL_CONTENT_MAX_CHARS 1800
|
||||
#define AGENT_ADOPTED_SKILLS_TOTAL_MAX_CHARS 7200
|
||||
@@ -1415,7 +1415,7 @@ static int append_recent_admin_dm_history(cJSON* messages, const char* current_m
|
||||
cJSON_AddItemToObject(filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(filter, "limit", AGENT_HISTORY_QUERY_LIMIT);
|
||||
|
||||
char* events_json = nostr_handler_query_json(filter, 8000);
|
||||
char* events_json = nostr_handler_query_json(filter, 1500);
|
||||
cJSON_Delete(filter);
|
||||
if (!events_json) {
|
||||
return 0;
|
||||
@@ -1648,7 +1648,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
cJSON_AddItemToObject(adopt_filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(adopt_filter, "limit", 1);
|
||||
|
||||
char* adoption_json = nostr_handler_query_json(adopt_filter, 8000);
|
||||
char* adoption_json = nostr_handler_query_json(adopt_filter, 2000);
|
||||
cJSON_Delete(adopt_filter);
|
||||
|
||||
cJSON* adoption_events = adoption_json ? cJSON_Parse(adoption_json) : NULL;
|
||||
@@ -1700,7 +1700,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
cJSON_AddItemToObject(skill_filter, "#d", d_values);
|
||||
cJSON_AddNumberToObject(skill_filter, "limit", 1);
|
||||
|
||||
char* skill_json = nostr_handler_query_json(skill_filter, 8000);
|
||||
char* skill_json = nostr_handler_query_json(skill_filter, 2000);
|
||||
cJSON_Delete(skill_filter);
|
||||
if (!skill_json) {
|
||||
continue;
|
||||
|
||||
@@ -318,6 +318,8 @@ int main(int argc, char** argv) {
|
||||
DEBUG_INFO("[didactyl] HTTP API disabled (set api.enabled=true in config to enable)");
|
||||
}
|
||||
|
||||
nostr_handler_refresh_relay_statuses();
|
||||
|
||||
DEBUG_INFO("[didactyl] entering main poll loop");
|
||||
DEBUG_INFO("[didactyl] running with pubkey %s", cfg.keys.public_key_hex);
|
||||
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@
|
||||
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
||||
#define DIDACTYL_VERSION_MAJOR 0
|
||||
#define DIDACTYL_VERSION_MINOR 0
|
||||
#define DIDACTYL_VERSION_PATCH 35
|
||||
#define DIDACTYL_VERSION "v0.0.35"
|
||||
#define DIDACTYL_VERSION_PATCH 41
|
||||
#define DIDACTYL_VERSION "v0.0.41"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
+57
-134
@@ -218,10 +218,19 @@ static void log_relay_state_changes(void) {
|
||||
nostr_pool_relay_status_t now = nostr_relay_pool_get_relay_status(g_pool, relay);
|
||||
nostr_pool_relay_status_t prev = g_last_relay_statuses[i];
|
||||
if (now != prev) {
|
||||
DEBUG_INFO("[didactyl] relay state changed: %s %s -> %s",
|
||||
relay,
|
||||
relay_status_str(prev),
|
||||
relay_status_str(now));
|
||||
const char* conn_err = nostr_relay_pool_get_relay_last_connection_error(g_pool, relay);
|
||||
if (conn_err && (now == NOSTR_POOL_RELAY_DISCONNECTED || now == NOSTR_POOL_RELAY_ERROR)) {
|
||||
DEBUG_INFO("[didactyl] relay state changed: %s %s -> %s (cause: %s)",
|
||||
relay,
|
||||
relay_status_str(prev),
|
||||
relay_status_str(now),
|
||||
conn_err);
|
||||
} else {
|
||||
DEBUG_INFO("[didactyl] relay state changed: %s %s -> %s",
|
||||
relay,
|
||||
relay_status_str(prev),
|
||||
relay_status_str(now));
|
||||
}
|
||||
g_last_relay_statuses[i] = now;
|
||||
|
||||
if (now == NOSTR_POOL_RELAY_CONNECTED) {
|
||||
@@ -231,6 +240,16 @@ static void log_relay_state_changes(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void nostr_handler_refresh_relay_statuses(void) {
|
||||
if (!g_pool || !g_cfg || !g_last_relay_statuses) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < g_cfg->relay_count; i++) {
|
||||
g_last_relay_statuses[i] = nostr_relay_pool_get_relay_status(g_pool, g_cfg->relays[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void log_publish_targets(const char* action) {
|
||||
if (!g_cfg) {
|
||||
return;
|
||||
@@ -1139,7 +1158,7 @@ int nostr_handler_subscribe_dms(dm_callback_t callback, void* user_data) {
|
||||
on_eose,
|
||||
NULL,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
NOSTR_POOL_EOSE_FULL_SET,
|
||||
30,
|
||||
120);
|
||||
@@ -1190,7 +1209,7 @@ int nostr_handler_subscribe_dms(dm_callback_t callback, void* user_data) {
|
||||
on_eose,
|
||||
NULL,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
NOSTR_POOL_EOSE_FULL_SET,
|
||||
30,
|
||||
120);
|
||||
@@ -1269,21 +1288,6 @@ int nostr_handler_send_dm(const char* recipient_pubkey_hex, const char* message)
|
||||
}
|
||||
}
|
||||
|
||||
int* pre_publish_ok = NULL;
|
||||
if (connected_count > 0) {
|
||||
pre_publish_ok = (int*)calloc((size_t)connected_count, sizeof(int));
|
||||
if (!pre_publish_ok) {
|
||||
free(connected_relays);
|
||||
cJSON_Delete(event);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < connected_count; i++) {
|
||||
const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(g_pool, connected_relays[i]);
|
||||
pre_publish_ok[i] = stats ? stats->events_published_ok : 0;
|
||||
}
|
||||
}
|
||||
|
||||
int sent = 0;
|
||||
if (connected_count > 0) {
|
||||
sent = nostr_relay_pool_publish_async(
|
||||
@@ -1297,48 +1301,6 @@ int nostr_handler_send_dm(const char* recipient_pubkey_hex, const char* message)
|
||||
for (int i = 0; i < connected_count; i++) {
|
||||
DEBUG_INFO("[didactyl] kind 4 event published to %s (async)", connected_relays[i]);
|
||||
}
|
||||
|
||||
// Briefly drain relay messages so NIP-42 AUTH handshake can complete.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
(void)nostr_relay_pool_poll(g_pool, 100);
|
||||
}
|
||||
|
||||
int any_publish_ok = 0;
|
||||
int auth_required_seen = 0;
|
||||
for (int i = 0; i < connected_count; i++) {
|
||||
const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(g_pool, connected_relays[i]);
|
||||
if (stats && stats->events_published_ok > pre_publish_ok[i]) {
|
||||
any_publish_ok = 1;
|
||||
}
|
||||
|
||||
const char* pub_err = nostr_relay_pool_get_relay_last_publish_error(g_pool, connected_relays[i]);
|
||||
if (pub_err && strstr(pub_err, "auth-required") != NULL) {
|
||||
auth_required_seen = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Always retry this same signed event once after handshake drain.
|
||||
// Some relays issue AUTH challenge for the first EVENT and only accept
|
||||
// the subsequent resend after AUTH succeeds.
|
||||
if (sent > 0) {
|
||||
if (auth_required_seen || !any_publish_ok) {
|
||||
DEBUG_WARN("[didactyl] retrying kind 4 event once after auth handshake window");
|
||||
}
|
||||
int resent = nostr_relay_pool_publish_async(
|
||||
g_pool,
|
||||
connected_relays,
|
||||
connected_count,
|
||||
event,
|
||||
NULL,
|
||||
NULL);
|
||||
if (resent > sent) {
|
||||
sent = resent;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
(void)nostr_relay_pool_poll(g_pool, 100);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_WARN("[didactyl] kind 4 event not queued: no connected relays");
|
||||
}
|
||||
@@ -1351,7 +1313,6 @@ int nostr_handler_send_dm(const char* recipient_pubkey_hex, const char* message)
|
||||
recipient_pubkey_hex,
|
||||
sent);
|
||||
|
||||
free(pre_publish_ok);
|
||||
free(connected_relays);
|
||||
cJSON_Delete(event);
|
||||
return sent > 0 ? 0 : -1;
|
||||
@@ -1872,8 +1833,6 @@ char* nostr_handler_relay_info_json(const char* relay_url) {
|
||||
}
|
||||
|
||||
int nostr_handler_send_dm_nip17(const char* recipient_pubkey_hex, const char* message, const char* subject) {
|
||||
static int g_nip17_send_in_progress = 0;
|
||||
|
||||
if (!g_cfg || !g_pool || !recipient_pubkey_hex || !message || recipient_pubkey_hex[0] == '\0' || message[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
@@ -1928,8 +1887,6 @@ int nostr_handler_send_dm_nip17(const char* recipient_pubkey_hex, const char* me
|
||||
int any_sent = 0;
|
||||
int sent_count = 0;
|
||||
|
||||
g_nip17_send_in_progress++;
|
||||
|
||||
for (int i = 0; i < gift_count; i++) {
|
||||
if (!gift_wraps[i]) {
|
||||
continue;
|
||||
@@ -1953,72 +1910,9 @@ int nostr_handler_send_dm_nip17(const char* recipient_pubkey_hex, const char* me
|
||||
cJSON_Delete(gift_wraps[i]);
|
||||
}
|
||||
|
||||
if (g_nip17_send_in_progress == 1) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
(void)nostr_relay_pool_poll(g_pool, 100);
|
||||
}
|
||||
|
||||
for (int i = 0; i < gift_count; i++) {
|
||||
if (!gift_wraps[i]) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int resend_total = 0;
|
||||
for (int i = 0; i < gift_count; i++) {
|
||||
(void)i;
|
||||
}
|
||||
|
||||
// Recreate and resend once after auth handshake drain.
|
||||
cJSON* retry_chat_event = nostr_nip17_create_chat_event(
|
||||
message,
|
||||
recipients,
|
||||
1,
|
||||
(subject && subject[0] != '\0') ? subject : NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
g_cfg->keys.public_key_hex);
|
||||
if (retry_chat_event) {
|
||||
cJSON* retry_gift_wraps[NIP17_MAX_GIFT_WRAPS] = {0};
|
||||
int retry_count = nostr_nip17_send_dm(retry_chat_event,
|
||||
recipients,
|
||||
1,
|
||||
g_cfg->keys.private_key,
|
||||
retry_gift_wraps,
|
||||
NIP17_MAX_GIFT_WRAPS,
|
||||
0);
|
||||
cJSON_Delete(retry_chat_event);
|
||||
|
||||
if (retry_count > 0) {
|
||||
for (int i = 0; i < retry_count; i++) {
|
||||
if (!retry_gift_wraps[i]) {
|
||||
continue;
|
||||
}
|
||||
int resent = nostr_relay_pool_publish_async(g_pool,
|
||||
target_relays,
|
||||
target_count,
|
||||
retry_gift_wraps[i],
|
||||
NULL,
|
||||
NULL);
|
||||
if (resent > 0) {
|
||||
any_sent = 1;
|
||||
resend_total += resent;
|
||||
}
|
||||
cJSON_Delete(retry_gift_wraps[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_TRACE("[didactyl] NIP-17 send retry window complete: recipient=%.16s... initial_sent=%d retry_sent=%d",
|
||||
recipient_pubkey_hex,
|
||||
sent_count,
|
||||
resend_total);
|
||||
} else {
|
||||
DEBUG_TRACE("[didactyl] NIP-17 send in nested context (depth=%d), skipping poll+retry window",
|
||||
g_nip17_send_in_progress);
|
||||
}
|
||||
|
||||
g_nip17_send_in_progress--;
|
||||
DEBUG_TRACE("[didactyl] NIP-17 send complete: recipient=%.16s... sent_count=%d",
|
||||
recipient_pubkey_hex,
|
||||
sent_count);
|
||||
|
||||
if (!any_sent) {
|
||||
DEBUG_WARN("[didactyl] NIP-17 send failed: no relay accepted publish for recipient %.16s...",
|
||||
@@ -2108,9 +2002,38 @@ int nostr_handler_poll(int timeout_ms) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
double start_ms = -1.0;
|
||||
struct timespec ts_start;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts_start) == 0) {
|
||||
start_ms = ts_start.tv_sec * 1000.0 + ts_start.tv_nsec / 1000000.0;
|
||||
}
|
||||
|
||||
int rc = nostr_relay_pool_poll(g_pool, timeout_ms);
|
||||
g_poll_counter++;
|
||||
|
||||
if (start_ms >= 0.0) {
|
||||
struct timespec ts_end;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts_end) == 0) {
|
||||
const double end_ms = ts_end.tv_sec * 1000.0 + ts_end.tv_nsec / 1000000.0;
|
||||
const double elapsed_ms = end_ms - start_ms;
|
||||
const double expected_ms = timeout_ms > 0 ? (double)timeout_ms : 0.0;
|
||||
|
||||
if (elapsed_ms > expected_ms + 250.0) {
|
||||
DEBUG_WARN("[didactyl] poll latency spike: nostr_relay_pool_poll(timeout=%d) took %.1fms rc=%d count=%llu",
|
||||
timeout_ms,
|
||||
elapsed_ms,
|
||||
rc,
|
||||
(unsigned long long)g_poll_counter);
|
||||
} else if ((g_poll_counter % 200ULL) == 0ULL) {
|
||||
DEBUG_TRACE("[didactyl] poll heartbeat: timeout=%d elapsed=%.1fms rc=%d count=%llu",
|
||||
timeout_ms,
|
||||
elapsed_ms,
|
||||
rc,
|
||||
(unsigned long long)g_poll_counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_relay_state_changes();
|
||||
|
||||
return rc;
|
||||
|
||||
@@ -37,6 +37,7 @@ void nostr_handler_publish_result_free(nostr_publish_result_t* result);
|
||||
char* nostr_handler_query_json(cJSON* filter, int timeout_ms);
|
||||
int nostr_handler_poll(int timeout_ms);
|
||||
int nostr_handler_reconcile_startup_events(void);
|
||||
void nostr_handler_refresh_relay_statuses(void);
|
||||
const char* nostr_handler_get_system_context(void);
|
||||
const char* nostr_handler_get_startup_display_name(void);
|
||||
int nostr_handler_connected_relay_count(void);
|
||||
|
||||
@@ -268,7 +268,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
cJSON_AddItemToObject(adopt_filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(adopt_filter, "limit", 1);
|
||||
|
||||
char* adoption_json = nostr_handler_query_json(adopt_filter, 8000);
|
||||
char* adoption_json = nostr_handler_query_json(adopt_filter, 2000);
|
||||
cJSON_Delete(adopt_filter);
|
||||
if (!adoption_json) {
|
||||
return 0;
|
||||
@@ -328,7 +328,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
cJSON_AddItemToObject(skill_filter, "#d", d_values);
|
||||
cJSON_AddNumberToObject(skill_filter, "limit", 1);
|
||||
|
||||
char* skill_json = nostr_handler_query_json(skill_filter, 8000);
|
||||
char* skill_json = nostr_handler_query_json(skill_filter, 2000);
|
||||
cJSON_Delete(skill_filter);
|
||||
if (!skill_json) {
|
||||
continue;
|
||||
@@ -513,7 +513,7 @@ int trigger_manager_poll(trigger_manager_t* mgr) {
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
if (mgr->last_poll_at > 0 && (now - mgr->last_poll_at) < 2) {
|
||||
if (mgr->last_poll_at > 0 && (now - mgr->last_poll_at) < 10) {
|
||||
return 0;
|
||||
}
|
||||
mgr->last_poll_at = now;
|
||||
@@ -538,7 +538,7 @@ int trigger_manager_poll(trigger_manager_t* mgr) {
|
||||
cJSON_AddNumberToObject(filter, "since", (double)since);
|
||||
cJSON_AddNumberToObject(filter, "limit", 8);
|
||||
|
||||
char* events_json = nostr_handler_query_json(filter, 4000);
|
||||
char* events_json = nostr_handler_query_json(filter, 1200);
|
||||
cJSON_Delete(filter);
|
||||
if (!events_json) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user