Files
Hermit/tests/test_registry.c
T
WHC 2fde898da0
ci / linux (clang) (push) Canceled after 0s
ci / linux (gcc) (push) Canceled after 0s
ci / sanitizers (push) Canceled after 0s
ci / musl (push) Canceled after 0s
ci / macos (push) Canceled after 0s
Usability pass: tool registry, TUI polish, saved keys and settings
Tools
- Table-driven tool registry with permission classes and default/minimal
  profiles; new tools: edit_file, list_dir, glob, grep, delete_file,
  move_file, todo_write, web_fetch, ask_user (plus read_file paging).
- Built-in Myers diff; write tools return unified diffs.
- Persistent permission rules (.agent/permissions.json), per-turn undo
  snapshots (.agent/undo), AGENTS.md project notes, auto-compact at 80%
  of the model context, cancellable run_shell with process-group kill.

TUI
- Table-driven slash commands: /provider /key /effort /status /tools
  /permissions /yolo /undo /retry /diff /export /copy /rename /delete
  /init /max-steps /todo, with / and @ completion.
- Permission modal with diff and prefix rules, ask_user modal, todo
  panel, header with model/effort/context, bracketed paste, line editing,
  wide-char cursor, NO_COLOR and ASCII fallback; Esc never quits.

Startup and configuration
- API keys are entered in the TUI (hidden) and saved per provider in
  <config>/credentials (0600); env variables are only a fallback.
- Last provider, per-provider model and reasoning effort are remembered
  in <config>/settings.json; --effort/-e and /effort send
  reasoning_effort (low|medium|high|max) to models that accept it.
- DeepSeek preset uses deepseek-flash / deepseek-v4-pro.
- Session flags --new/--continue/--resume/--list-sessions, --yolo,
  --confirm.

Compatibility and tests
- ncurses detection, macOS/Homebrew support, make install, XDG config
  dir, POSIX cleanups; Gitea CI (gcc/clang/asan/musl/macOS).
- Unit tests for diff, fs tools, registry, undo/perms and config;
  e2e fixtures multi_file_refactor and ask_or_todo; vendored cJSON.
2026-10-05 01:47:01 +08:00

115 lines
4.5 KiB
C

/* Tool registry: schema / prompt generation, profiles and /tools switches. */
#include "cJSON.h"
#include "cst_agent.h"
#include "cst_tools.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int nfail;
#define EXPECT(cond, msg) \
do { \
if (!(cond)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \
nfail++; \
} \
} while (0)
static int schema_has(cJSON *arr, const char *name) {
const cJSON *t = NULL;
cJSON_ArrayForEach(t, arr) {
const cJSON *fn = cJSON_GetObjectItemCaseSensitive(t, "function");
const cJSON *nm = cJSON_GetObjectItemCaseSensitive(fn, "name");
if (cJSON_IsString(nm) && strcmp(nm->valuestring, name) == 0)
return 1;
}
return 0;
}
int main(void) {
cst_agent_config_t cfg;
memset(&cfg, 0, sizeof(cfg));
/* every tool has a runner and a well-formed parameter list */
EXPECT(cst_tool_count() >= 13, "registry size");
for (int i = 0; i < cst_tool_count(); i++) {
const cst_tool_def_t *t = cst_tool_at(i);
EXPECT(t->run != NULL, t->name);
EXPECT((t->perm == CST_TP_WRITE) == (t->preview != NULL),
"write tools (and only those) have previews");
EXPECT(cst_tool_find(t->name) == t, "find by name");
}
cJSON *all = cst_tools_schema_cfg(&cfg);
EXPECT(cJSON_GetArraySize(all) == cst_tool_count(), "default: every tool");
EXPECT(schema_has(all, "edit_file") && schema_has(all, "grep") &&
schema_has(all, "web_fetch"),
"default schema has new tools");
cJSON_Delete(all);
cfg.tool_profile = CST_TOOLS_MINIMAL;
cJSON *min = cst_tools_schema_cfg(&cfg);
EXPECT(cJSON_GetArraySize(min) == 4, "minimal: four tools");
EXPECT(schema_has(min, "read_file") && schema_has(min, "run_shell") &&
schema_has(min, "write_file") && schema_has(min, "apply_patch"),
"minimal: the original four");
char *ms = cJSON_PrintUnformatted(min);
EXPECT(ms && strstr(ms, "\"description\":\"Read a UTF-8 text file under the "
"workspace.\""),
"minimal keeps the original wording");
EXPECT(ms && !strstr(ms, "offset"), "minimal hides optional params");
free(ms);
cJSON_Delete(min);
char *pmin = cst_agent_build_prompt(&cfg, CST_PROTO_NATIVE_EOS, NULL);
EXPECT(pmin && strcmp(pmin, cst_agent_system_prompt_for(
CST_PROTO_NATIVE_EOS)) == 0,
"minimal prompt == legacy prompt");
EXPECT(pmin && strstr(pmin, "ls/grep via run_shell"), "legacy workflow");
free(pmin);
char *pctl = cst_agent_build_prompt(&cfg, CST_PROTO_CTL, NULL);
EXPECT(pctl && !strstr(pctl, "must be quoted"), "minimal ctl: no new rule");
free(pctl);
cfg.tool_profile = CST_TOOLS_DEFAULT;
char *pdef = cst_agent_build_prompt(&cfg, CST_PROTO_CTL_HEREDOC,
"Always run `make test`.");
EXPECT(pdef && strstr(pdef, "- edit_file(path, old_string, new_string, "
"replace_all?):"),
"default prompt lists edit_file with optional marker");
EXPECT(pdef && strstr(pdef, "list_dir, glob, grep"), "default workflow");
EXPECT(pdef && strstr(pdef, "must be quoted"), "ctl numeric rule");
EXPECT(pdef && strstr(pdef, "Always run `make test`."), "project notes");
free(pdef);
/* /tools off: grep disappears everywhere */
cfg.tools_off = 1ULL << cst_tool_index("grep");
EXPECT(!cst_tool_active(&cfg, cst_tool_find("grep")), "grep off");
cJSON *off = cst_tools_schema_cfg(&cfg);
EXPECT(!schema_has(off, "grep") && schema_has(off, "glob"), "schema off");
cJSON_Delete(off);
char *names = cst_tools_names(&cfg);
EXPECT(names && !strstr(names, "grep") && strstr(names, "glob"),
"names off");
free(names);
char *poff = cst_agent_build_prompt(&cfg, CST_PROTO_NATIVE_EOS, NULL);
EXPECT(poff && !strstr(poff, "- grep(") && strstr(poff, "list_dir, glob)"),
"prompt off");
free(poff);
int prof = -1;
EXPECT(cst_tool_profile_parse("minimal", &prof) == 0 &&
prof == CST_TOOLS_MINIMAL,
"parse minimal");
EXPECT(cst_tool_profile_parse("bogus", &prof) != 0, "parse bogus");
if (nfail) {
fprintf(stderr, "test_registry: %d failure(s)\n", nfail);
return 1;
}
printf("test_registry: OK\n");
return 0;
}