v0.0.62 - fix: webhook trigger lookup uses trigger manager and add startup PoC triggers/tests
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user