Replaying the captured failing frame proved the std::vector max_size() abort was
never a parser bug. It was an object-lifetime bug in WaDevice::serialize():
put_blob(b, Bytes((jid ? *jid : "").begin(), (jid ? *jid : "").end()));
(jid ? *jid : "") builds a fresh temporary std::string for each evaluation, so
begin() and end() came from two different, already-destroyed objects; the range
constructor computed a garbage length and vector threw. It only misfired once
jid was set (after the phone scanned), which is why every parser test and the
initial unpaired save passed. Fixed by binding the value to a named const
std::string before taking iterators.
- src/whatsapp/wa_device.cpp: bind jid/lid before iterating; audited the tree
for the same two-temporaries pattern (no other instances).
- tests/whatsapp_test.cpp: test_device round-trips a PAIRED device (jid+lid set,
synthetic identifiers), the populated case that triggers the bug; runs under
tools/san-whatsapp.sh.
- docs/whatsapp.md 13.7: records the real root cause and closes the open item.
- docs/cpp-safety.md: adds the object-lifetime rule (never take iterators from a
temporary you rebuild) as a distinct bug class from size overflow.
Verified: replaying the captured pair-success frame now serializes, saves, and
encodes the response with no throw; build, whatsapp_test, make lint, and
tools/san-whatsapp.sh all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
detz: C++23 port of tai.py
Future molo detz subcommand. See plan.md, SPEC.md, docs/.
Build: make && make test. Binary: build/detz. Rules for working here: AGENTS.md.
What taicpp adds on top of the port
The port is behavior-compatible with ../tai.py and checked against it (PARITY.md, docs/parity.md).
These parts exist only here; each has a specification and tests:
| Feature | Spec | Code | Tests |
|---|---|---|---|
| Fuzzy matching of identifiers (letter order, word boundaries, typos, trigrams) | docs/fuzzy.md | src/util/fuzzy.* |
build/fuzzy_test |
CAP agents and their models: discovery, probes, @agent, @agent:model, @model |
docs/cap-agents.md | src/cap/cap_model_catalog.*, src/cap/cap_mention.* |
build/capmcp/cap_model_catalog_test |
| Sub agents in live panels on the multiplexer screen, with supervision | docs/orchestration.md | src/orch/, src/mplex/mplex_vpane.* |
build/orch_test, tests/orch_mplex_e2e.py |
Reading order for a new contributor: docs/cap.md (the protocol), docs/cap-agents.md (agents and
models), docs/fuzzy.md (how a mention is matched), docs/mplex.md and docs/orchestration.md (the screen).
Using it
@claude refactor the parser run one turn on a CAP agent (typos are fine: @clde)
@claude:opus review the diff on a specific model of that agent
@opus review the diff a model name alone works when one agent offers it
@claude pin the agent for following messages; @auto clears all pins
Sub agents (fork workers and agent turns) appear in panels on the right of the multiplexer screen while
they work; closing a panel cancels its agent. TAI_ORCH_PANELS=0 turns the panels off.
Tests
make test builds every test binary and runs tests/run_all.py (36 steps, logs in build/test-logs/).
Single steps: python3 tests/run_all.py --only orch, --only mplex, --only capmcp, --only fuzzy.
The research behind the decisions is in ../research/ (index: ../research/INDEX.md).