Wave 5+6: plugin ABI hardening, build modernization, ABI/security docs
Wave 5 (9 parallel agents): - W1.1 atomic diag callback + DLL handle release on shutdown (lin) - W2.1 unify cross-DLL heap discipline (host->alloc/free/strdup) (chen) - W2.2 secure_zero api_key on shutdown for deepseek/anthropic (cao) - W3 CMake modernization: target-based cxx_std_20, dstalk_boost_config INTERFACE lib, root-level RUNTIME_OUTPUT_DIRECTORY (hu) - W4 GitHub Actions CI with dynamic Linux/Windows matrix (ma) - W5.1 SSE buffer_body to cut peak memory ~67% on 32K streams (zhou) - W6.1 LSP JSON-RPC frame parser hardened against header reordering (sun) - W7 smoke test: copy plugin DLLs post-build + Boost.JSON src.hpp fix for full 9-plugin load coverage (wang) - W8.1 README slimmed 398->92, Diataxis docs/ skeleton (deng) Wave 6 (6 parallel agents): - W9.1 docs/explanation: architecture + plugin-lifecycle (deng) - W9.3 log credential leak audit (0 vulns, audit trail in docs/explanation/security-logging.md) (cao) - W9.4 docs/reference/plugin-abi.md - 7-point ABI contract (lin) - W9.6 CLI /history command + status integration (zhao) - W9.8 plugin_loader fault tolerance: per-plugin failure no longer aborts dstalk_init (huang) - W9.10 host_api unit tests: tests/host_api_test.cpp, 8 cases (liu) CEO oversight (preexisting bugs fixed during Wave 5 verification): - lsp_plugin.cpp:449 forward decl mismatch (int vs void) - tools_plugin.cpp:109 missing forward decl Multi-agent collaboration framework: - agents/WORKFLOW.md: 6-stage protocol, two-tier governance, prompt template, technical constraints registry Build: cmake --build 0 error / 0 warning. Tests: 2/2 100% pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,14 @@
|
||||
find_package(Boost REQUIRED CONFIG)
|
||||
find_package(OpenSSL REQUIRED CONFIG)
|
||||
|
||||
# 统一的 Boost 编译宏 (header-only 模式)
|
||||
add_library(dstalk_boost_config INTERFACE)
|
||||
target_compile_definitions(dstalk_boost_config INTERFACE
|
||||
BOOST_ALL_NO_LIB
|
||||
BOOST_ERROR_CODE_HEADER_ONLY
|
||||
BOOST_JSON_HEADER_ONLY
|
||||
)
|
||||
|
||||
add_library(dstalk SHARED
|
||||
src/host.cpp
|
||||
src/config_store.cpp
|
||||
@@ -20,8 +28,11 @@ target_include_directories(dstalk
|
||||
PRIVATE src
|
||||
)
|
||||
|
||||
target_compile_features(dstalk PUBLIC cxx_std_20)
|
||||
|
||||
target_link_libraries(dstalk
|
||||
PRIVATE
|
||||
dstalk_boost_config
|
||||
boost::boost
|
||||
openssl::openssl
|
||||
)
|
||||
@@ -35,9 +46,6 @@ endif()
|
||||
target_compile_definitions(dstalk
|
||||
PRIVATE
|
||||
DSTALK_BUILD_DLL
|
||||
BOOST_ALL_NO_LIB
|
||||
BOOST_ERROR_CODE_HEADER_ONLY
|
||||
BOOST_JSON_HEADER_ONLY
|
||||
INTERFACE
|
||||
DSTALK_USE_DLL
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "service_registry.hpp"
|
||||
#include "plugin_loader.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -24,7 +25,7 @@ namespace {
|
||||
dstalk::EventBus* g_event_bus = nullptr;
|
||||
dstalk::ServiceRegistry* g_service_registry = nullptr;
|
||||
dstalk::PluginLoader* g_plugin_loader = nullptr;
|
||||
static dstalk_diag_cb g_diag_callback = nullptr;
|
||||
static std::atomic<dstalk_diag_cb> g_diag_callback{nullptr};
|
||||
|
||||
// ---- 内部辅助 ----
|
||||
|
||||
@@ -50,10 +51,11 @@ namespace {
|
||||
vfprintf(stderr, fmt, args);
|
||||
fprintf(stderr, "\n");
|
||||
// 转发到诊断回调
|
||||
if (g_diag_callback) {
|
||||
auto cb = g_diag_callback.load(std::memory_order_acquire);
|
||||
if (cb) {
|
||||
char buf[1024];
|
||||
vsnprintf(buf, sizeof(buf), fmt, args_copy);
|
||||
g_diag_callback(level, nullptr, 0, nullptr, buf);
|
||||
cb(level, nullptr, 0, nullptr, buf);
|
||||
}
|
||||
va_end(args_copy);
|
||||
}
|
||||
@@ -300,7 +302,7 @@ DSTALK_API void dstalk_free(void* ptr) { free(ptr); }
|
||||
DSTALK_API char* dstalk_strdup(const char* s) { return host_strdup(s); }
|
||||
|
||||
DSTALK_API void dstalk_set_diag_callback(dstalk_diag_cb cb) {
|
||||
g_diag_callback = cb;
|
||||
g_diag_callback.store(cb, std::memory_order_release);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
@@ -199,9 +201,14 @@ std::vector<int> PluginLoader::topological_sort() const
|
||||
|
||||
int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
{
|
||||
if (!host_api) return -1;
|
||||
|
||||
try {
|
||||
std::vector<int> order = topological_sort();
|
||||
|
||||
std::unordered_set<std::string> failed_names;
|
||||
int failed_count = 0;
|
||||
|
||||
for (int id : order) {
|
||||
auto it = plugins_.find(id);
|
||||
if (it == plugins_.end()) continue;
|
||||
@@ -209,16 +216,40 @@ int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
PluginInfo& plugin = it->second;
|
||||
if (plugin.initialized) continue;
|
||||
|
||||
// 检查依赖是否已失败
|
||||
bool dep_unavailable = false;
|
||||
for (const auto& dep_name : plugin.dependencies) {
|
||||
if (failed_names.count(dep_name)) {
|
||||
dep_unavailable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dep_unavailable) {
|
||||
fprintf(stderr, "[WARN] Plugin '%s' skipped: dependency unavailable\n",
|
||||
plugin.name.c_str());
|
||||
failed_names.insert(plugin.name);
|
||||
failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plugin.info->on_init) {
|
||||
int result = plugin.info->on_init(host_api);
|
||||
if (result != 0) {
|
||||
return -1;
|
||||
fprintf(stderr, "[ERROR] Plugin '%s' init failed (code %d)\n",
|
||||
plugin.name.c_str(), result);
|
||||
failed_names.insert(plugin.name);
|
||||
failed_count++;
|
||||
continue; // 不设置 initialized=true
|
||||
}
|
||||
}
|
||||
plugin.initialized = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return failed_count;
|
||||
} catch (const std::runtime_error&) {
|
||||
// 循环依赖
|
||||
return -1;
|
||||
} catch (const std::exception&) {
|
||||
return -1;
|
||||
}
|
||||
@@ -272,13 +303,24 @@ void PluginLoader::shutdown_all()
|
||||
if (it == plugins_.end()) continue;
|
||||
|
||||
PluginInfo& plugin = it->second;
|
||||
if (!plugin.initialized) continue;
|
||||
|
||||
if (plugin.info->on_shutdown) {
|
||||
if (plugin.initialized && plugin.info->on_shutdown) {
|
||||
plugin.info->on_shutdown();
|
||||
}
|
||||
plugin.initialized = false;
|
||||
}
|
||||
|
||||
// 释放所有 DLL 句柄
|
||||
for (auto& [id, plugin] : plugins_) {
|
||||
if (plugin.handle) {
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)plugin.handle);
|
||||
#else
|
||||
dlclose(plugin.handle);
|
||||
#endif
|
||||
plugin.handle = nullptr;
|
||||
}
|
||||
}
|
||||
plugins_.clear();
|
||||
}
|
||||
|
||||
const PluginInfo* PluginLoader::get_plugin(int plugin_id) const
|
||||
|
||||
Reference in New Issue
Block a user