diff --git a/packages/kbot/cpp/CMakeLists.txt b/packages/kbot/cpp/CMakeLists.txt index 3c5a47f4..20f04f7e 100644 --- a/packages/kbot/cpp/CMakeLists.txt +++ b/packages/kbot/cpp/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.20) -project(polymech-cli +project(kbot-cli VERSION 0.1.0 - DESCRIPTION "Polymech C++ CLI" + DESCRIPTION "KBot C++ CLI" LANGUAGES CXX C ) @@ -112,6 +112,9 @@ add_executable(${PROJECT_NAME} src/sys_metrics.cpp ) +# Output file name is kbot.exe / kbot (not kbot-cli) +set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "kbot") + target_link_libraries(${PROJECT_NAME} PRIVATE CLI11::CLI11 tomlplusplus::tomlplusplus logger html postgres http json polymech ipc geo gadm_reader grid search enrichers kbot) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/packages/kbot/cpp/README.md b/packages/kbot/cpp/README.md index 577dcd84..2db5c2eb 100644 --- a/packages/kbot/cpp/README.md +++ b/packages/kbot/cpp/README.md @@ -28,77 +28,6 @@ polymech-cli --help polymech-cli --version ``` -## Worker Mode & Gridsearch - -The `worker` subcommand is designed to be spawned by the Node.js frontend orchestrator (`GridSearchUdsManager`) for background gridsearch execution. It accepts length-prefixed JSON frames over a Unix Domain Socket (UDS) or a local TCP port on Windows. - -```bash -polymech-cli worker --uds --daemon --user-uid --config -``` - -### IPC Resiliency and Logging -The C++ worker pipeline incorporates extensive feedback and retry instrumentation: - -1. **Watchdog Heartbeats (`ping` / `pong`)** - - The Node orchestrator sweeps the active worker pool every 15 seconds. It explicitly logs when a ping is sent and when a `pong` (or other active events like `log`, `job_progress`, or `ack`) are received. - - If a C++ worker stops responding to IPC events for 60 seconds (hanging thread or deadlock), it is automatically killed (`SIGKILL`) and evicted from the pool. - -2. **Socket Traceability** - - The UDS socket actively traps unexpected closures and TCP faults (like `ECONNRESET`). If the pipe breaks mid-job, explicit socket `error` event handlers in the Node orchestrator will instantly fail the job and log the stack trace, preventing indefinite client-side UI hangs, especially during heavy re-runs. - -3. **Persistent Crash Logging (`logs/uds.json`)** - - The C++ worker initializes a multi-sink logger (`logger::init_uds`). It pumps standard logs to `stderr` while simultaneously persisting an append-only file trace to `server/logs/uds.json`. - - The file sink guarantees synchronization to disk aggressively (every 1 second, and immediately on `info` severity). If the worker process vanishes or crashes, `uds.json` acts as the black-box flight recorder for post-mortem debugging. - -4. **Job Specification Transparency** - - Gridsearch payloads (including `retry` and `expand` endpoints) aggressively log their input shape (`guided` bounds flag, `enrichers` subset) within the Node console before passing work to the C++ orchestrator. This allows for clear traceability from UI action -> Node submission -> C++ execution. - -5. **Thread Safety & Frame Synchronization (Mutexes)** - - The UDS socket handles dual-direction asynchronous streams. The background execution graph (powered by Taskflow) emits high-frequency events (`location`, `waypoint-start`) via `GridsearchCallbacks`. Concurrently, the orchestrator Node.js process sends periodic commands (`ping`, `cancel`) that the C++ socket loop must instantly acknowledge. - - To prevent overlapping payload frames (which corrupt the critical 4-byte `len` header), a global `g_uds_socket_mutex` is strictly enforced. It guarantees that direct UI acknowledgments (`pong`, `cancel_ack`) and background logging (`uds_sink` / Taskflow events) never interleave their `asio::write` bursts onto the pipe. - -### IPC Framing & Payload Protocol -Communication runs strictly via length-prefixed JSON frames. This safeguards against TCP fragmentation during heavy event streams. - -**Binary Frame Format:** -`[4-byte Unsigned Little-Endian Integer (Payload Length)] [UTF-8 JSON Object]` - -#### Control Commands (Node → C++) -If the JSON object contains an `"action"` field, it is handled synchronously on the socket thread: -- **Health Check:** `{"action": "ping"}` - → *Replies:* `{"type": "pong", "data": {"memoryMb": 120, "cpuTimeMs": 4500}}` -- **Cancellation:** `{"action": "cancel", "jobId": "job_123"}` - → Worker sets the atomic cancellation token to safely halt the target `taskflow`, instantly replying `{"type": "cancel_ack", "data": "job_123"}` -- **Daemon Teardown:** `{"action": "stop"}` - → Flushes all streams and exits cleanly. - -#### Gridsearch Payload (Node → C++) -If no `"action"` field exists, the message is treated as a gridsearch spec and pushed into a lock-free `ConcurrentQueue` for the background execution graph: -```json -{ - "jobId": "run_9a8bc7", - "configPath": "config/postgres.toml", - "cacheDir": "../packages/gadm/cache", - "enrich": true, - "guided": { - "areas": [{ "gid": "ESP.6_1", "level": 1 }], - "settings": { "gridMode": "hex", "cellSize": 5.0 } - }, - "search": { - "types": ["restaurant"], - "limitPerArea": 500 - } -} -``` - -#### Event Streaming (C++ → Node) -As the gridsearch pipeline executes, the `GridsearchCallbacks` emit standard length-prefixed events directly back to the active UDS socket: -- **`ack`**: Acknowledges job was successfully dequeued (`{"type": "ack", "data": {"jobId": "..."}}`). -- **`log`**: Passthrough of all internal C++ `spdlog` messages using the custom `uds_sink` adapter. -- **`location` / `node`**: Raw geolocation geometries and enriched contact details streamed incrementally. -- **`job_progress`**: Phase updates (Grid Generation → Search → Enrichment). -- **`job_result`**: The final statistical and timer summary (EnumMs, SearchMs, Total Emails, etc). -- **`error`**: Unrecoverable boundary parsing or database initialization faults. ## License diff --git a/packages/kbot/cpp/build_out.txt b/packages/kbot/cpp/build_out.txt deleted file mode 100644 index 2189004b..00000000 Binary files a/packages/kbot/cpp/build_out.txt and /dev/null differ diff --git a/packages/kbot/cpp/build_out.utf8.txt b/packages/kbot/cpp/build_out.utf8.txt deleted file mode 100644 index 85bef50c..00000000 --- a/packages/kbot/cpp/build_out.utf8.txt +++ /dev/null @@ -1,58 +0,0 @@ -MSBuild version 18.4.0+6e61e96ac for .NET Framework - - libcurl_object.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_object.dir\Debug\libcurl_object.lib - Catch2.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\catch2-build\src\Debug\Catch2d.lib - Catch2WithMain.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\catch2-build\src\Debug\Catch2Maind.lib - lexbor_static.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\lexbor-build\Debug\lexbor_static.lib - html.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\html\Debug\html.lib - libcurl_static.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\Debug\libcurl-d.lib - http.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\http\Debug\http.lib - json.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\json\Debug\json.lib - spdlog.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\spdlog-build\Debug\spdlogd.lib - logger.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\logger\Debug\logger.lib - enrichers.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\enrichers\Debug\enrichers.lib - geo.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\geo\Debug\geo.lib - gadm_reader.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\gadm_reader\Debug\gadm_reader.lib - grid.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\grid\Debug\grid.lib - ipc.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\ipc\Debug\ipc.lib - azure.cpp -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): warning C4003: not enough arguments for function-like macro invocation 'max' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/azure.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2589: '(': illegal token on right side of '::' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/azure.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2059: syntax error: ')' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/azure.cpp') - - chat.cpp -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): warning C4003: not enough arguments for function-like macro invocation 'max' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/chat.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2589: '(': illegal token on right side of '::' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/chat.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2059: syntax error: ')' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/chat.cpp') - - Generating Code... - postgres.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\postgres\Debug\postgres.lib - polymech.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\polymech\Debug\polymech.lib - search.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\search\Debug\search.lib - test_enrichers.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_enrichers.exe - test_functional.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_functional.exe - test_gadm_reader.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_gadm_reader.exe - test_geo.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_geo.exe - test_grid.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_grid.exe - test_gridsearch_ipc.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_gridsearch_ipc.exe - test_html.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_html.exe - test_http.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_http.exe - test_ipc.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_ipc.exe - test_json.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_json.exe - test_logger.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_logger.exe - test_polymech.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_polymech.exe - test_polymech_e2e.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_polymech_e2e.exe - test_postgres.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_postgres.exe - test_postgres_live.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_postgres_live.exe - test_search.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_search.exe - test_supabase.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\dist\test_supabase.exe diff --git a/packages/kbot/cpp/kbot_build_v.txt b/packages/kbot/cpp/kbot_build_v.txt deleted file mode 100644 index 5710a969..00000000 --- a/packages/kbot/cpp/kbot_build_v.txt +++ /dev/null @@ -1,147 +0,0 @@ -MSBuild version 18.4.0+6e61e96ac for .NET Framework -Build started 3/29/2026 10:04:05 PM. - -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" on node 1 (default targets). -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (1) is building "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\ZERO_CHECK.vcxproj" (2) on node 1 (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Creating "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified. - Touching "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -FinalizeBuildStatus: - Deleting file "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild". - Touching "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\ZERO_CHECK.lastbuildstate". -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\ZERO_CHECK.vcxproj" (default targets). -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (1) is building "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\json\json.vcxproj" (3) on node 1 (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Creating "json.dir\Debug\json.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified. - Touching "json.dir\Debug\json.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -ClCompile: - All outputs are up-to-date. -Lib: - All outputs are up-to-date. - json.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\json\Debug\json.lib -FinalizeBuildStatus: - Deleting file "json.dir\Debug\json.tlog\unsuccessfulbuild". - Touching "json.dir\Debug\json.tlog\json.lastbuildstate". -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\json\json.vcxproj" (default targets). -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (1) is building "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_static.vcxproj" (4) on node 1 (default targets). -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_static.vcxproj" (4) is building "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_object.vcxproj" (5) on node 1 (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Creating "libcurl_object.dir\Debug\libcurl_object.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified. - Touching "libcurl_object.dir\Debug\libcurl_object.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -ClCompile: - All outputs are up-to-date. -Lib: - All outputs are up-to-date. - libcurl_object.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_object.dir\Debug\libcurl_object.lib -FinalizeBuildStatus: - Deleting file "libcurl_object.dir\Debug\libcurl_object.tlog\unsuccessfulbuild". - Touching "libcurl_object.dir\Debug\libcurl_object.tlog\libcurl_object.lastbuildstate". -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_object.vcxproj" (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Creating "libcurl_static.dir\Debug\libcurl_static.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified. - Touching "libcurl_static.dir\Debug\libcurl_static.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -Lib: - All outputs are up-to-date. - libcurl_static.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\Debug\libcurl-d.lib -FinalizeBuildStatus: - Deleting file "libcurl_static.dir\Debug\libcurl_static.tlog\unsuccessfulbuild". - Touching "libcurl_static.dir\Debug\libcurl_static.tlog\libcurl_static.lastbuildstate". -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_static.vcxproj" (default targets). -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (1) is building "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\logger\logger.vcxproj" (6) on node 1 (default targets). -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\logger\logger.vcxproj" (6) is building "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\spdlog-build\spdlog.vcxproj" (7) on node 1 (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Creating "spdlog.dir\Debug\spdlog.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified. - Touching "spdlog.dir\Debug\spdlog.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -ClCompile: - All outputs are up-to-date. -Lib: - All outputs are up-to-date. - spdlog.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\spdlog-build\Debug\spdlogd.lib -FinalizeBuildStatus: - Deleting file "spdlog.dir\Debug\spdlog.tlog\unsuccessfulbuild". - Touching "spdlog.dir\Debug\spdlog.tlog\spdlog.lastbuildstate". -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\spdlog-build\spdlog.vcxproj" (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Creating "logger.dir\Debug\logger.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified. - Touching "logger.dir\Debug\logger.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -ClCompile: - All outputs are up-to-date. -Lib: - All outputs are up-to-date. - logger.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\logger\Debug\logger.lib -FinalizeBuildStatus: - Deleting file "logger.dir\Debug\logger.tlog\unsuccessfulbuild". - Touching "logger.dir\Debug\logger.tlog\logger.lastbuildstate". -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\logger\logger.vcxproj" (default targets). -Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (1) is building "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\liboai\liboai\oai.vcxproj" (8) on node 1 (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Creating "oai.dir\Debug\oai.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified. - Touching "oai.dir\Debug\oai.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -ClCompile: - All outputs are up-to-date. -Lib: - All outputs are up-to-date. - oai.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\liboai\liboai\Debug\oaid.lib -FinalizeBuildStatus: - Deleting file "oai.dir\Debug\oai.tlog\unsuccessfulbuild". - Touching "oai.dir\Debug\oai.tlog\oai.lastbuildstate". -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\liboai\liboai\oai.vcxproj" (default targets). -PrepareForBuild: - Structured output is enabled. The formatting of compiler diagnostics will reflect the error hierarchy. See https://aka.ms/cpp/structured-output for more details. -InitializeBuildStatus: - Touching "kbot.dir\Debug\kbot.tlog\unsuccessfulbuild". -CustomBuild: - All outputs are up-to-date. -ClCompile: - C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\MSVC\14.50.35717\bin\HostX64\x64\CL.exe /c /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\kbot" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\taskflow-src" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\logger\include" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\spdlog-src\include" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\json\include" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\rapidjson-src\include" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\liboai\liboai\include" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\nlohmann_json-src\include" /I"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-src\include" /Zi /nologo /W1 /WX- /diagnostics:column /Od /Ob0 /D _MBCS /D WIN32 /D _WINDOWS /D SPDLOG_COMPILED_LIB /D CURL_STATICLIB /D "CMAKE_INTDIR=\"Debug\"" /EHsc /RTC1 /MDd /std:c++17 /Fo"kbot.dir\Debug\\" /Fd"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\Debug\kbot.pdb" /external:W1 /TP /errorReport:queue /utf-8 "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\kbot\llm_client.cpp" - llm_client.cpp -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\kbot\llm_client.cpp(64,19): error C2039: 'SetBaseUrl': is not a member of 'liboai::Authorization' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj] - C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\liboai\liboai\include\core\authorization.h(22,8): - see declaration of 'liboai::Authorization' - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\kbot\llm_client.cpp(69,22): warning C4834: discarding return value of function with [[nodiscard]] attribute [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj] -Done Building Project "C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (default targets) -- FAILED. - -Build FAILED. - -"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (default target) (1) -> -(ClCompile target) -> - C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\kbot\llm_client.cpp(69,22): warning C4834: discarding return value of function with [[nodiscard]] attribute [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj] - - -"C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj" (default target) (1) -> -(ClCompile target) -> - C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\packages\kbot\llm_client.cpp(64,19): error C2039: 'SetBaseUrl': is not a member of 'liboai::Authorization' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\packages\kbot\kbot.vcxproj] - - 1 Warning(s) - 1 Error(s) - -Time Elapsed 00:00:02.01 diff --git a/packages/kbot/cpp/oai_build.txt b/packages/kbot/cpp/oai_build.txt deleted file mode 100644 index 27bbe8f5..00000000 Binary files a/packages/kbot/cpp/oai_build.txt and /dev/null differ diff --git a/packages/kbot/cpp/oai_build.utf8.txt b/packages/kbot/cpp/oai_build.utf8.txt deleted file mode 100644 index 5e99e33c..00000000 --- a/packages/kbot/cpp/oai_build.utf8.txt +++ /dev/null @@ -1,25 +0,0 @@ -MSBuild version 18.4.0+6e61e96ac for .NET Framework - - libcurl_object.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\libcurl_object.dir\Debug\libcurl_object.lib - libcurl_static.vcxproj -> C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\curl-build\lib\Debug\libcurl-d.lib - azure.cpp -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): warning C4003: not enough arguments for function-like macro invocation 'max' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/azure.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2589: '(': illegal token on right side of '::' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/azure.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2059: syntax error: ')' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/azure.cpp') - - chat.cpp -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): warning C4003: not enough arguments for function-like macro invocation 'max' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/chat.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2589: '(': illegal token on right side of '::' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/chat.cpp') - -C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-src\liboai\include\components\chat.h(836,60): error C2059: syntax error: ')' [C:\Users\zx\Desktop\polymech\polymech-mono\packages\kbot\cpp\build\release\_deps\liboai-build\oai.vcxproj] - (compiling source file '../liboai-src/liboai/components/chat.cpp') - - Generating Code... diff --git a/packages/kbot/cpp/orchestrator/test-ipc.mjs b/packages/kbot/cpp/orchestrator/test-ipc.mjs index b63e179c..f1d52994 100644 --- a/packages/kbot/cpp/orchestrator/test-ipc.mjs +++ b/packages/kbot/cpp/orchestrator/test-ipc.mjs @@ -1,18 +1,22 @@ /** * orchestrator/test-ipc.mjs * - * Integration test: spawn the C++ worker, exchange messages, verify responses. + * Integration test: spawn the C++ worker in UDS mode, exchange messages, verify responses. * - * Run: node orchestrator/test-ipc.mjs - * Needs: npm run build (to compile the C++ binary first) + * Run: npm run test:ipc */ -import { spawnWorker } from './spawn.mjs'; +import { spawn } from 'node:child_process'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import net from 'node:net'; +import { randomUUID } from 'node:crypto'; +import { existsSync, unlinkSync } from 'node:fs'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const EXE = resolve(__dirname, '..', 'dist', 'polymech-cli.exe'); +const IS_WIN = process.platform === 'win32'; +const EXE_NAME = IS_WIN ? 'kbot.exe' : 'kbot'; +const EXE = resolve(__dirname, '..', 'dist', EXE_NAME); let passed = 0; let failed = 0; @@ -28,24 +32,109 @@ function assert(condition, label) { } async function run() { - console.log('\n🔧 IPC Integration Tests\n'); + console.log('\n🔧 IPC [UDS] Integration Tests\n'); + + if (!existsSync(EXE)) { + console.error(`❌ Binary not found at ${EXE}`); + process.exit(1); + } + + const CPP_UDS_ARG = IS_WIN ? '4001' : '/tmp/kbot-test-ipc.sock'; + if (!IS_WIN && existsSync(CPP_UDS_ARG)) { + unlinkSync(CPP_UDS_ARG); + } // ── 1. Spawn & ready ──────────────────────────────────────────────────── - console.log('1. Spawn worker and wait for ready signal'); - const worker = spawnWorker(EXE); + console.log('1. Spawn worker (UDS mode) and wait for ready signal'); + const workerProc = spawn(EXE, ['worker', '--uds', CPP_UDS_ARG], { stdio: 'pipe' }); + + workerProc.stderr.on('data', d => { + const txt = d.toString().trim(); + if (txt) console.error(`[worker:stderr] ${txt}`); + }); - const readyMsg = await worker.ready; + let socket; + for (let i = 0; i < 15; i++) { + try { + await new Promise((res, rej) => { + if (IS_WIN) { + socket = net.connect({ port: 4001, host: '127.0.0.1' }); + } else { + socket = net.connect(CPP_UDS_ARG); + } + socket.once('connect', res); + socket.once('error', rej); + }); + break; + } catch (e) { + if (i === 14) throw e; + await new Promise(r => setTimeout(r, 400)); + } + } + assert(true, 'Socket connected successfully'); + + // Pending request map: id → { resolve, reject, timer } + const pending = new Map(); + let readyResolve; + const readyPromise = new Promise(res => { readyResolve = res; }); + + let buffer = Buffer.alloc(0); + socket.on('data', chunk => { + buffer = Buffer.concat([buffer, chunk]); + while (buffer.length >= 4) { + const len = buffer.readUInt32LE(0); + if (buffer.length >= 4 + len) { + const payload = buffer.toString('utf8', 4, 4 + len); + buffer = buffer.subarray(4 + len); + try { + const msg = JSON.parse(payload); + if (msg.type === 'ready') { + readyResolve(msg); + } else if (msg.id && pending.has(msg.id)) { + const p = pending.get(msg.id); + clearTimeout(p.timer); + pending.delete(msg.id); + p.resolve(msg); + } + } catch (e) { + console.error('[orchestrator] frame parse error', e); + } + } else { + break; + } + } + }); + + function request(msg, timeoutMs = 5000) { + return new Promise((resolve, reject) => { + const id = msg.id || randomUUID(); + msg.id = id; + const timer = setTimeout(() => { + pending.delete(id); + reject(new Error(`IPC request timed out`)); + }, timeoutMs); + pending.set(id, { resolve, reject, timer }); + + const str = JSON.stringify(msg); + const lenBuf = Buffer.alloc(4); + lenBuf.writeUInt32LE(Buffer.byteLength(str)); + socket.write(lenBuf); + socket.write(str); + }); + } + + const readyMsg = await readyPromise; assert(readyMsg.type === 'ready', 'Worker sends ready message on startup'); // ── 2. Ping / Pong ───────────────────────────────────────────────────── console.log('2. Ping → Pong'); - const pong = await worker.request({ type: 'ping' }); + const pong = await request({ type: 'ping' }); assert(pong.type === 'pong', `Response type is "pong" (got "${pong.type}")`); // ── 3. Job echo ───────────────────────────────────────────────────────── console.log('3. Job → Job Result (echo payload)'); const payload = { action: 'resize', width: 1024, format: 'webp' }; - const jobResult = await worker.request({ type: 'job', payload }); + const jobResult = await request({ type: 'job', payload }); assert(jobResult.type === 'job_result', `Response type is "job_result" (got "${jobResult.type}")`); assert( jobResult.payload?.action === 'resize' && jobResult.payload?.width === 1024, @@ -54,14 +143,14 @@ async function run() { // ── 4. Unknown type → error ───────────────────────────────────────────── console.log('4. Unknown type → error response'); - const errResp = await worker.request({ type: 'nonsense' }); + const errResp = await request({ type: 'nonsense' }); assert(errResp.type === 'error', `Response type is "error" (got "${errResp.type}")`); // ── 5. Multiple rapid requests ────────────────────────────────────────── console.log('5. Multiple concurrent requests'); const promises = []; for (let i = 0; i < 10; i++) { - promises.push(worker.request({ type: 'ping', payload: { seq: i } })); + promises.push(request({ type: 'ping', payload: { seq: i } })); } const results = await Promise.all(promises); assert(results.length === 10, `All 10 responses received`); @@ -69,12 +158,13 @@ async function run() { // ── 6. Graceful shutdown ──────────────────────────────────────────────── console.log('6. Graceful shutdown'); - const shutdownRes = await worker.shutdown(); + const shutdownRes = await request({ type: 'shutdown' }); assert(shutdownRes.type === 'shutdown_ack', `Shutdown acknowledged (got "${shutdownRes.type}")`); // Wait a beat for process exit await new Promise(r => setTimeout(r, 200)); - assert(worker.process.exitCode === 0, `Worker exited with code 0 (got ${worker.process.exitCode})`); + socket.destroy(); + assert(workerProc.exitCode === 0, `Worker exited with code 0 (got ${workerProc.exitCode})`); // ── Summary ───────────────────────────────────────────────────────────── console.log(`\n────────────────────────────────`); diff --git a/packages/kbot/cpp/package.json b/packages/kbot/cpp/package.json index 766542f5..8fcfa978 100644 --- a/packages/kbot/cpp/package.json +++ b/packages/kbot/cpp/package.json @@ -1,7 +1,7 @@ { - "name": "mono-cpp", + "name": "kbot-cpp", "version": "1.0.0", - "description": "Cross-platform C++ CLI built with CMake.", + "description": "KBot C++ CLI built with CMake.", "directories": { "test": "tests" }, @@ -15,14 +15,12 @@ "test:release": "ctest --test-dir build/release -C Release --output-on-failure", "clean": "cmake -E rm -rf build dist", "rebuild": "npm run clean && npm run build", - "run": ".\\dist\\polymech-cli.exe --help", - "worker": ".\\dist\\polymech-cli.exe worker", - "test:ipc": "node orchestrator/test-gridsearch-ipc.mjs", - "gridsearch": ".\\dist\\polymech-cli.exe gridsearch ABW recycling --dry-run", - "gridsearch:settings": ".\\dist\\polymech-cli.exe gridsearch --settings config/gridsearch-sample.json --dry-run", - "gridsearch:settings:live": ".\\dist\\polymech-cli.exe gridsearch --settings config/gridsearch-sample.json", - "gridsearch:enrich": ".\\dist\\polymech-cli.exe gridsearch --settings config/gridsearch-sample.json --enrich", - "gridsearch:enrich-test": ".\\dist\\polymech-cli.exe gridsearch --settings config/gridsearch-test-bcn.json --enrich --persistence-postgres", + "run": ".\\dist\\kbot.exe --help", + "worker": ".\\dist\\kbot.exe worker", + "worker:uds": ".\\dist\\kbot.exe worker --uds \\\\.\\pipe\\kbot-worker", + "kbot:ai": ".\\dist\\kbot.exe kbot ai --prompt \"hi\"", + "kbot:run": ".\\dist\\kbot.exe kbot run --list", + "test:ipc": "node orchestrator/test-ipc.mjs", "test:gridsearch-ipc": "node orchestrator/test-gridsearch-ipc.mjs", "test:gridsearch-filter-ipc": "cmake --build build/release --target test_gridsearch_ipc && .\\dist\\test_gridsearch_ipc.exe", "test:ipc:daemon": "node orchestrator/test-gridsearch-ipc-daemon.mjs", diff --git a/packages/kbot/cpp/packages/kbot/kbot.cpp b/packages/kbot/cpp/packages/kbot/kbot.cpp index 7203388c..512f32d3 100644 --- a/packages/kbot/cpp/packages/kbot/kbot.cpp +++ b/packages/kbot/cpp/packages/kbot/kbot.cpp @@ -8,7 +8,7 @@ namespace polymech { namespace kbot { int run_kbot_ai_pipeline(const KBotOptions& opts, const KBotCallbacks& cb) { - logger::info("Starting kbot ai pipeline (stub)"); + logger::debug("Starting kbot ai pipeline"); if (opts.dry_run) { logger::info("Dry run triggered for kbot ai"); } @@ -18,14 +18,14 @@ int run_kbot_ai_pipeline(const KBotOptions& opts, const KBotCallbacks& cb) { tf::Taskflow taskflow; taskflow.emplace([opts, cb](){ - logger::info("Executing kbot ai completion via LLMClient..."); + logger::debug("Executing kbot ai completion via LLMClient..."); LLMClient client(opts); std::string target_prompt = opts.prompt.empty() ? "Respond with 'Hello from KBot C++ AI Pipeline!'" : opts.prompt; LLMResponse res = client.execute_chat(target_prompt); if (res.success) { - logger::info("AI Response:\n" + res.text); + std::cout << res.text << "\n"; if (cb.onEvent) { cb.onEvent("ai_progress", "{\"message\":\"Task completion received\"}"); } diff --git a/packages/kbot/cpp/packages/kbot/llm_client.cpp b/packages/kbot/cpp/packages/kbot/llm_client.cpp index 4a399daf..752407c5 100644 --- a/packages/kbot/cpp/packages/kbot/llm_client.cpp +++ b/packages/kbot/cpp/packages/kbot/llm_client.cpp @@ -1,6 +1,7 @@ #include "llm_client.h" #include "logger/logger.h" #include +#include #include namespace polymech { @@ -43,17 +44,17 @@ LLMClient::~LLMClient() = default; LLMResponse LLMClient::execute_chat(const std::string& prompt) { LLMResponse res; - logger::info("LLMClient::execute_chat: Starting. api_key length: " + std::to_string(api_key_.length())); + logger::debug("LLMClient::execute_chat: Starting. api_key length: " + std::to_string(api_key_.length())); if (api_key_.empty()) { res.success = false; res.error = "API Key is empty."; return res; } - logger::info("LLMClient::execute_chat: base_url_: " + base_url_); + logger::debug("LLMClient::execute_chat: base_url_: " + base_url_); liboai::OpenAI oai_impl(base_url_.empty() ? "https://api.openai.com/v1" : base_url_); - logger::info("LLMClient::execute_chat: Setting API Key"); + logger::debug("LLMClient::execute_chat: Setting API Key"); bool success = oai_impl.auth.SetKey(api_key_); if (!success) { res.success = false; @@ -62,24 +63,37 @@ LLMResponse LLMClient::execute_chat(const std::string& prompt) { } std::string target_model = model_.empty() ? "gpt-4o" : model_; - logger::info("LLMClient::execute_chat: Target model: " + target_model); + logger::debug("LLMClient::execute_chat: Target model: " + target_model); - logger::info("LLMClient::execute_chat: Init Conversation"); + logger::debug("LLMClient::execute_chat: Init Conversation"); liboai::Conversation convo; convo.AddUserData(prompt); - logger::info("LLMClient::execute_chat: Calling create()"); + logger::debug("LLMClient::execute_chat: Calling create()"); try { liboai::Response response = oai_impl.ChatCompletion->create( target_model, convo ); - logger::info("LLMClient::execute_chat: Got response with status: " + std::to_string(response.status_code)); + logger::debug("LLMClient::execute_chat: Got response with status: " + std::to_string(response.status_code)); - if (!response.raw_json.contains("choices") || response.raw_json["choices"].empty()) { + // liboai may not populate raw_json for custom base URLs — parse content directly. + nlohmann::json j; + bool json_ok = false; + if (!response.raw_json.empty() && response.raw_json.contains("choices")) { + j = response.raw_json; + json_ok = true; + } else if (!response.content.empty()) { + try { + j = nlohmann::json::parse(response.content); + json_ok = j.contains("choices"); + } catch (...) {} + } + + if (!json_ok || j["choices"].empty()) { res.success = false; - if (response.raw_json.contains("error")) { - res.error = "API Error: " + response.raw_json["error"].dump(); + if (json_ok && j.contains("error")) { + res.error = "API Error: " + j["error"].dump(); } else { res.error = "Invalid response format: no choices found. Raw: " + response.content; } @@ -87,7 +101,7 @@ LLMResponse LLMClient::execute_chat(const std::string& prompt) { } res.success = true; - res.text = response.raw_json["choices"][0]["message"]["content"].get(); + res.text = j["choices"][0]["message"]["content"].get(); } catch (std::exception& e) { logger::error("LLMClient::execute_chat: Exception caught: " + std::string(e.what())); diff --git a/packages/kbot/cpp/src/main.cpp b/packages/kbot/cpp/src/main.cpp index da567213..99d87998 100644 --- a/packages/kbot/cpp/src/main.cpp +++ b/packages/kbot/cpp/src/main.cpp @@ -29,7 +29,7 @@ #endif int main(int argc, char *argv[]) { - CLI::App app{"polymech-cli — Polymech C++ CLI", "polymech-cli"}; + CLI::App app{"kbot — KBot C++ CLI", "kbot"}; app.set_version_flag("-v,--version", PROJECT_VERSION); std::string log_level = "info"; @@ -89,11 +89,9 @@ int main(int argc, char *argv[]) { worker_cmd->add_option("--user-uid", daemon_uid, "User ID to bind this daemon to (needed for place owner)"); worker_cmd->add_option("--uds", uds_path, "Run over Unix Domain Socket / Named Pipe at the given path"); - // Subcommand: gridsearch — Run a full gridsearch pipeline - auto* gs_cmd = polymech::setup_cmd_gridsearch(app); - // Subcommand: kbot — AI workflows & task configurations auto* kbot_cmd = polymech::setup_cmd_kbot(app); + (void)kbot_cmd; CLI11_PARSE(app, argc, argv); @@ -294,11 +292,6 @@ int main(int argc, char *argv[]) { return 0; } - // ── gridsearch subcommand ────────────────────────────────────────────── - if (gs_cmd->parsed()) { - return polymech::run_cmd_gridsearch(); - } - // ── kbot subcommand ────────────────────────────────────────────────── if (polymech::is_kbot_ai_parsed()) { return polymech::run_cmd_kbot_ai();