Root causes, each reproduced against the live server or endpoint: - Server-only JIDs decoded as "@s.whatsapp.net" and pairing replies copied them into `to`, so the device was never registered and every login returned 401. Decoder/encoder fixed, replies use s.whatsapp.net, ADV hosted prefixes follow whatsmeow. The link wizard now waits for the restart, logs in, uploads prekeys and retries once with a fresh device on 401/403. - Closed sockets made every receive loop spin until its deadline (the 7 minute CPU burn). Loops stop on a closed stream. - whatsapp_send had no image argument and send_image was a stub. Added WaMedia (byte-exact against 8 wa-go golden vectors), media_conn upload, image send and the DeviceSentMessage copy for the user's other devices. - Phone contacts never reached the client: added app-state sync (WaAppState, WaReceiver, LTHash and MAC checks verified against a whatsmeow-built oracle) and the historySyncConfig declaration at registration. - Contact lookup: FTS5 trigram plus fuzzy ranking, ambiguity is reported and never guessed, global search lists contacts. Send caps (10/min, 200/day) are enforced and survive unlink. Failover and agent: - Every failed backend candidate is printed the moment it fails. - muse runs with low reasoning effort, an empty preset and one stable workspace (36.5s down to 2.8s on the real prompt); Ctrl-C no longer counts as a backend failure; CLI backends use the circuit breaker; opencode runs are capped at 30s. - Orphan tool results are dropped before sending (devplace HTTP 400, reproduced); compaction no longer starts its window on a tool result. - Terminal art pasted as a prompt is refused before any model call. Tests: whatsapp_test, muse_flags_test, llm_history_test; full suite 40 steps (agent.e2e reads live tmux panes and is flaky, green on re-run). Research archived under research/whatsapp-automation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
182 lines
8.6 KiB
Makefile
182 lines
8.6 KiB
Makefile
CXX ?= g++
|
|
CXXSTD = -std=c++23
|
|
WARN = -Wall -Wextra
|
|
OPT ?= -O1
|
|
BUILD = build
|
|
VERSION = $(shell cat VERSION)
|
|
BIN = $(BUILD)/detz
|
|
GEN_INC = $(BUILD)/gen
|
|
VERSION_HPP = $(GEN_INC)/version.hpp
|
|
# moloapp (src/cli includes tenancy/strategy.h). ../../moloapp/src resolves only from the main
|
|
# checkout (taicpp is /home/retoor/projects/tai/taicpp); from a worktree under
|
|
# .claude/worktrees/<id>/taicpp it is five levels up. Override with MOLOAPP_SRC=...
|
|
MOLOAPP_SRC ?= $(firstword $(wildcard ../../moloapp/src ../../../../../moloapp/src $(HOME)/projects/moloapp/src))
|
|
ifeq ($(MOLOAPP_SRC),)
|
|
$(error moloapp/src not found: set MOLOAPP_SRC=/path/to/moloapp/src)
|
|
endif
|
|
# Include path: every src directory (headers include their own directory first, so same-named
|
|
# headers in two modules do not collide).
|
|
SRC_DIRS = $(shell find src -type d)
|
|
INC = -I$(GEN_INC) $(addprefix -I,$(SRC_DIRS)) -I$(MOLOAPP_SRC) -Ithird_party/ed25519 -Ithird_party/qrcodegen
|
|
CXXFLAGS ?= $(CXXSTD) $(WARN) $(OPT)
|
|
LDLIBS = -lsqlite3 -lcurl -lz -pthread -lutil -lssl -lcrypto
|
|
|
|
ALL_SRCS = $(shell find src -name '*.cpp')
|
|
ALL_OBJS = $(patsubst %.cpp,$(BUILD)/obj/%.o,$(ALL_SRCS))
|
|
# Vendored C: the public-domain ed25519 ref10 curve arithmetic (third_party/ed25519,
|
|
# zlib license) that detz::wa WaCrypto needs for XEdDSA, the one Signal primitive OpenSSL
|
|
# does not expose. Compiled as C (its static load_3/load_4 collide if merged into one TU).
|
|
TP_C_SRCS = $(wildcard third_party/ed25519/*.c)
|
|
TP_C_OBJS = $(patsubst %.c,$(BUILD)/obj/%.o,$(TP_C_SRCS))
|
|
# Vendored C++: the nayuki QR Code generator (third_party/qrcodegen, MIT) for the
|
|
# `--whatsapp-link` terminal wizard.
|
|
TP_CPP_SRCS = $(wildcard third_party/qrcodegen/*.cpp)
|
|
TP_C_OBJS += $(patsubst %.cpp,$(BUILD)/obj/%.o,$(TP_CPP_SRCS))
|
|
MAIN_OBJ = $(BUILD)/obj/main.o
|
|
LIB = $(BUILD)/libdetz.a
|
|
|
|
.PHONY: all static lint test test-build test-run parity parity-check version-bump changelog tag release release-dry docs clean
|
|
|
|
all: $(BIN)
|
|
|
|
$(VERSION_HPP): VERSION src/cli/version.hpp.in
|
|
@mkdir -p $(GEN_INC)
|
|
@sed 's/@VERSION@/$(VERSION)/' src/cli/version.hpp.in > $@
|
|
|
|
$(BUILD)/obj/%.o: %.cpp $(VERSION_HPP)
|
|
@mkdir -p $(dir $@)
|
|
$(CXX) $(CXXFLAGS) $(INC) -MMD -MP -c $< -o $@
|
|
|
|
$(BUILD)/obj/%.o: %.c
|
|
@mkdir -p $(dir $@)
|
|
$(CC) -O2 -Wall -Ithird_party/ed25519 -c $< -o $@
|
|
|
|
$(LIB): $(ALL_OBJS) $(TP_C_OBJS)
|
|
@rm -f $@
|
|
ar rcs $@ $^
|
|
|
|
$(BIN): $(MAIN_OBJ) $(ALL_OBJS) $(TP_C_OBJS)
|
|
$(CXX) $(CXXFLAGS) $(MAIN_OBJ) $(ALL_OBJS) $(TP_C_OBJS) $(LDLIBS) -o $@
|
|
|
|
static: $(MAIN_OBJ) $(ALL_OBJS) $(TP_C_OBJS)
|
|
$(CXX) $(CXXFLAGS) -pthread -static $(MAIN_OBJ) $(ALL_OBJS) $(TP_C_OBJS) third_party/curl/libcurl.a third_party/sqlite/libsqlite3.a -lssl -lcrypto third_party/jitterentropy/libjitterentropy.a -lzstd -lz -lnghttp2 -lpsl -lbrotlidec -lidn2 -lrtmp -o $(BUILD)/detz-static
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test binaries. Every test links tests/<name>.cpp against build/libdetz.a (the archive pulls
|
|
# only the objects a test needs, main.cpp is never in it). No test links main.cpp.
|
|
# TESTBIN(output, source, optional include flags). Several modules ship same-named headers
|
|
# (text.h, scheduler.h, consts.h, ...), so a test whose flat includes mean one module gets only
|
|
# that module's -I dirs.
|
|
MISC_INC = -Isrc/skills -Isrc/sched -Isrc/telegram -Isrc/rag -Isrc/install
|
|
SHELL_INC = -Isrc/util -Isrc/shell
|
|
define TESTBIN
|
|
TEST_BINS += $(1)
|
|
LINT_TESTS += $(2)
|
|
LINT_INC_$(2) := $(if $(3),$(3),$$(INC))
|
|
$(1): $(2) $(LIB) $(VERSION_HPP)
|
|
@mkdir -p $$(dir $$@)
|
|
$$(CXX) $$(CXXFLAGS) $(if $(3),$(3),$$(INC)) $(2) $$(LIB) $$(LDLIBS) -o $$@
|
|
endef
|
|
|
|
TEST_BINS =
|
|
LINT_TESTS =
|
|
# foundation (store, config, session, plexer, repl)
|
|
$(eval $(call TESTBIN,$(BUILD)/foundation_test,tests/foundation_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/foundation_probe,tests/foundation_probe.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/repl_probe,tests/repl_probe.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/store_test,tests/store_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/cli_test,tests/cli_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/util_config_test,tests/util_config_test.cpp))
|
|
# agent core
|
|
$(eval $(call TESTBIN,$(BUILD)/agent_diff,tests/agent_diff.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/agent_e2e,tests/agent_e2e.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/agent_sched,tests/agent_sched.cpp))
|
|
# shell and util (shell_parity_driver is built by tests/shell_parity.py itself; lint only)
|
|
LINT_TESTS += tests/shell_parity_driver.cpp
|
|
LINT_INC_tests/shell_parity_driver.cpp := $(SHELL_INC)
|
|
$(eval $(call TESTBIN,$(BUILD)/shell_test,tests/shell_test.cpp,$(SHELL_INC)))
|
|
# cap and mcp
|
|
$(eval $(call TESTBIN,$(BUILD)/capmcp/capmcp_unit,tests/capmcp_unit.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/capmcp/capmcp_probe,tests/capmcp_probe.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/capmcp/capmcp_cli,tests/capmcp_cli.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/capmcp/cap_model_catalog_test,tests/cap_model_catalog_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/fuzzy_test,tests/fuzzy_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/whatsapp_test,tests/whatsapp_test.cpp,-Isrc/whatsapp -Isrc/llm -Ithird_party/ed25519))
|
|
$(eval $(call TESTBIN,$(BUILD)/termshot_test,tests/termshot_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/muse_flags_test,tests/muse_flags_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/llm_history_test,tests/llm_history_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/orch_test,tests/orch_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/orch_live,tests/orch_live.cpp))
|
|
# misc (skills, sched, telegram, rag, install)
|
|
$(eval $(call TESTBIN,$(BUILD)/misc/misc_test,tests/misc_test.cpp,$(MISC_INC)))
|
|
$(eval $(call TESTBIN,$(BUILD)/misc/misc_driver,tests/misc_driver.cpp,$(MISC_INC)))
|
|
# tui and grr
|
|
$(eval $(call TESTBIN,$(BUILD)/tui_spec,tests/tui_spec.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/tui_shot,tests/tui_shot.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/tui_script,tests/tui_script.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/tui_rows,tests/tui_rows.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/tui_chat,tests/tui_chat.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/tui_grr,tests/tui_grr.cpp))
|
|
# mplex
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_cli,tests/mplex_cli_main.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_foundation_test,tests/mplex_foundation_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_core_test,tests/mplex_core_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_model_test,tests/mplex_model_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_copy_test,tests/mplex_copy_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_vt_test,tests/mplex_vt_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_flow_test,tests/mplex_flow_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_regress_test,tests/mplex_regress_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_spec_test,tests/mplex_spec_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_server_test,tests/mplex_server_test.cpp))
|
|
$(eval $(call TESTBIN,$(BUILD)/mplex_commands_test,tests/mplex_commands_test.cpp))
|
|
|
|
# test-build compiles every test binary; test-run executes them (tests/run_all.py) and prints the
|
|
# summary table; `make test` does both and fails if any step fails or any binary does not build.
|
|
test-build: $(TEST_BINS)
|
|
|
|
test-run:
|
|
python3 tests/run_all.py
|
|
|
|
test: test-build
|
|
python3 tests/run_all.py
|
|
|
|
# lint: real -Werror -fsyntax-only over every source, main.cpp and every test source, in parallel.
|
|
# Test sources use the same include set their binary is built with (see TESTBIN).
|
|
LINT_SRCS = $(ALL_SRCS) main.cpp
|
|
lint: $(VERSION_HPP)
|
|
@mkdir -p $(BUILD)
|
|
@: > $(BUILD)/lint.cmds
|
|
@$(foreach f,$(LINT_SRCS),echo '$(CXX) $(CXXSTD) $(WARN) -Werror -fsyntax-only $(INC) $(f) || { echo "LINT FAIL $(f)"; exit 1; }' >> $(BUILD)/lint.cmds;)
|
|
@$(foreach f,$(LINT_TESTS),echo '$(CXX) $(CXXSTD) $(WARN) -Werror -fsyntax-only $(LINT_INC_$(f)) $(f) || { echo "LINT FAIL $(f)"; exit 1; }' >> $(BUILD)/lint.cmds;)
|
|
@if xargs -d '\n' -P $$(nproc) -I{} sh -c {} < $(BUILD)/lint.cmds; then echo "lint: $$(wc -l < $(BUILD)/lint.cmds) files clean"; else echo "lint: FAILED"; exit 1; fi
|
|
|
|
# Parity verification (docs/wiring/verify.md). Output goes to build/parity/ and PARITY.md.
|
|
parity: $(BIN)
|
|
DETZ_BIN=$(abspath $(BIN)) tools/parity.sh run --detz $(abspath $(BIN))
|
|
|
|
parity-check: $(BIN)
|
|
DETZ_BIN=$(abspath $(BIN)) tools/parity.sh check --detz $(abspath $(BIN))
|
|
|
|
version-bump:
|
|
tools/version-bump.sh $(PART)
|
|
|
|
changelog:
|
|
tools/changelog.sh "$(MSG)"
|
|
|
|
tag:
|
|
tools/tag.sh
|
|
|
|
release:
|
|
PART=$(PART) MSG="$(MSG)" tools/release.sh
|
|
|
|
release-dry:
|
|
PART=$(PART) MSG="$(MSG)" sh -n tools/release.sh && echo "release dry-run OK"
|
|
|
|
docs:
|
|
@for f in agent cap cap-agents fuzzy orchestration mcp tui mplex shell store util llm misc foundation parity; do test -f docs/$$f.md || { echo "missing docs/$$f.md"; exit 1; }; done; echo "docs OK"
|
|
|
|
clean:
|
|
rm -rf $(BUILD)
|
|
|
|
-include $(shell find $(BUILD)/obj -name '*.d' 2>/dev/null)
|