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:
WHC
2026-10-05 01:47:01 +08:00
parent 9877ff48b6
commit 2fde898da0
105 changed files with 19348 additions and 3786 deletions
+20
View File
@@ -0,0 +1,20 @@
CC ?= gcc
CFLAGS ?= -std=c11 -Wall -Wextra -O2
OBJS = main.o order.o report.o
.PHONY: all clean test
all: shop
shop: $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS)
%.o: %.c order.h report.h
$(CC) $(CFLAGS) -c -o $@ $<
test: shop
./tests/run.sh
clean:
rm -f shop $(OBJS)
+15
View File
@@ -0,0 +1,15 @@
# Task: rename across a small C project
`shop` is a tiny C program split over several files. Do the following, then stop:
1. Rename the function `compute_total` to `order_total` everywhere: the
declaration, the definition, every call site and the comments. Nothing
named `compute_total` may remain anywhere in the project.
2. `report.c` applies the 10% discount a second time, so the printed total is
too low. Fix it so the discount is applied exactly once (`order_total`
already applies it).
3. Run `make test` and make sure it passes.
4. Summarize the change in two or three sentences.
Find the call sites with the search tools instead of guessing. Edit only
what the task needs.
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Pass iff the rename is complete and the discount bug is fixed.
set -euo pipefail
cd "$(dirname "$0")"
if grep -rn --include='*.c' --include='*.h' compute_total . ; then
echo "check: compute_total still present"
exit 1
fi
grep -q 'int order_total(const int \*prices, int n);' order.h
make -s clean >/dev/null
make -s test
+19
View File
@@ -0,0 +1,19 @@
#include <stdio.h>
#include <stdlib.h>
#include "order.h"
#include "report.h"
int main(int argc, char **argv) {
int prices[64];
int n = 0;
for (int i = 1; i < argc && n < 64; i++)
prices[n++] = atoi(argv[i]);
if (n == 0) {
fprintf(stderr, "usage: shop PRICE_CENTS...\n");
return 2;
}
print_report(stdout, prices, n);
/* exit status: 0 when compute_total is positive */
return compute_total(prices, n) > 0 ? 0 : 1;
}
+17
View File
@@ -0,0 +1,17 @@
#include "order.h"
int compute_total(const int *prices, int n) {
int sum = 0;
for (int i = 0; i < n; i++)
sum += prices[i];
if (n > 3)
sum -= sum / 10; /* bulk discount */
return sum;
}
int count_expensive(const int *prices, int n, int limit) {
int c = 0;
for (int i = 0; i < n; i++)
c += prices[i] > limit;
return c;
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef ORDER_H
#define ORDER_H
/* Sum of prices in cents, with the 10% bulk discount applied when there
* are more than three items. */
int compute_total(const int *prices, int n);
/* Number of items that cost more than limit cents. */
int count_expensive(const int *prices, int n, int limit);
#endif
+12
View File
@@ -0,0 +1,12 @@
#include "report.h"
#include "order.h"
void print_report(FILE *out, const int *prices, int n) {
/* compute_total gives the discounted sum */
int total = compute_total(prices, n);
if (n > 3)
total -= total / 10;
fprintf(out, "items: %d\n", n);
fprintf(out, "expensive: %d\n", count_expensive(prices, n, 1000));
fprintf(out, "total: %d.%02d\n", total / 100, total % 100);
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef REPORT_H
#define REPORT_H
#include <stdio.h>
void print_report(FILE *out, const int *prices, int n);
#endif
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Exit 0 only when shop prints the expected reports.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
fail=0
expect() {
local want="$1"
shift
local got
got="$("$ROOT/shop" "$@")"
if [[ "$got" != "$want" ]]; then
printf 'FAIL: shop %s\n--- want\n%s\n--- got\n%s\n' "$*" "$want" "$got"
fail=1
else
echo "ok: shop $*"
fi
}
expect $'items: 2\nexpensive: 0\ntotal: 15.00' 500 1000
expect $'items: 4\nexpensive: 2\ntotal: 36.00' 500 1500 2000 0
expect $'items: 5\nexpensive: 0\ntotal: 45.00' 1000 1000 1000 1000 1000
if [[ "$fail" -ne 0 ]]; then
echo "tests: FAILED"
exit 1
fi
echo "tests: PASSED"