perf: faster builds, leaner daemon, smoother install/run (0.9.20)
Reuse libcstcore.a for tests with -MMD incremental deps; idle-backoff the parliament seat loop; defer curl init and cache settings; add systemd helper. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
*.o
|
||||
*.d
|
||||
cst-agent
|
||||
cst-agent.exe
|
||||
libcstcore.a
|
||||
tests/test_*
|
||||
!tests/test_*.c
|
||||
session.jsonl
|
||||
|
||||
+4
-8
@@ -400,17 +400,13 @@ if(CST_BUILD_WEBUI)
|
||||
list(APPEND _CST_CORE_TESTS test_webui)
|
||||
endif()
|
||||
foreach(t ${_CST_CORE_TESTS})
|
||||
add_executable(${t} tests/${t}.c ${CST_CORE_SOURCES})
|
||||
# The core now contains src/web_md.c, which needs sashidown's header and
|
||||
# archive exactly like the Makefile build does.
|
||||
# Link the shared cstcore archive instead of recompiling every core .c.
|
||||
add_executable(${t} tests/${t}.c)
|
||||
target_include_directories(${t} PRIVATE include "${CJSON_DIR}" "${SD_DIR}/include")
|
||||
target_compile_definitions(${t} PRIVATE _GNU_SOURCE)
|
||||
target_link_libraries(${t} PRIVATE
|
||||
"${SASHIDOWN_A}" CURL::libcurl Threads::Threads)
|
||||
target_link_libraries(${t} PRIVATE cstcore)
|
||||
add_dependencies(${t} sashidown)
|
||||
if(NOT WIN32)
|
||||
target_link_libraries(${t} PRIVATE m)
|
||||
else()
|
||||
if(WIN32)
|
||||
target_link_libraries(${t} PRIVATE ws2_32 crypt32 secur32 bcrypt advapi32 user32)
|
||||
endif()
|
||||
cst_win_exe(${t})
|
||||
|
||||
@@ -134,8 +134,9 @@ OBJ = $(SRC:.c=.o)
|
||||
# the GUI links the same engine the CLI/TUI/WebUI run. No TUI, no main.
|
||||
CORE_OBJ = $(CORE_SRC:.c=.o)
|
||||
CST_CORE_LIB = libcstcore.a
|
||||
HDRS = $(wildcard include/*.h) $(wildcard src/tools/*.h) $(CJSON_DIR)/cJSON.h \
|
||||
.gen/models_builtin.inc .gen/web_assets.inc
|
||||
# Generated embeds; per-file .d files (from -MMD) track the rest.
|
||||
GEN_INCS = .gen/models_builtin.inc .gen/web_assets.inc
|
||||
DEPFILES = $(OBJ:.o=.d)
|
||||
|
||||
TESTS = \
|
||||
tests/test_ctl_frontier \
|
||||
@@ -193,33 +194,29 @@ TEST_LIBS = -L$(SD_DIR) -lsashidown $(CURL_LIBS) $(RPATH_LIBS) $(TEST_SYS_LIBS)
|
||||
# keeps them, so the small tests link the OS layer too.
|
||||
TEST_OS_SRC = src/os_compat.c src/os_win.c src/util.c
|
||||
|
||||
.PHONY: all clean check demo sashidown-lib FORCE cmake-gcc cmake-clang asan install uninstall
|
||||
.PHONY: all clean check demo sashidown-lib cmake-gcc cmake-clang asan install \
|
||||
uninstall install-service
|
||||
|
||||
all: sashidown-lib cst-agent $(CST_CORE_LIB)
|
||||
|
||||
$(CST_CORE_LIB): $(CORE_OBJ) FORCE
|
||||
$(CST_CORE_LIB): $(CORE_OBJ)
|
||||
rm -f $@
|
||||
$(AR) rcs $@ $(CORE_OBJ)
|
||||
|
||||
cmake-gcc:
|
||||
cmake -B build-gcc -DCMAKE_C_COMPILER=gcc -DCST_BUILD_TUI=ON
|
||||
cmake --build build-gcc
|
||||
ctest --test-dir build-gcc --output-on-failure
|
||||
cmake --build build-gcc -j
|
||||
ctest --test-dir build-gcc --output-on-failure -j
|
||||
|
||||
cmake-clang:
|
||||
cmake -B build-clang -DCMAKE_C_COMPILER=clang -DCST_BUILD_TUI=ON
|
||||
cmake --build build-clang
|
||||
cmake --build build-clang -j
|
||||
|
||||
sashidown-lib: $(SD_DIR)/libsashidown.a
|
||||
|
||||
# The sub-make decides whether the archive is stale; FORCE only makes sure it
|
||||
# is asked, and -j builds wait for it before linking.
|
||||
$(SD_DIR)/libsashidown.a: FORCE
|
||||
# Always ask the sub-make; it no-ops when libsashidown.a is current.
|
||||
sashidown-lib:
|
||||
$(MAKE) -C $(SD_DIR) libsashidown.a
|
||||
|
||||
FORCE:
|
||||
|
||||
cst-agent: $(OBJ) $(SD_DIR)/libsashidown.a
|
||||
cst-agent: $(OBJ) sashidown-lib
|
||||
$(CC) $(CFLAGS) -o $@ $(OBJ) $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
# Built-in model data as a C byte list (included by model_registry.c).
|
||||
@@ -232,11 +229,15 @@ cst-agent: $(OBJ) $(SD_DIR)/libsashidown.a
|
||||
@mkdir -p .gen
|
||||
@sh scripts/embed_web.sh $@ $(WEB_DIR)
|
||||
|
||||
%.o: %.c $(HDRS)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
|
||||
# Incremental rebuilds: -MMD tracks real includes instead of every header.
|
||||
COMPILE_C = $(CC) $(CFLAGS) $(CPPFLAGS) -MMD -MP -c -o $@ $<
|
||||
|
||||
src/tui.o: src/tui.c $(HDRS) $(SD_DIR)/include/sashidown.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
|
||||
%.o: %.c
|
||||
$(COMPILE_C)
|
||||
|
||||
src/model_registry.o: .gen/models_builtin.inc
|
||||
src/webui.o: .gen/web_assets.inc
|
||||
src/tui.o: $(SD_DIR)/include/sashidown.h
|
||||
|
||||
tests/test_ctl_frontier: tests/test_ctl_frontier.c src/ctl_parse.c include/ctl.h $(TEST_OS_SRC)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_ctl_frontier.c src/ctl_parse.c $(TEST_OS_SRC) $(TEST_SYS_LIBS)
|
||||
@@ -244,7 +245,7 @@ tests/test_ctl_frontier: tests/test_ctl_frontier.c src/ctl_parse.c include/ctl.h
|
||||
tests/test_ctl_heredoc: tests/test_ctl_heredoc.c src/ctl_parse.c include/ctl.h $(TEST_OS_SRC)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_ctl_heredoc.c src/ctl_parse.c $(TEST_OS_SRC) $(TEST_SYS_LIBS)
|
||||
|
||||
tests/test_json_baseline: tests/test_json_baseline.c src/json_baseline.c $(CJSON_DIR)/cJSON.c $(HDRS) $(TEST_OS_SRC)
|
||||
tests/test_json_baseline: tests/test_json_baseline.c src/json_baseline.c $(CJSON_DIR)/cJSON.c $(TEST_OS_SRC)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_json_baseline.c src/json_baseline.c $(CJSON_DIR)/cJSON.c $(TEST_OS_SRC) $(TEST_SYS_LIBS)
|
||||
|
||||
tests/test_replay_starvation: tests/test_replay_starvation.c src/ctl_parse.c src/json_baseline.c $(CJSON_DIR)/cJSON.c $(TEST_OS_SRC)
|
||||
@@ -265,32 +266,47 @@ tests/test_pdf_text: tests/test_pdf_text.c src/pdf_text.c include/cst_pdf.h $(TE
|
||||
tests/test_markdown: tests/test_markdown.c $(SD_DIR)/libsashidown.a
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_markdown.c $(LDFLAGS) -lsashidown -lm
|
||||
|
||||
tests/test_%: tests/test_%.c $(CORE_SRC) $(HDRS)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(CORE_SRC) $(TEST_LIBS)
|
||||
# Core tests reuse libcstcore.a — avoid recompiling ~35 .c files per binary.
|
||||
# Do not depend on the phony sashidown-lib target or every test relinks each check.
|
||||
tests/test_%: tests/test_%.c $(CST_CORE_LIB) $(SD_DIR)/libsashidown.a
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(CST_CORE_LIB) $(TEST_LIBS)
|
||||
|
||||
$(TESTS): sashidown-lib
|
||||
$(SD_DIR)/libsashidown.a: sashidown-lib
|
||||
|
||||
check: $(TESTS)
|
||||
# Build test binaries with make -j; run them one-by-one for readable logs.
|
||||
check: sashidown-lib $(TESTS)
|
||||
@set -e; for t in $(TESTS); do echo "== $$t"; ./$$t; done
|
||||
|
||||
# AddressSanitizer + UBSan build of the agent and all tests.
|
||||
# Under a ptrace-based sandbox (some Termux setups) LeakSanitizer aborts with
|
||||
# "Tracer caught signal 11"; add ASAN_OPTIONS=detect_leaks=0 there.
|
||||
# AddressSanitizer + UBSan rebuild (clears objects so sanitizer flags apply).
|
||||
asan:
|
||||
$(MAKE) clean
|
||||
$(MAKE) sashidown-lib
|
||||
$(MAKE) CFLAGS="-std=c11 -Wall -Wextra -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer" cst-agent check
|
||||
$(MAKE) CFLAGS="-std=c11 -Wall -Wextra -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer" \
|
||||
sashidown-lib cst-agent check
|
||||
|
||||
demo: cst-agent
|
||||
./cst-agent --demo-ctl --workspace .
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) cst-agent $(CST_CORE_LIB) $(TESTS) .gen/models_builtin.inc \
|
||||
.gen/web_assets.inc
|
||||
rm -f $(OBJ) $(DEPFILES) cst-agent $(CST_CORE_LIB) $(TESTS) \
|
||||
.gen/models_builtin.inc .gen/web_assets.inc
|
||||
|
||||
install: cst-agent
|
||||
install -d "$(DESTDIR)$(PREFIX)/bin"
|
||||
install -m 0755 cst-agent "$(DESTDIR)$(PREFIX)/bin/cst-agent"
|
||||
|
||||
# Optional systemd --user unit for the parliament seat service.
|
||||
install-service: install
|
||||
install -d "$(DESTDIR)$(HOME)/.config/systemd/user"
|
||||
sed -e 's|@BIN@|$(PREFIX)/bin/cst-agent|g' \
|
||||
scripts/cst-agent-parliament.service.in \
|
||||
> "$(DESTDIR)$(HOME)/.config/systemd/user/cst-agent-parliament.service"
|
||||
@echo "Installed user unit. Enable with:"
|
||||
@echo " systemctl --user daemon-reload"
|
||||
@echo " systemctl --user enable --now cst-agent-parliament.service"
|
||||
@echo "(set CST_REALM_PASSWORD / CST_INSTANCE_ID in the drop-in or environment)"
|
||||
|
||||
uninstall:
|
||||
rm -f "$(DESTDIR)$(PREFIX)/bin/cst-agent"
|
||||
rm -f "$(HOME)/.config/systemd/user/cst-agent-parliament.service"
|
||||
|
||||
-include $(DEPFILES)
|
||||
|
||||
@@ -41,8 +41,8 @@ irm https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/install_window
|
||||
也可以下载 Release 里的安装包 `cst-agent-VERSION-windows-x86_64-setup.exe`(按用户安装、自动加 PATH、
|
||||
开始菜单有 WebUI 入口),或便携版 zip。详见 [docs/README-windows.md](docs/README-windows.md)。
|
||||
|
||||
一键脚本可以重复运行:已有仓库会 `git pull --ff-only`,代码有更新时干净重编,然后重新安装;
|
||||
同时装一个 `cst-agent-update` 命令(等同重跑脚本)。NixOS 上会自动进 `nix-shell` 编译。
|
||||
一键脚本可以重复运行:已有仓库会 `git pull --ff-only`,**增量编译**后重新安装(`CST_AGENT_CLEAN=1` 才强制 clean);
|
||||
同时装 `cst-agent-update` 与 `cst-agent-service`(议会席位服务辅助)。NixOS 上会自动进 `nix-shell` 编译。
|
||||
|
||||
之后再启动会沿用上次的 provider、模型和推理强度(low / medium / high / max),不需要任何参数或环境变量。
|
||||
界面里用 `/provider` 切换(没保存过 key 的会当场询问),`/model`、`/effort` 切换模型和推理强度,`/key` 更换 key。
|
||||
@@ -90,14 +90,15 @@ scripts/run_matrix.sh -p "native-eos native-inc ctl ctl-heredoc xml" \
|
||||
|
||||
```bash
|
||||
cd CST-agent
|
||||
make # 依赖 pkg-config 找 libcurl / ncursesw(找不到时退回 ncurses)
|
||||
make check # 单元测试 + 截断实验(含真实录制流)
|
||||
make asan # AddressSanitizer + UBSan 构建并跑全部测试
|
||||
make -j$(nproc) # 增量编译;头文件依赖走 -MMD,测试复用 libcstcore.a
|
||||
make -j$(nproc) check # 并行编测试二进制,再串行跑
|
||||
make asan # AddressSanitizer + UBSan 构建并跑全部测试
|
||||
make CC=tcc AR=ar check # TinyCC:整套构建 + 测试十几秒(CI 的校验就是这条)
|
||||
make install PREFIX=$HOME/.local # 或 sudo make install(默认 /usr/local)
|
||||
make install-service PREFIX=$HOME/.local # 可选:systemd --user 议会服务单元
|
||||
```
|
||||
|
||||
NixOS:`nix-shell`(仓库自带 [`shell.nix`](shell.nix))后再 `make`。
|
||||
NixOS:`nix-shell --run 'make -j$(nproc) && make install PREFIX=$HOME/.local'`(CLI);Qt 桌面用 `nix-build` / `cst-agent-app`。
|
||||
macOS:`brew install curl ncurses pkg-config` 后直接 `make`(Makefile 会找 Homebrew 的 keg-only 库)。
|
||||
各平台细节与验收清单见 [docs/compat.md](docs/compat.md)。
|
||||
|
||||
|
||||
@@ -35,6 +35,22 @@ cst-agent # TUI: /status 或 /realm
|
||||
cst-agent --service-stop
|
||||
```
|
||||
|
||||
### 常驻服务(推荐)
|
||||
|
||||
登录后会 fork 后台席位进程;也可用 systemd --user 托管前台循环(崩溃自动拉起):
|
||||
|
||||
```bash
|
||||
# 安装脚本附带的辅助命令
|
||||
cst-agent-service install-unit
|
||||
# 在 ~/.config/cst-agent/parliament.env 写入:
|
||||
# CST_REALM_PASSWORD=…
|
||||
# CST_INSTANCE_ID=my-laptop
|
||||
systemctl --user enable --now cst-agent-parliament.service
|
||||
cst-agent-service status
|
||||
```
|
||||
|
||||
或手动前台:`CST_REALM_PASSWORD=… cst-agent --service-fg`。
|
||||
|
||||
密码 Realm 开启后,直接打开 agent 会要求登录(TTY 下可当场输入密码)。
|
||||
|
||||
## 议员协作协议
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#define CST_VERSION_MAJOR 0
|
||||
#define CST_VERSION_MINOR 9
|
||||
#define CST_VERSION_PATCH 19
|
||||
#define CST_VERSION_PATCH 20
|
||||
|
||||
#define CST_VERSION_STR_(x) #x
|
||||
#define CST_VERSION_STR(x) CST_VERSION_STR_(x)
|
||||
|
||||
@@ -84,15 +84,20 @@ main() {
|
||||
|
||||
local jobs
|
||||
jobs="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
|
||||
echo "==> build"
|
||||
if ((changed)); then
|
||||
echo "==> build (incremental, -j$jobs)"
|
||||
# Prefer incremental rebuilds. Set CST_AGENT_CLEAN=1 to force a clean tree.
|
||||
if [[ "${CST_AGENT_CLEAN:-0}" == "1" ]]; then
|
||||
run_make clean >/dev/null 2>&1 || true
|
||||
fi
|
||||
# `changed` is only used so re-exec after pull still builds; keep for clarity.
|
||||
: "${changed:=0}"
|
||||
if ! run_make -j"$jobs"; then
|
||||
deps_hint
|
||||
return 1
|
||||
fi
|
||||
run_make check >/dev/null 2>&1 || echo "warning: some tests failed (run 'make check')"
|
||||
if [[ "${CST_AGENT_SKIP_CHECK:-0}" != "1" ]]; then
|
||||
run_make -j"$jobs" check >/dev/null 2>&1 || echo "warning: some tests failed (run 'make check')"
|
||||
fi
|
||||
|
||||
echo "==> install to $prefix/bin"
|
||||
mkdir -p "$prefix/bin"
|
||||
@@ -103,6 +108,35 @@ export CST_AGENT_PREFIX="\${CST_AGENT_PREFIX:-$prefix}"
|
||||
exec bash "$dir/scripts/bootstrap_linux.sh" "$dir" "\$@"
|
||||
EOF
|
||||
chmod 0755 "$prefix/bin/cst-agent-update"
|
||||
cat >"$prefix/bin/cst-agent-service" <<EOF
|
||||
#!$(command -v bash)
|
||||
# Manage the parliament seat service (foreground under systemd, or --service).
|
||||
set -euo pipefail
|
||||
BIN="$prefix/bin/cst-agent"
|
||||
cmd="\${1:-status}"
|
||||
case "\$cmd" in
|
||||
status)
|
||||
"\$BIN" --version
|
||||
if command -v systemctl >/dev/null 2>&1 && systemctl --user is-active cst-agent-parliament.service >/dev/null 2>&1; then
|
||||
systemctl --user status cst-agent-parliament.service --no-pager
|
||||
else
|
||||
"\$BIN" --service-stop >/dev/null 2>&1 || true
|
||||
echo "parliament service: not managed by systemd --user (use --login / --service)"
|
||||
fi
|
||||
;;
|
||||
install-unit)
|
||||
make -C "$dir" install-service PREFIX="$prefix"
|
||||
;;
|
||||
start) systemctl --user start cst-agent-parliament.service ;;
|
||||
stop) systemctl --user stop cst-agent-parliament.service; "\$BIN" --service-stop || true ;;
|
||||
restart) systemctl --user restart cst-agent-parliament.service ;;
|
||||
*)
|
||||
echo "usage: cst-agent-service {status|install-unit|start|stop|restart}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod 0755 "$prefix/bin/cst-agent-service"
|
||||
|
||||
echo
|
||||
echo "OK: $("$prefix/bin/cst-agent" --version)"
|
||||
@@ -110,7 +144,8 @@ EOF
|
||||
*":$prefix/bin:"*) ;;
|
||||
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
|
||||
esac
|
||||
echo "Run 'cst-agent' inside a project directory; upgrade later with 'cst-agent-update'."
|
||||
echo "Run 'cst-agent' inside a project directory; upgrade with 'cst-agent-update'."
|
||||
echo "Parliament seat service helper: 'cst-agent-service status|install-unit'."
|
||||
echo "The first start asks for the provider and its API key and saves them."
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=CST-Agent parliament seat service
|
||||
Documentation=file://%h/CST-agent/docs/parliament.md
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# Prefer --service-fg under systemd so the unit tracks the real process.
|
||||
ExecStart=@BIN@ --service-fg --peer-port ${CST_PEER_PORT:-9100}
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
Environment=CST_INSTANCE_ID=%H
|
||||
# Put the Realm password in a drop-in, e.g.:
|
||||
# ~/.config/systemd/user/cst-agent-parliament.service.d/override.conf
|
||||
# [Service]
|
||||
# Environment=CST_REALM_PASSWORD=…
|
||||
EnvironmentFile=-%h/.config/cst-agent/parliament.env
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
+27
-4
@@ -139,6 +139,26 @@ int cst_discover_local_put(const char *realm_fp, const char *instance_id,
|
||||
arr = cJSON_CreateArray();
|
||||
cJSON_AddItemToObject(root, "instances", arr);
|
||||
}
|
||||
time_t now = time(NULL);
|
||||
const cJSON *it;
|
||||
cJSON_ArrayForEach(it, arr) {
|
||||
const cJSON *iid = cJSON_GetObjectItemCaseSensitive(it, "iid");
|
||||
const cJSON *rid = cJSON_GetObjectItemCaseSensitive(it, "rid");
|
||||
const cJSON *port = cJSON_GetObjectItemCaseSensitive(it, "port");
|
||||
const cJSON *updated = cJSON_GetObjectItemCaseSensitive(it, "updated");
|
||||
if (!cJSON_IsString(iid) || strcmp(iid->valuestring, instance_id) != 0)
|
||||
continue;
|
||||
if (cJSON_IsString(rid) && !strcmp(rid->valuestring, realm_fp) &&
|
||||
cJSON_IsNumber(port) && port->valueint == peer_port &&
|
||||
cJSON_IsNumber(updated) &&
|
||||
now - (time_t)updated->valuedouble < 60) {
|
||||
/* Same seat advertisement still fresh — skip disk rewrite. */
|
||||
cJSON_Delete(root);
|
||||
pthread_mutex_unlock(&local_mu);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
local_remove_iid(arr, instance_id);
|
||||
cJSON *row = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(row, "rid", realm_fp);
|
||||
@@ -146,7 +166,7 @@ int cst_discover_local_put(const char *realm_fp, const char *instance_id,
|
||||
cJSON_AddStringToObject(row, "name", name ? name : "");
|
||||
cJSON_AddNumberToObject(row, "port", peer_port);
|
||||
cJSON_AddNumberToObject(row, "pid", (double)os_getpid());
|
||||
cJSON_AddNumberToObject(row, "updated", (double)time(NULL));
|
||||
cJSON_AddNumberToObject(row, "updated", (double)now);
|
||||
cJSON_AddItemToArray(arr, row);
|
||||
int rc = save_local(root);
|
||||
cJSON_Delete(root);
|
||||
@@ -283,12 +303,15 @@ static void *discover_main(void *arg) {
|
||||
cst_discover_t *d = arg;
|
||||
int ticks = 0;
|
||||
while (!d->stop) {
|
||||
if ((ticks++ % 10) == 0) {
|
||||
/* ~100ms recv timeout: beacon every ~5s, rewrite local registry ~60s. */
|
||||
if ((ticks % 50) == 0) {
|
||||
send_beacon(d);
|
||||
(void)cst_discover_local_put(d->realm_fp, d->instance_id, d->name,
|
||||
d->peer_port);
|
||||
if ((ticks / 50) % 12 == 0)
|
||||
(void)cst_discover_local_put(d->realm_fp, d->instance_id, d->name,
|
||||
d->peer_port);
|
||||
ingest_local(d);
|
||||
}
|
||||
ticks++;
|
||||
if (d->sock == DISC_BAD) {
|
||||
struct timespec ts = {0, 100 * 1000 * 1000};
|
||||
nanosleep(&ts, NULL);
|
||||
|
||||
+6
-1
@@ -20,7 +20,12 @@ void cst_extensions_wait(FILE *log) {
|
||||
cst_mcp_counts(&ready, &total);
|
||||
if (total == 0)
|
||||
return;
|
||||
cst_mcp_wait_ready(30000);
|
||||
/* Default 8s (was 30s); override with CST_MCP_WAIT_MS. */
|
||||
const char *e = getenv("CST_MCP_WAIT_MS");
|
||||
int ms = e && atoi(e) >= 0 ? atoi(e) : 8000;
|
||||
if (ms > 60000)
|
||||
ms = 60000;
|
||||
cst_mcp_wait_ready(ms);
|
||||
cst_mcp_info_t info[CST_MCP_MAX];
|
||||
int n = cst_mcp_info(info, CST_MCP_MAX);
|
||||
for (int i = 0; log && i < n; i++) {
|
||||
|
||||
+54
-36
@@ -22,6 +22,21 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/* Defer libcurl init: --version / --login / parliament CLI need none of it. */
|
||||
static int g_curl_ready;
|
||||
static void cst_curl_ensure(void) {
|
||||
if (!g_curl_ready) {
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
g_curl_ready = 1;
|
||||
}
|
||||
}
|
||||
static void cst_curl_cleanup(void) {
|
||||
if (g_curl_ready) {
|
||||
curl_global_cleanup();
|
||||
g_curl_ready = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(const char *argv0) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s [options] (works in the current directory)\n"
|
||||
@@ -311,7 +326,6 @@ int main(int argc, char **argv) {
|
||||
"1903 or later is required); non-ASCII text may break\n");
|
||||
os_set_prog_path(argv[0]);
|
||||
os_temp_dir_export();
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
cst_agent_config_t cfg = {0};
|
||||
char cwd[4096];
|
||||
@@ -532,7 +546,7 @@ int main(int argc, char **argv) {
|
||||
case 'r':
|
||||
if (cst_protocol_parse(optarg, &cfg.protocol) != 0) {
|
||||
fprintf(stderr, "unknown protocol: %s\n", optarg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
@@ -545,7 +559,7 @@ int main(int argc, char **argv) {
|
||||
if (cst_tool_profile_parse(optarg, &cfg.tool_profile) != 0) {
|
||||
fprintf(stderr, "unknown tool profile: %s (default | minimal)\n",
|
||||
optarg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
@@ -580,14 +594,14 @@ int main(int argc, char **argv) {
|
||||
case 'A':
|
||||
if (cst_approval_parse(optarg, &approval) != 0) {
|
||||
fprintf(stderr, "bad --approval %s (ask | auto | yolo)\n", optarg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
if (parse_confirm(optarg, &cfg) != 0) {
|
||||
fprintf(stderr, "bad --confirm %s (shell,write,net | none)\n", optarg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
@@ -595,7 +609,7 @@ int main(int argc, char **argv) {
|
||||
if (!cst_effort_valid(optarg)) {
|
||||
fprintf(stderr, "bad --effort %s (low | medium | high | max)\n",
|
||||
optarg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
snprintf(cfg.effort, sizeof(cfg.effort), "%s", optarg);
|
||||
@@ -603,21 +617,22 @@ int main(int argc, char **argv) {
|
||||
case 'M':
|
||||
if (!cst_mode_valid(optarg)) {
|
||||
fprintf(stderr, "bad --mode %s (coding | general)\n", optarg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
snprintf(cfg.mode, sizeof(cfg.mode), "%s", optarg);
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 0;
|
||||
case 0x100: {
|
||||
const char *only = optarg;
|
||||
if (!only && optind < argc && argv[optind][0] != '-')
|
||||
only = argv[optind++];
|
||||
cst_curl_ensure();
|
||||
int rc = list_models(only);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
case 0x101:
|
||||
@@ -626,7 +641,7 @@ int main(int argc, char **argv) {
|
||||
case 0x102:
|
||||
if (parse_port(optarg, &web.port) != 0) {
|
||||
fprintf(stderr, "--web-port %s is not a port (0-65535)\n", optarg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
web_mode = 1;
|
||||
@@ -652,30 +667,30 @@ int main(int argc, char **argv) {
|
||||
break;
|
||||
case 'V':
|
||||
printf("cst-agent %s\n", CST_VERSION);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 0;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_migrate) {
|
||||
int rc = migrate_store(cfg.workspace_root);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (do_realm_new) {
|
||||
int rc = cst_parliament_cli_realm_new();
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_realm_pw_init) {
|
||||
const char *pw = getenv("CST_REALM_PASSWORD");
|
||||
int rc = cst_parliament_cli_realm_password_init(pw);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_login) {
|
||||
@@ -687,17 +702,17 @@ int main(int argc, char **argv) {
|
||||
iid = iidbuf;
|
||||
}
|
||||
int rc = cst_parliament_cli_login(pw, iid, realm_label, discover_peer_port);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_new_project) {
|
||||
int rc = cst_parliament_cli_new_project(new_project_id);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_service_stop) {
|
||||
int rc = cst_parliament_cli_service_stop();
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_service || do_service_fg) {
|
||||
@@ -710,12 +725,12 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
int rc = cst_parliament_cli_service(pw, iid, discover_peer_port,
|
||||
do_service_fg);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_realm_enroll) {
|
||||
int rc = cst_parliament_cli_realm_enroll(realm_enroll_id, realm_label);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_peer_listen) {
|
||||
@@ -723,7 +738,7 @@ int main(int argc, char **argv) {
|
||||
if (!iid)
|
||||
iid = getenv("CST_INSTANCE_ID");
|
||||
int rc = cst_parliament_cli_peer_listen(peer_listen_port, iid);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_peer_dial) {
|
||||
@@ -743,13 +758,13 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
int rc = cst_parliament_cli_peer_dial(host, port, iid);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_discover) {
|
||||
const char *iid = parliament_instance ? parliament_instance : getenv("CST_INSTANCE_ID");
|
||||
int rc = cst_parliament_cli_discover(iid, discover_peer_port, discover_wait_ms);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -778,7 +793,7 @@ int main(int argc, char **argv) {
|
||||
fprintf(stderr, "%s\n", err[0] ? err : "login required");
|
||||
if (pw == pwbuf)
|
||||
memset(pwbuf, 0, sizeof(pwbuf));
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
free(hand);
|
||||
@@ -792,7 +807,7 @@ int main(int argc, char **argv) {
|
||||
fprintf(stderr,
|
||||
"password Realm active: run --login (or set "
|
||||
"CST_REALM_PASSWORD) before opening the agent\n");
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -800,10 +815,13 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (do_list) {
|
||||
int rc = list_sessions(cfg.workspace_root);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Agent / TUI / WebUI / headless need libcurl; parliament CLI above does not. */
|
||||
cst_curl_ensure();
|
||||
|
||||
/* Explicit option > environment > last used (settings.json) > preset. */
|
||||
cst_settings_t saved;
|
||||
cst_settings_load(&saved);
|
||||
@@ -815,7 +833,7 @@ int main(int argc, char **argv) {
|
||||
prov = cst_provider_find(provider_name);
|
||||
if (!prov) {
|
||||
fprintf(stderr, "unknown provider: %s\n", provider_name);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -894,7 +912,7 @@ int main(int argc, char **argv) {
|
||||
if (rc != 0 || cst_agent_store_path_for_id(cfg.workspace_root, id,
|
||||
cfg.session_path,
|
||||
sizeof(cfg.session_path)) != 0) {
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
cst_agent_store_set_current(cfg.workspace_root, id);
|
||||
@@ -906,7 +924,7 @@ int main(int argc, char **argv) {
|
||||
sizeof(id)) != 0) {
|
||||
fprintf(stderr, "failed to init .agent/sessions under %s\n",
|
||||
cfg.workspace_root);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "session %s\n", id);
|
||||
@@ -914,7 +932,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (do_demo) {
|
||||
int rc = demo_ctl_impl(cfg.workspace_root);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -924,7 +942,7 @@ int main(int argc, char **argv) {
|
||||
if (cst_creds_prompt_console(cfg.provider, cfg.api_key,
|
||||
sizeof(cfg.api_key)) != 0) {
|
||||
fprintf(stderr, "no API key entered\n");
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
char dir[1024] = "the config directory";
|
||||
@@ -936,7 +954,7 @@ int main(int argc, char **argv) {
|
||||
dir);
|
||||
/* --ask-key alone: save and exit */
|
||||
if (force_ask_key && !cfg.headless && !prompt) {
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -945,7 +963,7 @@ int main(int argc, char **argv) {
|
||||
"no API key for %s: start cst-agent once to enter it, or run "
|
||||
"cst-agent --ask-key --provider %s\n",
|
||||
cfg.provider, cfg.provider);
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
/* Router presets have no fixed model: take the first one the API lists
|
||||
@@ -968,7 +986,7 @@ int main(int argc, char **argv) {
|
||||
} else {
|
||||
fprintf(stderr, "no model for %s: pass --model (%s)\n", cfg.provider,
|
||||
err[0] ? err : "the API listed none");
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -989,7 +1007,7 @@ int main(int argc, char **argv) {
|
||||
cst_agent_t *agent = cst_agent_create(&cfg);
|
||||
if (!agent) {
|
||||
fprintf(stderr, "failed to create agent (session path?)\n");
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1021,6 +1039,6 @@ int main(int argc, char **argv) {
|
||||
|
||||
cst_agent_destroy(agent);
|
||||
cst_extensions_unload();
|
||||
curl_global_cleanup();
|
||||
cst_curl_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
+22
-12
@@ -272,33 +272,43 @@ int cst_parliament_daemon_run(const char *password, const char *instance_id,
|
||||
signal(SIGHUP, on_sig);
|
||||
#endif
|
||||
g_stop = 0;
|
||||
int tick = 0;
|
||||
int tick = 0, idle = 0;
|
||||
while (!g_stop) {
|
||||
cst_peer_session_t sess;
|
||||
char herr[128];
|
||||
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, 500, &sess, herr,
|
||||
sizeof(herr)) == 0) {
|
||||
/* Accept wait backs off when idle so a quiet seat barely wakes. */
|
||||
int accept_ms = idle > 20 ? 2000 : idle > 5 ? 1000 : 400;
|
||||
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, accept_ms, &sess,
|
||||
herr, sizeof(herr)) == 0) {
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
idle = 0;
|
||||
} else {
|
||||
idle++;
|
||||
}
|
||||
int push = cst_parliament_sync_pending();
|
||||
tick++;
|
||||
if ((push || tick % 10 == 0) && disc) {
|
||||
/* Dial peers on push, or periodically (slower while idle). */
|
||||
int period = idle > 20 ? 45 : idle > 5 ? 20 : 8;
|
||||
if ((push || tick % period == 0) && disc) {
|
||||
cst_peer_info_t peers[16];
|
||||
int n = cst_discover_peers(disc, peers, 16);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (peers[i].port <= 0)
|
||||
continue;
|
||||
if (cst_peer_dial(peers[i].host, peers[i].port, &cert, sk, realm.pk,
|
||||
3000, &sess, herr, sizeof(herr)) == 0) {
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
if (n > 0 || push) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (peers[i].port <= 0)
|
||||
continue;
|
||||
if (cst_peer_dial(peers[i].host, peers[i].port, &cert, sk, realm.pk,
|
||||
3000, &sess, herr, sizeof(herr)) == 0) {
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
idle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (push)
|
||||
cst_parliament_sync_ack();
|
||||
}
|
||||
sleep_ms(200);
|
||||
/* No extra sleep: accept_ms already paced the loop. */
|
||||
}
|
||||
if (disc)
|
||||
cst_discover_stop(disc);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
static int settings_path(char *out, size_t n) {
|
||||
char dir[OS_PATH_MAX];
|
||||
@@ -20,11 +22,32 @@ static void copy_str(char *dst, size_t n, const cJSON *v) {
|
||||
snprintf(dst, n, "%s", v->valuestring);
|
||||
}
|
||||
|
||||
static struct {
|
||||
int valid;
|
||||
time_t mtime;
|
||||
off_t size;
|
||||
cst_settings_t s;
|
||||
} g_settings_cache;
|
||||
|
||||
static void settings_cache_invalidate(void) {
|
||||
g_settings_cache.valid = 0;
|
||||
g_settings_cache.mtime = 0;
|
||||
g_settings_cache.size = 0;
|
||||
}
|
||||
|
||||
int cst_settings_load(cst_settings_t *s) {
|
||||
memset(s, 0, sizeof(*s));
|
||||
char path[OS_PATH_MAX];
|
||||
if (settings_path(path, sizeof(path)) != 0)
|
||||
return -1;
|
||||
struct stat st;
|
||||
int have_st = stat(path, &st) == 0;
|
||||
if (have_st && g_settings_cache.valid &&
|
||||
g_settings_cache.mtime == st.st_mtime &&
|
||||
g_settings_cache.size == st.st_size) {
|
||||
*s = g_settings_cache.s;
|
||||
return 0;
|
||||
}
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp)
|
||||
return -1;
|
||||
@@ -94,10 +117,19 @@ int cst_settings_load(cst_settings_t *s) {
|
||||
s->nparams++;
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
if (!have_st)
|
||||
have_st = stat(path, &st) == 0;
|
||||
if (have_st) {
|
||||
g_settings_cache.s = *s;
|
||||
g_settings_cache.mtime = st.st_mtime;
|
||||
g_settings_cache.size = st.st_size;
|
||||
g_settings_cache.valid = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_settings_save(const cst_settings_t *s) {
|
||||
settings_cache_invalidate();
|
||||
char dir[OS_PATH_MAX], path[OS_PATH_MAX], tmp[OS_PATH_MAX + 8];
|
||||
if (os_config_dir(dir, sizeof(dir)) != 0 || os_mkdir_p(dir, 0700) != 0 ||
|
||||
settings_path(path, sizeof(path)) != 0)
|
||||
|
||||
Reference in New Issue
Block a user