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.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user