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>
Response to incident WA-2026-10-27 (the std::vector max_size() crash). Deep
research archived under research/safe-cpp-practices (8 Tier 1, 37 Tier 2, 2
conclusions) on production-safe C++ that keeps development speed: OpenSSF
compiler hardening, the C++ Core Guidelines bounds profile, integer-overflow
practice, libFuzzer/sanitizer CI, the sanitizer overhead ladder, clang-tidy
ratchets.
The research prescribed an ASan+UBSan+_GLIBCXX_ASSERTIONS build of the parsers.
Standing it up immediately caught a second, independent undefined-behavior bug
that two manual reviews had missed: a protobuf varint loop that shifted by up
to 70 bits (shift >= 64 is UB) on malformed input. Fixed and guarded by a
deterministic test and the sanitizer net.
- src/whatsapp/wa_proto.cpp: guard the wire-0 varint shift (the UB).
- tests/whatsapp_test.cpp: add an overlong-varint hostile case.
- tools/san-whatsapp.sh: the standing sanitizer net for detz::wa parsers.
- WaClient frame capture: a frame that fails to decode/dispatch has its exact
bytes recorded to <home>/whatsapp/last-error.log (bounded), so the still
unreproduced live <iq> failure becomes a one-paste deterministic fix. Wired
from the wizard and the whatsapp_send tool.
- docs/cpp-safety.md: the project-wide playbook and design decisions (four
layers: overflow-safe checked-reader parsing, cheap release hardening, the
sanitizer test net, capture). docs/whatsapp.md section 13.6 records the
follow-up and the honest open item. AGENTS.md makes it a standing rule;
cap.md and mcp.md inherit it for their wire parsers.
Verified: build, whatsapp_test (incl. hostile+fuzz), make lint, and
tools/san-whatsapp.sh all green; research audit PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Native WhatsApp Web multi-device client in C++ (src/whatsapp/, namespace
detz::wa), built on OpenSSL plus a vendored public-domain ed25519 ref10
(third_party/ed25519, zlib) for XEdDSA and the nayuki QR generator
(third_party/qrcodegen, MIT); no new runtime dependency.
- WaCrypto, WaBinary, WaProto, WaNoise, WaSignal, WaDevice, WaSocket,
WaClient, WaStore, WaQr, WaWizard.
- Verified offline against golden vectors captured from the real Baileys
client (tests/whatsapp_data, from jfelipesjc/wa-go, MIT): HKDF/PBKDF2 RFC
vectors, XEdDSA roundtrips, codec battery 19/19, ClientPayload byte-exact,
the Noise handshake, and a 4-exchange Signal session byte-exact. Unit test
build/whatsapp_test (16/16), run_all step whatsapp.unit.
- Verified live against web.whatsapp.com: `detz --whatsapp-link` connects,
completes the Noise handshake, receives pair-device, renders a scannable QR
in the terminal, and persists the device to the vault (whatsapp/<profile>.db).
Also --whatsapp-status / --whatsapp-unlink.
- Agent tools whatsapp_send and whatsapp_contacts (detz-only, in DetzTools).
- Pairing completion after the phone scans, and the message send path
(usync + prekey fetch + Signal encrypt + send node), are ported faithfully
from whatsmeow but unverified in-tree (no linked account in development).
send_image and own-device fan-out are not wired yet. See docs/whatsapp.md.
Also in this snapshot: the terminal-screenshot tool (src/termshot/,
terminal_screenshot, docs/termshot.md, research/terminal-screenshot-rendering)
and a concurrent session's in-progress llm/muse failover changes carried along
in the shared files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No native Python WhatsApp library exists (neonize is whatsmeow over
ctypes, yowsup is dead) and no browser is needed. Spec for detz::wa:
native C++ transpiled from wa-go (MIT, golden vectors) with whatsmeow as
referee, libcurl WebSocket plus OpenSSL, no new dependencies, build vs
deep-research table, caps, tests, phases, and a whatsapp-mcp shortcut.
Research archive: research/whatsapp-automation (70 sources, 3
conclusions). Nothing built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Feed panes in the multiplexer (vpane_* control commands, main-vertical layout, header row),
src/orch (board, panel, formatter, transport, CAP adapter), Agent observer and cancel flag,
supervision (idle warn/cancel, user close cancels, soft then hard stop), CAP answers collected
through the spill path. Research archived under background-work-limits.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- New ask_user core tool: mid-turn user question with option picking,
refusing cleanly in autonomous mode, for background workers, and
without a tty; system prompt points at it instead of prose questions.
- Structural loop control for prompt-JSON CLI backends (opencode,
grok, claude, codex, gemini): shared cli_json_turn repairs a
missing JSON envelope exactly once, then accepts prose; prompt
contract hardened against narrate-without-calling.
- research/agentic-tool-loop-design: 74 sources plus 2 conclusions
backing the loop-control decisions; INDEX updated to 6 subjects.
- Plexer: always-active terminal multiplexing. NativePlexerController/
TmuxPlexerController share one interface, chosen by which env var is
already set (TAI_PLEXER_SOCKET vs TMUX_PANE); NativePlexerBackend is
the pty.fork()-based wrapper (raw mode, SIGWINCH resize forwarding,
a Unix-socket control protocol, continuous session logging, a
Ctrl+A prefix key distinct from tmux's own Ctrl+B). Auto-wraps new
interactive shells via a .bashrc hook mirroring the existing
command-not-found install pattern, gated on $TMUX/$TAI_PLEXER_ACTIVE/
a $HOME/.tai-no-plexer bypass. New plexer_send/read/type/status
tools, env-gated on a detected controller.
- tests/container.py + docker-compose.yml + tests/docker/Dockerfile:
a disposable podman/docker image for the tests that must never touch
a real machine (installing the plexer hook into a real ~/.bashrc, a
live tmux session). Required suite (test_seal.py, test_tai.py,
tests/plexer.py, tests/bashrc.py) gates the result; tests/rag.py
runs as best-effort since its real-LLM dependency can't be satisfied
inside a disposable container with no nested engine.
- tests/bashrc.py: real end-to-end hook install + bash -i auto-wrap,
anti-recursion, tmux-active skip, non-interactive skip, a live
TmuxPlexerController test, and an install/uninstall round-trip.
- tests/plexer.py: NativePlexerBackend under a real pty, plus a
dedicated robustness pass (file permissions, >64KB payload framing,
a malformed control request, a genuine self-deadlock fix in the
write-to-child path).
- tai_test.py: the shared async process-driving framework (pipe/pty
transport, group-wide kill-and-reap, a StdinBridge for --interactive)
both test files build on.
- ChatClient last-resort backend: when there's no local opencode CLI
but the sandbox box is already running, install opencode into it
(official Node tarball, not apt -- apt's nodejs/npm pull in
webpack/babel/jest as transitive deps) and route through it instead
of giving up. Only reachable after the first retry round already
failed everything else; never creates the box itself. TmuxPlexerController.status()
fixed to use `list-panes` instead of `display-message -p`, which
silently fell back to the default pane instead of erroring against
a dead target.
- Fix 4 pre-existing test_tai.py bugs surfaced by running it in a
clean environment for the first time: two stale mock signatures
(subprocess.run's stdout= kwarg, Chat.complete's deadline= kwarg),
one $TMUX-dependent catalog assertion, one tty-dependent color
assertion.
- research/: a sourced research archive (Source Dossier/Citation
Card/Conclusion tiers) backing the above design decisions, with its
own audit.py consistency checker.
- .gitignore: fix a blanket *.md rule that was about to silently drop
the entire research/ archive from version control; add __pycache__/
and the one-off debug transcript this session started from.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>