Extensions: skills, plugins, MCP, sub-agents; configurable keys
- Skills (SKILL.md discovery, skill tool, /skills /skill) - plugin.json exec tools with permission prompts, timeouts and workspace trust (/plugins) - MCP stdio client: JSON-RPC initialize/tools/list/tools/call, mcp__server__tool, /mcp and sidebar status - task tool with general/explore/plan and .agent/agents/*.md roles, parallel read-only runs, cancel propagation, /agents - Keymap with Ctrl+X leader, keybinds.json overrides and /keys panel - Collapsed thinking, throttled streaming Markdown, leaner tool output, parallel read-only tools - Spawn: close-on-exec pipes and a process-wide fork lock - Tests for keymap, skills, plugins, MCP, sub-agents, shell compaction; docs/plugins.md
This commit is contained in:
+96
-1
@@ -28,7 +28,102 @@ static int schema_has(cJSON *arr, const char *name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int echo_run(const cst_tool_ctx_t *ctx, const ctl_tool_call_t *c,
|
||||
cst_tool_result_t *r, void *ud) {
|
||||
(void)ctx;
|
||||
const cst_tool_def_t *self = ud;
|
||||
char *j = cst_tool_args_json(self, c);
|
||||
r->out = j;
|
||||
r->out_len = j ? strlen(j) : 0;
|
||||
r->exit_code = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dynamic_tools(void) {
|
||||
cst_agent_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
int base = cst_tool_count();
|
||||
cst_tool_def_t d;
|
||||
memset(&d, 0, sizeof(d));
|
||||
d.name = "dyn_echo";
|
||||
d.desc = "Echo the arguments.";
|
||||
d.schema_json = "{\"type\":\"object\",\"properties\":{\"text\":{"
|
||||
"\"type\":\"string\"},\"n\":{\"type\":\"integer\"},"
|
||||
"\"opts\":{\"type\":\"object\"}},\"required\":[\"text\"]}";
|
||||
d.perm = CST_TP_READ;
|
||||
d.run_ex = echo_run;
|
||||
d.owner = "test:echo";
|
||||
EXPECT(cst_tool_register(&d) == 0, "register");
|
||||
EXPECT(cst_tool_register(&d) != 0, "duplicate name refused");
|
||||
EXPECT(cst_tool_count() == base + 1, "count grows");
|
||||
const cst_tool_def_t *t = cst_tool_find("dyn_echo");
|
||||
EXPECT(t && t != &d && strcmp(t->owner, "test:echo") == 0, "copied def");
|
||||
/* the runner gets its own def as ud for typing */
|
||||
((cst_tool_def_t *)t)->ud = (void *)t;
|
||||
|
||||
cJSON *sc = cst_tools_schema_cfg(&cfg);
|
||||
char *ss = cJSON_PrintUnformatted(sc);
|
||||
EXPECT(ss && strstr(ss, "\"name\":\"dyn_echo\"") &&
|
||||
strstr(ss, "\"required\":[\"text\"]"),
|
||||
"schema passes the JSON Schema through");
|
||||
free(ss);
|
||||
cJSON_Delete(sc);
|
||||
char *pr = cst_tools_prompt(&cfg);
|
||||
EXPECT(pr && strstr(pr, "- dyn_echo(text, n?, opts?): Echo the arguments."),
|
||||
"text prompt lists schema params");
|
||||
free(pr);
|
||||
|
||||
ctl_tool_call_t c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
snprintf(c.name, sizeof(c.name), "dyn_echo");
|
||||
cst_tool_ctx_t ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
cst_tool_result_t r;
|
||||
memset(&r, 0, sizeof(r));
|
||||
cst_tool_run(&ctx, &c, &r);
|
||||
EXPECT(r.exit_code == 1 && r.out && strstr(r.out, "needs argument \"text\""),
|
||||
"schema required checked");
|
||||
cst_tool_result_free(&r);
|
||||
c.nargs = 3;
|
||||
snprintf(c.args[0].key, sizeof(c.args[0].key), "text");
|
||||
c.args[0].value = "42";
|
||||
c.args[0].value_len = 2;
|
||||
snprintf(c.args[1].key, sizeof(c.args[1].key), "n");
|
||||
c.args[1].value = "7";
|
||||
c.args[1].value_len = 1;
|
||||
snprintf(c.args[2].key, sizeof(c.args[2].key), "opts");
|
||||
c.args[2].value = "{\"a\":true}";
|
||||
c.args[2].value_len = strlen(c.args[2].value);
|
||||
memset(&r, 0, sizeof(r));
|
||||
cst_tool_run(&ctx, &c, &r);
|
||||
EXPECT(r.exit_code == 0 && r.out &&
|
||||
strcmp(r.out, "{\"text\":\"42\",\"n\":7,\"opts\":{\"a\":true}}") == 0,
|
||||
"args typed by schema");
|
||||
cst_tool_result_free(&r);
|
||||
|
||||
cst_tool_set_disabled(&cfg, "dyn_echo", 1);
|
||||
cst_tool_set_disabled(&cfg, "grep", 1);
|
||||
EXPECT(!cst_tool_active(&cfg, t) &&
|
||||
!cst_tool_active(&cfg, cst_tool_find("grep")),
|
||||
"disable by name");
|
||||
cst_tool_set_disabled(&cfg, "dyn_echo", 0);
|
||||
EXPECT(cst_tool_active(&cfg, t) &&
|
||||
!cst_tool_active(&cfg, cst_tool_find("grep")),
|
||||
"re-enable one name");
|
||||
cst_tool_set_disabled(&cfg, "grep", 0);
|
||||
EXPECT(cfg.tools_off[0] == 0, "set empties");
|
||||
|
||||
char nm[64];
|
||||
cst_tool_sanitize_name("mcp__my server__do.it", nm, sizeof(nm));
|
||||
EXPECT(strcmp(nm, "mcp__my_server__do_it") == 0, "sanitize");
|
||||
|
||||
EXPECT(cst_tool_unregister_owner("test:echo") == 1 &&
|
||||
!cst_tool_find("dyn_echo") && cst_tool_count() == base,
|
||||
"unregister by owner");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
dynamic_tools();
|
||||
cst_agent_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
|
||||
@@ -85,7 +180,7 @@ int main(void) {
|
||||
free(pdef);
|
||||
|
||||
/* /tools off: grep disappears everywhere */
|
||||
cfg.tools_off = 1ULL << cst_tool_index("grep");
|
||||
cst_tool_set_disabled(&cfg, "grep", 1);
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user