/* The app's C smoke test: does the engine embed the way app/ needs it to? * * It links libcstcore.a (or the cstcore target) and runs without Qt, so the * C side of the GUI stays verifiable on any machine -- including CI and the * Android cross-build. `cst-agent-app --selftest` runs the same checks inside * the real process. */ #define _POSIX_C_SOURCE 200809L #include "cst_agent.h" #include "cst_tools.h" #include #include #include #ifdef _WIN32 #include #include #define getpid _getpid #else #include #include #endif static int failures; #define CHECK(cond, what) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, what); \ failures++; \ } \ } while (0) /* A tool the app would implement (the Linux build uses notify-send; here it * only has to prove the plumbing). */ static int ctx_runs; static int app_tool_run(const cst_tool_ctx_t *ctx, const ctl_tool_call_t *call, cst_tool_result_t *r, void *ud) { (void)ctx; (void)call; (void)ud; ctx_runs++; r->out = strdup("app tool ran"); r->out_len = r->out ? strlen(r->out) : 0; r->exit_code = 0; return 0; } static int probe_missing(char *why, size_t why_sz) { snprintf(why, why_sz, "the desktop service is not running"); return -1; } int main(void) { char ws[1024]; snprintf(ws, sizeof(ws), "/tmp/cst-app-selftest-%d", (int)getpid()); #ifdef _WIN32 _mkdir(ws); /* the engine wants its workspace to exist */ #else mkdir(ws, 0700); #endif cst_agent_config_t cfg; memset(&cfg, 0, sizeof(cfg)); snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", ws); snprintf(cfg.session_path, sizeof(cfg.session_path), "%s/s.jsonl", ws); snprintf(cfg.provider, sizeof(cfg.provider), "deepseek"); snprintf(cfg.model, sizeof(cfg.model), "deepseek-flash"); snprintf(cfg.api_base, sizeof(cfg.api_base), "https://api.deepseek.com/v1"); snprintf(cfg.api_key, sizeof(cfg.api_key), "test-key"); snprintf(cfg.mode, sizeof(cfg.mode), "general"); cfg.max_steps = 1; cst_agent_t *a = cst_agent_create(&cfg); CHECK(a != NULL, "the app can start an engine in its own process"); if (!a) { fprintf(stderr, "cst-app-selftest: cannot start the engine\n"); return 1; } /* 1. a tool that exists here (no probe) is offered to the model */ cst_tool_def_t plain; memset(&plain, 0, sizeof(plain)); plain.name = "app_ping"; plain.desc = "A tool the app provides."; plain.perm = CST_TP_READ; plain.run_ex = app_tool_run; plain.owner = "app:selftest"; CHECK(cst_tool_register(&plain) == 0, "register an app tool"); const cst_tool_def_t *t = cst_tool_find("app_ping"); CHECK(t != NULL, "it is findable"); CHECK(t && cst_tool_active(cst_agent_config(a), t), "and it is offered"); ctl_tool_call_t call; memset(&call, 0, sizeof(call)); snprintf(call.name, sizeof(call.name), "app_ping"); cst_tool_result_t res; cst_tool_ctx_t ctx; memset(&ctx, 0, sizeof(ctx)); ctx.workspace_root = ws; ctx.agent = a; cst_tool_run(&ctx, &call, &res); CHECK(ctx_runs == 1, "the app callback ran"); CHECK(res.out && strcmp(res.out, "app tool ran") == 0, "its result came back"); cst_tool_result_free(&res); /* 2. a tool whose platform service is missing is never offered ... */ cst_tool_def_t missing; memset(&missing, 0, sizeof(missing)); missing.name = "app_no_service"; missing.desc = "Needs a service that is not here."; missing.perm = CST_TP_ASK; missing.perm_name = "notify"; missing.available = probe_missing; missing.run_ex = app_tool_run; missing.owner = "app:selftest"; CHECK(cst_tool_register(&missing) == 0, "register a tool with a probe"); const cst_tool_def_t *mt = cst_tool_find("app_no_service"); char why[160] = ""; CHECK(mt && cst_tool_available(mt, why, sizeof(why)) != 0, "the probe says it cannot run"); CHECK(strstr(why, "desktop service") != NULL, "and passes its reason on"); CHECK(mt && !cst_tool_active(cst_agent_config(a), mt), "so it stays out of tools[] and the prompt"); snprintf(call.name, sizeof(call.name), "app_no_service"); cst_tool_run(&ctx, &call, &res); CHECK(res.out && strstr(res.out, "cannot run here") != NULL, "asking for it anyway is answered with the reason"); CHECK(ctx_runs == 1, "and its callback never ran"); cst_tool_result_free(&res); /* 3. the app can drop its tools again */ CHECK(cst_tool_unregister_owner("app:selftest") == 2, "unregister both"); CHECK(cst_tool_find("app_ping") == NULL, "app_ping is gone"); CHECK(cst_tool_find("read_file") != NULL, "the built-ins are untouched"); cst_agent_destroy(a); { char cmd[1200]; snprintf(cmd, sizeof(cmd), "rm -rf %s", ws); if (system(cmd) != 0) fprintf(stderr, "note: could not clean %s\n", ws); } if (failures) { fprintf(stderr, "cst-app-selftest: %d failure(s)\n", failures); return 1; } printf("cst-app-selftest: OK\n"); return 0; }