v0.0.62 - fix: webhook trigger lookup uses trigger manager and add startup PoC triggers/tests

This commit is contained in:
Your Name
2026-03-10 05:48:21 -04:00
parent 1d66a3a97e
commit 8018ea6e2f
4 changed files with 112 additions and 93 deletions
+2 -2
View File
@@ -53,11 +53,11 @@ Skills support context modes (`inject`, `full`, `override`) and per-skill LLM fa
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.61
## Current Status — v0.0.62
**Active build — this project is barely working. Experiment at your own risk.**
> Last release update: v0.0.61 — feat: add webhook cron and chain trigger types
> Last release update: v0.0.62 — fix: webhook trigger lookup uses trigger manager and add startup PoC triggers/tests
- Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected
+6 -89
View File
@@ -105,81 +105,6 @@ static int uri_extract_after_prefix(const struct mg_str* uri,
return 0;
}
static cJSON* find_skill_event_by_d_tag(const char* d_tag) {
if (!d_tag || d_tag[0] == '\0') {
return NULL;
}
char* events_json = nostr_handler_get_self_skill_events_json();
if (!events_json) {
return NULL;
}
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (!events || !cJSON_IsArray(events)) {
cJSON_Delete(events);
return NULL;
}
cJSON* found = NULL;
int n = cJSON_GetArraySize(events);
for (int i = 0; i < n; i++) {
cJSON* ev = cJSON_GetArrayItem(events, i);
cJSON* tags = ev ? cJSON_GetObjectItemCaseSensitive(ev, "tags") : NULL;
if (!tags || !cJSON_IsArray(tags)) {
continue;
}
int tn = cJSON_GetArraySize(tags);
for (int ti = 0; ti < tn; ti++) {
cJSON* tag = cJSON_GetArrayItem(tags, ti);
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
continue;
}
cJSON* key = cJSON_GetArrayItem(tag, 0);
cJSON* val = cJSON_GetArrayItem(tag, 1);
if (!key || !val || !cJSON_IsString(key) || !cJSON_IsString(val) || !key->valuestring || !val->valuestring) {
continue;
}
if (strcmp(key->valuestring, "d") == 0 && strcmp(val->valuestring, d_tag) == 0) {
found = cJSON_Duplicate(ev, 1);
break;
}
}
if (found) {
break;
}
}
cJSON_Delete(events);
return found;
}
static const char* find_tag_string_local(cJSON* tags, const char* key) {
if (!tags || !cJSON_IsArray(tags) || !key || key[0] == '\0') {
return NULL;
}
int n = cJSON_GetArraySize(tags);
for (int i = 0; i < n; i++) {
cJSON* tag = cJSON_GetArrayItem(tags, i);
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
continue;
}
cJSON* k = cJSON_GetArrayItem(tag, 0);
cJSON* v = cJSON_GetArrayItem(tag, 1);
if (!k || !v || !cJSON_IsString(k) || !cJSON_IsString(v) || !k->valuestring || !v->valuestring) {
continue;
}
if (strcmp(k->valuestring, key) == 0) {
return v->valuestring;
}
}
return NULL;
}
static void handle_trigger_webhook(struct mg_connection* c,
const struct mg_http_message* hm,
@@ -189,23 +114,18 @@ static void handle_trigger_webhook(struct mg_connection* c,
return;
}
cJSON* skill_ev = find_skill_event_by_d_tag(d_tag);
if (!skill_ev) {
reply_error(c, 404, "skill not found by d_tag");
active_trigger_t trigger;
if (trigger_manager_find(g_api_ctx.trigger_manager, d_tag, &trigger) != 0) {
reply_error(c, 404, "active trigger not found for d_tag");
return;
}
cJSON* tags = cJSON_GetObjectItemCaseSensitive(skill_ev, "tags");
const char* trigger_type = find_tag_string_local(tags, "trigger");
if (!trigger_type || strcmp(trigger_type, "webhook") != 0) {
cJSON_Delete(skill_ev);
reply_error(c, 400, "skill trigger type is not webhook");
if (trigger.trigger_type != TRIGGER_TYPE_WEBHOOK) {
reply_error(c, 400, "trigger type is not webhook");
return;
}
const char* enabled = find_tag_string_local(tags, "enabled");
if (enabled && (strcmp(enabled, "false") == 0 || strcmp(enabled, "0") == 0)) {
cJSON_Delete(skill_ev);
if (!trigger.enabled) {
reply_error(c, 400, "webhook trigger is disabled");
return;
}
@@ -213,7 +133,6 @@ static void handle_trigger_webhook(struct mg_connection* c,
cJSON* payload = parse_body_json(hm);
if (!payload || !cJSON_IsObject(payload)) {
cJSON_Delete(payload);
cJSON_Delete(skill_ev);
reply_error(c, 400, "invalid JSON body");
return;
}
@@ -221,7 +140,6 @@ static void handle_trigger_webhook(struct mg_connection* c,
cJSON* event = cJSON_CreateObject();
if (!event) {
cJSON_Delete(payload);
cJSON_Delete(skill_ev);
reply_error(c, 500, "oom");
return;
}
@@ -234,7 +152,6 @@ static void handle_trigger_webhook(struct mg_connection* c,
int fired = trigger_manager_fire(g_api_ctx.trigger_manager, d_tag, event, "webhook");
cJSON_Delete(event);
cJSON_Delete(skill_ev);
if (fired < 0) {
reply_error(c, 404, "active trigger not found for d_tag");
+2 -2
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 0
#define DIDACTYL_VERSION_PATCH 61
#define DIDACTYL_VERSION "v0.0.61"
#define DIDACTYL_VERSION_PATCH 62
#define DIDACTYL_VERSION "v0.0.62"
// Agent metadata
#define DIDACTYL_NAME "Didactyl"
Executable
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
PASS_COUNT=0
FAIL_COUNT=0
pass() {
PASS_COUNT=$((PASS_COUNT + 1))
printf "[PASS] %s\n" "$1"
}
fail() {
FAIL_COUNT=$((FAIL_COUNT + 1))
printf "[FAIL] %s\n" "$1"
}
assert_grep() {
local pattern="$1"
local file="$2"
local desc="$3"
if grep -E -q "$pattern" "$file"; then
pass "$desc"
else
fail "$desc (pattern not found in $file)"
fi
}
assert_cmd() {
local desc="$1"
shift
if "$@" >/dev/null 2>&1; then
pass "$desc"
else
fail "$desc"
fi
}
printf "== Didactyl Trigger Feature Test ==\n"
printf "Root: %s\n" "$ROOT_DIR"
printf "\n-- Build check --\n"
if make -j >/tmp/didactyl-test-build.log 2>&1; then
pass "Project builds successfully"
else
fail "Project build failed (see /tmp/didactyl-test-build.log)"
fi
assert_cmd "Built binary exists" test -x ./didactyl
printf "\n-- Source wiring checks --\n"
assert_grep "TRIGGER_TYPE_WEBHOOK" src/trigger_manager.h "Webhook trigger type defined"
assert_grep "TRIGGER_TYPE_CRON" src/trigger_manager.h "Cron trigger type defined"
assert_grep "TRIGGER_TYPE_CHAIN" src/trigger_manager.h "Chain trigger type defined"
assert_grep "trigger_manager_fire\(" src/trigger_manager.c "Generic trigger fire API implemented"
assert_grep "trigger_manager_fire_chains\(" src/trigger_manager.c "Chain fire API implemented"
assert_grep "cron_matches_now\(" src/trigger_manager.c "Cron matcher implemented"
assert_grep "trigger_type_to_string" src/trigger_manager.c "Trigger type serialization implemented"
assert_grep "\"/api/trigger/\*\"" src/http_api.c "Webhook API route registered"
assert_grep "handle_trigger_webhook\(" src/http_api.c "Webhook handler implemented"
assert_grep "nostr-subscription, webhook, cron, chain" src/tools.c "skill_create validation accepts 4 trigger types"
assert_grep "skill_edit trigger must be one of: nostr-subscription, webhook, cron, chain" src/tools.c "skill_edit validation accepts 4 trigger types"
assert_grep "trigger_manager_fire_chains\(" src/agent.c "Post-execution chain hook wired in agent"
printf "\n-- Docs checks --\n"
assert_grep "\`webhook\`" docs/SKILLS.md "SKILLS docs include webhook trigger"
assert_grep "\`cron\`" docs/SKILLS.md "SKILLS docs include cron trigger"
assert_grep "\`chain\`" docs/SKILLS.md "SKILLS docs include chain trigger"
assert_grep "POST /api/trigger/:d_tag" docs/API.md "API docs include webhook endpoint"
printf "\n-- Optional local API probe --\n"
if command -v curl >/dev/null 2>&1; then
HTTP_CODE=""
HTTP_CODE="$(curl -k -s --connect-timeout 2 --max-time 5 -o /tmp/didactyl-trigger-probe.out -w "%{http_code}" https://127.0.0.1:8484/api/trigger/nonexistent -X POST -H 'Content-Type: application/json' -d '{}' || true)"
if [[ "$HTTP_CODE" == "000" ]]; then
HTTP_CODE="$(curl -s --connect-timeout 2 --max-time 5 -o /tmp/didactyl-trigger-probe.out -w "%{http_code}" http://127.0.0.1:8484/api/trigger/nonexistent -X POST -H 'Content-Type: application/json' -d '{}' || true)"
fi
if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "400" || "$HTTP_CODE" == "404" || "$HTTP_CODE" == "503" ]]; then
pass "Webhook endpoint responds on local API (http code: $HTTP_CODE)"
else
fail "Webhook endpoint probe did not get expected response (http code: ${HTTP_CODE:-none})"
fi
else
fail "curl not available for API probe"
fi
printf "\n== Result ==\n"
printf "Pass: %d\n" "$PASS_COUNT"
printf "Fail: %d\n" "$FAIL_COUNT"
if [[ "$FAIL_COUNT" -gt 0 ]]; then
exit 1
fi
exit 0