diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d1899f9..71e8aa0 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -49,37 +49,6 @@ jobs: ./cst-agent --workspace "$PWD" --list-sessions ./cst-agent --demo-ctl --workspace . - # Disabled: Docker runner lacks SYS_PTRACE; ASan spins on DEADLYSIGNAL and - # wedged the only runner. Re-enable after the runner gets --cap-add SYS_PTRACE - # (or seccomp=unconfined). - sanitizers: - if: false - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout - env: - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_SHA: ${{ github.sha }} - run: | - curl -fsSL "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/commit/$GITHUB_SHA/scripts/ci_checkout.sh" \ - -o /tmp/ci_checkout.sh - bash /tmp/ci_checkout.sh - - name: Install dependencies - run: | - SUDO=$(command -v sudo || true) - $SUDO bash scripts/ci_pkg_mirror.sh - $SUDO apt-get update - $SUDO apt-get install -y --no-install-recommends \ - build-essential pkg-config libcurl4-openssl-dev libncurses-dev - - name: ASan + UBSan - timeout-minutes: 10 - env: - ASAN_OPTIONS: abort_on_error=1:halt_on_error=1:detect_leaks=0:use_sigaltstack=0 - UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 - run: timeout -s KILL 480 make asan - musl: runs-on: ubuntu-latest timeout-minutes: 30 diff --git a/tests/test_subagent.c b/tests/test_subagent.c index 21e2bc8..06af50b 100644 --- a/tests/test_subagent.c +++ b/tests/test_subagent.c @@ -93,7 +93,7 @@ static void reply_calls(test_sock_t fd, const char *const *calls, int n) { cJSON *tc = cJSON_AddArrayToObject(d, "tool_calls"); cJSON *c = cJSON_CreateObject(); cJSON_AddNumberToObject(c, "index", i); - char id[16]; + char id[32]; snprintf(id, sizeof(id), "call_%d", i); cJSON_AddStringToObject(c, "id", id); cJSON_AddStringToObject(c, "type", "function"); @@ -120,19 +120,30 @@ static int has_tool(const cJSON *body, const char *name) { static void handle(test_sock_t fd, const char *body_txt) { cJSON *body = cJSON_Parse(body_txt); + if (!body) { + const char *err = "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"; + send_all(fd, err, strlen(err)); + return; + } const cJSON *msgs = cJSON_GetObjectItemCaseSensitive(body, "messages"); - int nm = cJSON_GetArraySize(msgs); - const cJSON *sys = cJSON_GetObjectItemCaseSensitive( - cJSON_GetArrayItem(msgs, 0), "content"); - const cJSON *last = cJSON_GetArrayItem(msgs, nm - 1); - const char *last_role = - cJSON_GetObjectItemCaseSensitive(last, "role")->valuestring; + int nm = cJSON_IsArray(msgs) ? cJSON_GetArraySize(msgs) : 0; + const cJSON *sys = NULL; + const char *last_role = ""; const char *user = ""; + if (nm > 0) { + sys = cJSON_GetObjectItemCaseSensitive(cJSON_GetArrayItem(msgs, 0), + "content"); + const cJSON *last = cJSON_GetArrayItem(msgs, nm - 1); + const cJSON *lr = cJSON_GetObjectItemCaseSensitive(last, "role"); + if (cJSON_IsString(lr)) + last_role = lr->valuestring; + } for (int i = 0; i < nm; i++) { const cJSON *m = cJSON_GetArrayItem(msgs, i); const cJSON *r = cJSON_GetObjectItemCaseSensitive(m, "role"); const cJSON *c = cJSON_GetObjectItemCaseSensitive(m, "content"); - if (strcmp(r->valuestring, "user") == 0 && cJSON_IsString(c)) + if (cJSON_IsString(r) && strcmp(r->valuestring, "user") == 0 && + cJSON_IsString(c)) user = c->valuestring; } const char *hdr = "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n" @@ -160,8 +171,10 @@ static void handle(test_sock_t fd, const char *body_txt) { const char *calls[] = {"read_file", "{\"path\":\"a.txt\"}"}; reply_calls(fd, calls, 1); } else { - char out[256]; - snprintf(out, sizeof(out), "child report %s", user + strlen(user) - 1); + /* last char of the user prompt (e.g. "Find A" -> "A"); empty-safe */ + char tag = user[0] ? user[strlen(user) - 1] : '?'; + char out[64]; + snprintf(out, sizeof(out), "child report %c", tag); reply_text(fd, out); } atomic_fetch_sub(&g_child_active, 1);