v0.2.32 - Made user-settings recall resilient and backfill missing didactyl admin_pubkey/dm_protocol on republish

This commit is contained in:
Didactyl User
2026-04-09 07:42:08 -04:00
parent 870c720526
commit 0fc8af96e4
4 changed files with 72 additions and 34 deletions

View File

@@ -54,11 +54,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e
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.2.31
## Current Status — v0.2.32
**Active build — this project is barely working. Experiment at your own risk.**
> Last release update: v0.2.31Added kind 10050 to genesis.jsonc
> Last release update: v0.2.32Made user-settings recall resilient and backfill missing didactyl admin_pubkey/dm_protocol on republish
- Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected

View File

@@ -741,8 +741,13 @@ static int fetch_self_config_plaintext(const didactyl_config_t* cfg, const char*
return 0;
}
static int apply_recalled_user_settings(didactyl_config_t* cfg, const char* plaintext) {
static int apply_recalled_user_settings(didactyl_config_t* cfg,
const char* plaintext,
int* out_backfill_needed) {
if (!cfg || !plaintext) return -1;
if (out_backfill_needed) {
*out_backfill_needed = 0;
}
cJSON* root = cJSON_Parse(plaintext);
if (!root || !cJSON_IsObject(root)) {
@@ -752,7 +757,7 @@ static int apply_recalled_user_settings(didactyl_config_t* cfg, const char* plai
cJSON* global_llm = cJSON_GetObjectItemCaseSensitive(root, "global_llm");
cJSON* didactyl = cJSON_GetObjectItemCaseSensitive(root, "didactyl");
if (!global_llm || !cJSON_IsObject(global_llm) || !didactyl || !cJSON_IsObject(didactyl)) {
if (!global_llm || !cJSON_IsObject(global_llm)) {
cJSON_Delete(root);
return -1;
}
@@ -764,15 +769,20 @@ static int apply_recalled_user_settings(didactyl_config_t* cfg, const char* plai
cJSON* max_tokens = cJSON_GetObjectItemCaseSensitive(global_llm, "max_tokens");
cJSON* temperature = cJSON_GetObjectItemCaseSensitive(global_llm, "temperature");
cJSON* admin_pubkey = cJSON_GetObjectItemCaseSensitive(didactyl, "admin_pubkey");
cJSON* dm_protocol = cJSON_GetObjectItemCaseSensitive(didactyl, "dm_protocol");
cJSON* max_turns = cJSON_GetObjectItemCaseSensitive(didactyl, "max_turns");
cJSON* admin_pubkey = NULL;
cJSON* dm_protocol = NULL;
cJSON* max_turns = NULL;
if (didactyl && cJSON_IsObject(didactyl)) {
admin_pubkey = cJSON_GetObjectItemCaseSensitive(didactyl, "admin_pubkey");
dm_protocol = cJSON_GetObjectItemCaseSensitive(didactyl, "dm_protocol");
max_turns = cJSON_GetObjectItemCaseSensitive(didactyl, "max_turns");
} else if (out_backfill_needed) {
*out_backfill_needed = 1;
}
if (!api_key || !cJSON_IsString(api_key) || !api_key->valuestring || api_key->valuestring[0] == '\0' ||
!model || !cJSON_IsString(model) || !model->valuestring || model->valuestring[0] == '\0' ||
!base_url || !cJSON_IsString(base_url) || !base_url->valuestring || base_url->valuestring[0] == '\0' ||
!admin_pubkey || !cJSON_IsString(admin_pubkey) || !admin_pubkey->valuestring || admin_pubkey->valuestring[0] == '\0' ||
!dm_protocol || !cJSON_IsString(dm_protocol) || !dm_protocol->valuestring || dm_protocol->valuestring[0] == '\0') {
!base_url || !cJSON_IsString(base_url) || !base_url->valuestring || base_url->valuestring[0] == '\0') {
cJSON_Delete(root);
return -1;
}
@@ -793,16 +803,22 @@ static int apply_recalled_user_settings(didactyl_config_t* cfg, const char* plai
cfg->llm.temperature = temperature->valuedouble;
}
{
if (admin_pubkey && cJSON_IsString(admin_pubkey) && admin_pubkey->valuestring && admin_pubkey->valuestring[0] != '\0') {
char decoded[65] = {0};
if (decode_pubkey_hex_or_npub_local(admin_pubkey->valuestring, decoded) != 0) {
cJSON_Delete(root);
return -1;
}
snprintf(cfg->admin.pubkey, sizeof(cfg->admin.pubkey), "%s", decoded);
} else if (out_backfill_needed) {
*out_backfill_needed = 1;
}
dm_protocol_from_string(dm_protocol->valuestring, cfg);
if (dm_protocol && cJSON_IsString(dm_protocol) && dm_protocol->valuestring && dm_protocol->valuestring[0] != '\0') {
dm_protocol_from_string(dm_protocol->valuestring, cfg);
} else if (out_backfill_needed) {
*out_backfill_needed = 1;
}
if (max_turns && cJSON_IsNumber(max_turns)) {
int parsed = (int)max_turns->valuedouble;
@@ -903,10 +919,13 @@ static int persist_runtime_config_to_nostr(const didactyl_config_t* cfg, int per
return rc;
}
static int recover_missing_runtime_config_from_nostr(didactyl_config_t* cfg) {
static int recover_missing_runtime_config_from_nostr(didactyl_config_t* cfg, int* out_backfill_needed) {
if (!cfg) {
return 0;
}
if (out_backfill_needed) {
*out_backfill_needed = 0;
}
char* settings_plaintext = NULL;
if (fetch_self_config_plaintext(cfg, "user-settings", &settings_plaintext) != 0 || !settings_plaintext) {
@@ -915,14 +934,22 @@ static int recover_missing_runtime_config_from_nostr(didactyl_config_t* cfg) {
return 0;
}
if (apply_recalled_user_settings(cfg, settings_plaintext) != 0) {
int backfill_needed = 0;
if (apply_recalled_user_settings(cfg, settings_plaintext, &backfill_needed) != 0) {
DEBUG_WARN("[didactyl] startup phase: failed to parse/decrypt recalled user-settings");
free(settings_plaintext);
return 0;
}
free(settings_plaintext);
DEBUG_INFO("[didactyl] startup phase: recovered user-settings from Nostr");
if (out_backfill_needed) {
*out_backfill_needed = backfill_needed;
}
if (backfill_needed) {
DEBUG_INFO("[didactyl] startup phase: recovered user-settings from Nostr (missing didactyl fields will be backfilled)");
} else {
DEBUG_INFO("[didactyl] startup phase: recovered user-settings from Nostr");
}
return 1;
}
@@ -1203,8 +1230,13 @@ int main(int argc, char** argv) {
}
startup_step_begin(2, "Recover runtime config from Nostr");
int llm_recalled_from_nostr = recover_missing_runtime_config_from_nostr(&cfg);
startup_step_ok(2, "Recover runtime config from Nostr", llm_recalled_from_nostr ? "user-settings recalled" : NULL);
int user_settings_backfill_needed = 0;
int llm_recalled_from_nostr = recover_missing_runtime_config_from_nostr(&cfg, &user_settings_backfill_needed);
startup_step_ok(2,
"Recover runtime config from Nostr",
llm_recalled_from_nostr
? (user_settings_backfill_needed ? "user-settings recalled (backfill required)" : "user-settings recalled")
: NULL);
startup_step_begin(3, "Validate LLM config");
if (!llm_config_is_complete(&cfg.llm)) {
@@ -1266,7 +1298,8 @@ int main(int argc, char** argv) {
}
if (bootstrap_mode || first_run) {
if (persist_runtime_config_to_nostr(&cfg, llm_recalled_from_nostr ? 0 : 1) != 0) {
if (persist_runtime_config_to_nostr(&cfg,
(llm_recalled_from_nostr && !user_settings_backfill_needed) ? 0 : 1) != 0) {
DEBUG_WARN("[didactyl] startup phase: failed to persist encrypted runtime config to Nostr");
} else {
DEBUG_INFO("[didactyl] startup phase: persisted encrypted runtime config to Nostr");

View File

@@ -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 2
#define DIDACTYL_VERSION_PATCH 31
#define DIDACTYL_VERSION "v0.2.31"
#define DIDACTYL_VERSION_PATCH 32
#define DIDACTYL_VERSION "v0.2.32"
// Agent metadata
#define DIDACTYL_NAME "Didactyl"

View File

@@ -1019,7 +1019,7 @@ static int apply_recalled_user_settings_wizard(didactyl_config_t* cfg, const cha
cJSON* global_llm = cJSON_GetObjectItemCaseSensitive(root, "global_llm");
cJSON* didactyl = cJSON_GetObjectItemCaseSensitive(root, "didactyl");
if (!global_llm || !cJSON_IsObject(global_llm) || !didactyl || !cJSON_IsObject(didactyl)) {
if (!global_llm || !cJSON_IsObject(global_llm)) {
cJSON_Delete(root);
return -1;
}
@@ -1031,15 +1031,18 @@ static int apply_recalled_user_settings_wizard(didactyl_config_t* cfg, const cha
cJSON* max_tokens = cJSON_GetObjectItemCaseSensitive(global_llm, "max_tokens");
cJSON* temperature = cJSON_GetObjectItemCaseSensitive(global_llm, "temperature");
cJSON* admin_pubkey = cJSON_GetObjectItemCaseSensitive(didactyl, "admin_pubkey");
cJSON* dm_protocol = cJSON_GetObjectItemCaseSensitive(didactyl, "dm_protocol");
cJSON* max_turns = cJSON_GetObjectItemCaseSensitive(didactyl, "max_turns");
cJSON* admin_pubkey = NULL;
cJSON* dm_protocol = NULL;
cJSON* max_turns = NULL;
if (didactyl && cJSON_IsObject(didactyl)) {
admin_pubkey = cJSON_GetObjectItemCaseSensitive(didactyl, "admin_pubkey");
dm_protocol = cJSON_GetObjectItemCaseSensitive(didactyl, "dm_protocol");
max_turns = cJSON_GetObjectItemCaseSensitive(didactyl, "max_turns");
}
if (!api_key || !cJSON_IsString(api_key) || !api_key->valuestring || api_key->valuestring[0] == '\0' ||
!model || !cJSON_IsString(model) || !model->valuestring || model->valuestring[0] == '\0' ||
!base_url || !cJSON_IsString(base_url) || !base_url->valuestring || base_url->valuestring[0] == '\0' ||
!admin_pubkey || !cJSON_IsString(admin_pubkey) || !admin_pubkey->valuestring || admin_pubkey->valuestring[0] == '\0' ||
!dm_protocol || !cJSON_IsString(dm_protocol) || !dm_protocol->valuestring || dm_protocol->valuestring[0] == '\0') {
!base_url || !cJSON_IsString(base_url) || !base_url->valuestring || base_url->valuestring[0] == '\0') {
cJSON_Delete(root);
return -1;
}
@@ -1060,7 +1063,7 @@ static int apply_recalled_user_settings_wizard(didactyl_config_t* cfg, const cha
cfg->llm.temperature = temperature->valuedouble;
}
{
if (admin_pubkey && cJSON_IsString(admin_pubkey) && admin_pubkey->valuestring && admin_pubkey->valuestring[0] != '\0') {
char decoded[65] = {0};
if (decode_pubkey_hex_or_npub_local(admin_pubkey->valuestring, decoded) != 0) {
cJSON_Delete(root);
@@ -1069,12 +1072,14 @@ static int apply_recalled_user_settings_wizard(didactyl_config_t* cfg, const cha
snprintf(cfg->admin.pubkey, sizeof(cfg->admin.pubkey), "%s", decoded);
}
if (strcmp(dm_protocol->valuestring, "nip17") == 0) {
cfg->dm_protocol = DM_PROTOCOL_NIP17;
} else if (strcmp(dm_protocol->valuestring, "both") == 0) {
cfg->dm_protocol = DM_PROTOCOL_BOTH;
} else {
cfg->dm_protocol = DM_PROTOCOL_NIP04;
if (dm_protocol && cJSON_IsString(dm_protocol) && dm_protocol->valuestring && dm_protocol->valuestring[0] != '\0') {
if (strcmp(dm_protocol->valuestring, "nip17") == 0) {
cfg->dm_protocol = DM_PROTOCOL_NIP17;
} else if (strcmp(dm_protocol->valuestring, "both") == 0) {
cfg->dm_protocol = DM_PROTOCOL_BOTH;
} else {
cfg->dm_protocol = DM_PROTOCOL_NIP04;
}
}
if (max_turns && cJSON_IsNumber(max_turns)) {