- Introduced `dstalk_lsp_plugin_test` for testing LSP plugin functionalities including `lsp_trim`, `lsp_frame_message`, and `lsp_parse_content_length`. - Created `dstalk_frontend_common` static library to encapsulate shared initialization logic for frontend components (CLI, GUI, Web). - Implemented configuration file discovery and service querying in `dstalk_frontend_init`. - Added internal headers for LSP and Anthropic plugins to facilitate unit testing. - Established a mailroom system for asynchronous message passing between stateless agents, enhancing coordination and context management.
9.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build & run
The repo ships its own toolchain under tools/ (clang-cl/clang, CMake, Ninja, Conan 2 in a venv). Build scripts prefer those over PATH.
- One-shot build (Windows):
build.bat— checks/installs toolchain, runs Conan install againstdeps/conanfile.txt, configures CMake with clang-cl, builds with Ninja intobuild/. - One-shot build (Linux/macOS):
bash build.sh [Release|Debug]— same flow with clang (or gcc fallback) and Ninja. - First-time setup:
tools/setup.batorbash tools/setup.shinstalls CMake, Ninja, and Conan 2 intotools/without touching system PATH. - CI-style build:
scripts/ci-build.sh/scripts/ci-build.bat— minimal Conan + CMake + Ninja flow used by CI. - Incremental rebuild after edits:
cd build && <tools/ninja or system ninja> -j<N>from insidebuild/. No need to re-run Conan unlessdeps/conanfile.txtchanges.
Build outputs:
- Executables →
build/bin/(dstalk_cli, test binaries, optionaldstalk_gui/dstalk_web) - Plugin DLLs →
build/plugins/(eachplugin_*target writes here, prefix stripped)
CMake options (root CMakeLists.txt):
DSTALK_BUILD_GUI=ON— also builds the SDL3 GUI frontend (off by default; requires SDL3 from Conan/system)DSTALK_BUILD_WEB=ON— also builds the Boost.Beast web frontendDSTALK_BUILD_TESTS=ON— on by default; turn off to skiptests/subdir
Tests
CTest, hand-rolled CHECK-macro tests (no GoogleTest — see "Network-restricted environments" below).
- Run all tests:
cd build && ctest --output-on-failure - Run one test:
cd build && ctest -R dstalk_smoke_test --output-on-failure(or any other target name fromtests/CMakeLists.txt) - Run a test binary directly:
build/bin/dstalk_smoke_test.exe - Coverage:
cmake --build build --target coverage(requires gcovr + gcov/llvm-cov; producesbuild/coverage/index.html). Only meaningful when configured with--coverageflags.
Test targets and what they cover (see tests/CMakeLists.txt for the authoritative list):
dstalk_smoke_test— loads real plugins frombuild/plugins/, end-to-end integration; also carries regression cases (R1-R4, W21.5 tool calls)dstalk_host_api_test,dstalk_event_bus_test,dstalk_service_registry_test— core unit tests; compile core sources directlydstalk_plugin_loader_test—PluginLoaderregression tests; setsDSTALK_TEST_PLUGINS_DIRtobuild/pluginsdstalk_context_plugin_test— token/trim/UTF-8 boundariesdstalk_anthropic_plugin_test,dstalk_openai_plugin_test— currently#includethe plugin.cppto reach static functions; linkai_commonand need its include dirdstalk_network_plugin_test—#includesnetwork_plugin.cpp; needs OpenSSLdstalk_lsp_plugin_test— testslsp_trim/lsp_frame_message/lsp_parse_content_lengthvialsp_internal.hpp
After changing the
dstalk_host_api_tvtable layout, clean rebuild is mandatory — stale.objfiles from incremental builds will surface as segfaults indstalk_smoke_test/dstalk_host_api_testbecause the test binary and plugin DLLs disagree on struct layout.rm -rf build && bash build.sh(or delete the specific stale.objfiles) before re-running ctest.
Architecture
Plugin-host design. One process loads a host DLL and many plugin DLLs over a C ABI.
Frontend (dstalk_cli / dstalk_gui / dstalk_web)
│ links dstalk_frontend_common (shared bootstrap: config discovery, init,
│ service queries, FrontendServices struct)
▼
dstalk_core.dll ─ PluginLoader · ServiceRegistry · EventBus · Config · Logging · Memory
▲
│ C ABI (dstalk_host.h)
│
Plugins (loaded from build/plugins/, each plugin_*.dll exports dstalk_plugin_init)
plugins_base/ config, file_io, lsp
plugins_middle/ network, session, tools (may depend on base)
plugins_upper/ context, openai, anthropic (may depend on base + middle)
ai_common (static lib, shared by openai + anthropic)
Plugin tiers matter for build order and dependency direction: plugins_upper may depend on plugins_middle may depend on plugins_base. Never let a lower tier depend on a higher one.
Service vtables are the only cross-DLL contract. Plugins register service vtables (e.g. dstalk_ai_service_t, dstalk_http_service_t, dstalk_session_service_t) with the host's ServiceRegistry. Other plugins query them by (name, min_version). The vtable shapes live in dstalk_core/include/dstalk/dstalk_services.h; the host API the loader passes into each plugin's on_init(const dstalk_host_api_t* host) lives in dstalk_host.h.
ai_common (plugins_upper/ai_common/) is a static library, not a plugin. It holds shared types (PluginConfig, ToolCallAccum, StreamContext) and utilities (secure_zero, extract_host_port, serialize_tool_calls, free_chat_result) used by both openai and anthropic plugins, all under namespace dstalk_ai. Each plugin still compiles as its own DLL and links ai_common privately.
Hard rules — violating these is undefined behavior
These come from docs/reference/plugin-abi.md. They are not style; they are correctness.
-
Cross-DLL heap discipline. Plugins must NOT call
std::malloc/std::free/std::strdup/new/deleteon data that crosses the host↔plugin boundary. Usehost->alloc/host->free/host->strdup(passed in viaon_init). Windows /MD CRTs and even some Linux/libc configs give each DLL its own heap — mismatched alloc/free crashes. -
API version is a hard match.
dstalk_plugin_info_t.api_versionmust equalDSTALK_API_VERSION(currently 1). The loader rejects mismatches. There is no backward compat — rebuild plugins against the new host. -
String ownership.
dstalk_chat_result_t.content/.error/.tool_calls_jsonare allocated withhost->strdupby the producing plugin; the caller frees them withhost->free. Never returnstd::string::c_str()or stack buffers across the ABI. -
C ABI + atomic globals. Service vtable function pointers and cached service pointers stored in plugin global state must be
std::atomic<T*>withmemory_order_acquire/release. Raw pointers race during shutdown (the anthropic plugin'sg_configwas a real data race fixed in W17). Same applies tog_host,g_http, etc. -
No new on plugin info strings.
name/version/descriptionindstalk_plugin_info_tonly need to live for the duration ofdstalk_plugin_init()— string literals are fine; the host copies them. -
api_keylifecycle. Onon_shutdown, overwrite key bytes viavolatile char*loop, then clear the string. Usedstalk_ai::secure_zero()fromai_common.
Build system facts that bite
- Conan provides only
boost::boost(the umbrella target) — granularboost::json/boost::asio/boost::beasttargets do not exist in this Conan setup. Don't migrate to them. - Every Boost-using target needs its own
find_package(Boost REQUIRED CONFIG)call beforetarget_link_libraries(... boost::boost). Doing it once at the root is not enough; Conan-generated config exposes the target per-subdir. dstalk_boost_config(defined indstalk_core/CMakeLists.txt) is an INTERFACE target carryingBOOST_ALL_NO_LIB/BOOST_ERROR_CODE_HEADER_ONLY/BOOST_JSON_HEADER_ONLY. Link it from any target using<boost/json.hpp>. Boost.JSON is header-only here, so each TU using it must#include <boost/json/src.hpp>in exactly one file (seedstalk_core/src/boost_json.cpp).- The network plugin links
openssl::openssldirectly and calls OpenSSL C APIs (SSL_set_tlsext_host_name,SSL_set1_host). Don't remove that link "because Boost.Asio already pulls SSL" — it doesn't, at least not for these symbols. - Plugin DLLs are written to
build/plugins/withPREFIX ""(soplugin_openai.dll, notlibplugin_openai.dll). The smoke test and plugin_loader_test look there.
Network-restricted environments
This repo has been built in environments where outbound network is locked down. Two consequences:
- Do not add
FetchContentfor GitHub-hosted deps.git cloneover smart-HTTP fails with "early EOF" through restrictive proxies (W17.3 lesson — GoogleTest dropped, tests use hand-rolledCHECKmacros instead). Vendor or skip. - Conan packages must be pre-cached in
~/.conan2/p/or available through an allowed mirror; first-runconan installfrom a clean cache will fail offline.
Repo-specific conventions
- README is in Chinese (Simplified). Comments and docs are routinely bilingual (Chinese first, English after a
/). Match the surrounding style in any file you edit. - Plugin code style. Each AI/network plugin has the same skeleton:
g_host(atomic),g_cfg,on_init/on_shutdown, service vtable, thendstalk_plugin_init()returning a staticdstalk_plugin_info_t. Keep that shape when adding a provider. - CRT.
CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL(/MD). All plugins and the host must agree.
The agents/ directory
agents/ (README, WORKFLOW.md, STATUS.md, per-agent profile.md files, groups/) describes a multi-agent collaboration mode used by the project owner with Claude. It is documentation, not code — nothing in the build references it. Treat its contents as historical context; do not invent or extend the "16-person team / waves / 6-stage workflow" apparatus unless the user explicitly asks for it. If the user does invoke it, the rules are in agents/WORKFLOW.md.