Forgejo doesn't support @actions/artifact v4. Package the tarball
and print sizes instead. Binaries can be grabbed from the runner
workspace or deployed directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The featherchat submodule uses SSH URL which doesn't work in CI.
Convert to HTTPS via git insteadOf before submodule init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Forgejo runner needs Node.js for actions/checkout@v4.
catthehacker/ubuntu:act-latest has Node.js pre-installed.
Also install Rust in the workflow since the base image doesn't have it.
Build triggers on main + feat/* branches now.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add RoomUpdate signal message to wzp-proto with participant count + list
- Add RoomParticipant struct (fingerprint + optional alias)
- Store fingerprint/alias in relay Participant struct
- Broadcast RoomUpdate to all room members on join and leave
- Add signal recv task in Android engine to handle RoomUpdate
- Surface room_participant_count + room_participants in CallStats JSON
- Show "X in room" with participant names in Android in-call UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wire CallService foreground service for background calls (microphone type)
- Add Voice Volume + Mic Gain sliders (-20 to +20 dB) applied in Kotlin
- Connect AudioRouteManager for real speaker toggle via AudioManager
- Feed quinn QUIC RTT into PathMonitor, display Loss/RTT/Jitter from live data
- Nuclear teardown between calls — recreate engine + audio pipeline each call
- Fix re-entrant teardown loop from CallService notification callback
- Park audio threads as daemons to avoid libcrypto TLS destructor crash on exit
- Remove duplicate wakelocks from Activity (service owns them now)
- Strip AEC + denoise from capture path, keep AGC only (incremental approach)
- Fix .so copy target: libwzp_android.so not libwzp.so
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wire AutoGainControl on both capture (mic → encode) and playout
(decode → speaker) paths to normalize volume levels
- Add server list with add/remove custom server dialog
- Add IPv4/IPv6 preference toggle for DNS resolution
- Resolve DNS hostnames to IP in Kotlin before passing to Rust engine
- Revert to IP addresses for default servers (DNS still broken on QUIC)
AGC confirmed working — voice levels noticeably improved in testing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Partial wake lock + WiFi high-perf lock during calls — audio
continues when screen is off / phone is locked
- Server selector: toggle between LAN (172.16.81.175) and Pangolin
(pangolin.manko.yoga) before connecting
- Room name editable in idle screen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AudioPipeline: Kotlin AudioRecord/AudioTrack on JVM threads, PCM
shuttled to Rust via lock-free ring buffers + JNI
- FEC: RaptorQ fountain codes on encode (5 frames/block, 20% repair
ratio for GOOD profile), decoder feeds repair symbols for recovery
- Real audio level meter from mic RMS (replaces fake animation)
- Room name editable in UI (default: "android")
- Relay changed to pangolin.manko.yoga:4433
- Stats overlay shows FEC recovered count
- CallState now synced from polled stats (fixes "Connecting" stuck bug)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JS-based upload-artifact action doesn't work with act runner.
Use curl to create a pre-release and attach the tarball instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSH has no keys in the container. Use exact URL remap to
https://<token>@git.tbs.amn.gg/manawenuz/featherChat.git
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use ssh://git@git.tbs.amn.gg:2222/ instead of HTTPS token auth
which gets 403 on cross-repo access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wzp-crypto depends on deps/featherchat (git submodule). Remap the
origin SSH URL to the Forgejo HTTPS mirror with github.token auth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The act runner can't clone a private repo over HTTPS without credentials.
Inject the auto-provided github.token into the clone URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
act runner uses bare ubuntu:24.04 without Node.js — actions/checkout,
actions/upload-artifact, etc. all fail. Replace with plain git clone
and shell commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The act runner doesn't have Node.js in the rust:1-bookworm container,
breaking JS-based actions (checkout, cache, upload-artifact).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pthread_create crashes on Android due to static bionic __init_tcb stubs
in the Rust std prebuilt rlibs. This is unfixable without rebuilding std.
Solution: run the entire call (QUIC connect, handshake, media send/recv)
on a single tokio current_thread runtime. The JNI startCall() now blocks,
so Kotlin dispatches it to Dispatchers.IO (JVM thread, not pthread).
Audio pipeline temporarily simplified to silence frames — will restore
once threading is solved (either via Java Thread or rebuilding std).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The getauxval override (dlsym wrapper) fixes SIGSEGV in
init_have_lse_atomics at library load time. The current_thread
tokio runtime avoids SEGV_ACCERR in pthread_create/__init_tcb.
Both fixes are required together.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multi-thread tokio runtime crashes with SEGV_ACCERR in __init_tcb
during pthread_create on Android (static bionic stubs from CRT).
Switch to current_thread runtime which runs network I/O on the
calling thread without spawning additional OS threads.
Also: clean up build.rs — use only libc++_shared.so (dynamic),
remove getauxval_fix.c hack, remove static c++/c++abi linking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
compiler-rt's init_have_lse_atomics calls getauxval(AT_HWCAP) at
library load time. The static getauxval from the CRT reads from
__libc_auxv which is NULL in shared libraries → SIGSEGV at 0x0.
Fix: compile getauxval_fix.c that provides a getauxval() which uses
dlsym(RTLD_DEFAULT) to find the real bionic getauxval at runtime.
Also switch to libc++_shared.so (bundled in APK) to avoid pulling
in static libc stubs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The .note.android.ident ELF section had API level 0x15 (21), causing
Android's bionic linker to block pthread_atfork (used by rand crate).
Fix: pass -P 26 to cargo-ndk and set linker to android26-clang.
Verified: ELF now shows 0x1a (26).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Simple Python script that captures adb logcat and serves it over HTTP.
Run on laptop, read from anywhere via curl/browser.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set cpp_link_stdlib(None) to suppress cc crate's automatic linking
- Explicitly link both c++_static and c++abi with NDK sysroot search path
- Fixes RTTI vtable symbol (_ZTVN10__cxxabiv117__class_type_infoE) error
- Verified: only liblog.so remains as dynamic dependency
Closes#001
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous fix linked c++_static but not c++abi. Android NDK splits the
static C++ runtime into two archives: libc++_static.a (STL) and
libc++abi.a (RTTI/exceptions). Without c++abi, dlopen fails on
_ZTVN10__cxxabiv117__class_type_infoE.
Now using cpp_link_stdlib(None) to suppress cc crate auto-linking, then
explicitly linking both c++_static and c++abi via cargo:rustc-link-lib.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
libc++_shared.so is no longer a runtime dependency — verified
via llvm-readelf. Only system libs (libdl, liblog, libm, libc) remain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The app crashed immediately when loading libwzp_android.so because the
cc crate's default dynamic linking produced a runtime dependency on
libc++_shared.so, which was never packaged into the APK.
Adding .cpp_link_stdlib(Some("c++_static")) to build.rs bakes the C++
runtime into libwzp_android.so directly, eliminating the missing .so.
See issues/001-libc++-shared-crash.md for full diagnosis and logcat trace.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CallActivity no longer auto-starts a call on launch
- CallViewModel lazily inits engine only when startCall() is called
- nativeGetStats nullable return handled safely in Kotlin
- Removed tracing_subscriber::fmt() which panics on Android (no stdout)
- All JNI calls wrapped in try/catch on Kotlin side
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New wzp-android crate with Oboe C++ backend, lock-free SPSC ring buffers,
engine orchestrator, codec pipeline, and Android Gradle project structure
- AEC (NLMS adaptive filter), AGC (two-stage with fast attack/slow release),
windowed-sinc FIR resampler replacing linear interpolation (wzp-codec)
- Opus encoder tuning: complexity 7 default, set_expected_loss support
- Mobile jitter buffer: asymmetric EMA (fast up/slow down), handoff spike
detection with 2s cooldown, configurable safety margin
- Network-aware quality control: cellular-specific thresholds, faster
downgrade on cellular, proactive tier drop on WiFi→cellular handoff,
FEC ratio boost during network transitions
- Handoff detection in PathMonitor via RTT jitter spike analysis
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detailed implementation plan for adding WS support directly to wzp-relay:
- Abstract Participant over transport type (Quic + WebSocket enum)
- New --ws-port flag for browser connections
- Cross-transport fan-out (QUIC↔WS in same rooms)
- Auth, room management, session cleanup unchanged
- Eliminates wzp-web container entirely
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RelayLink: QUIC connection to peer relay (SNI "_relay") for forwarding
specific sessions. Methods: connect, forward, add/remove_session, is_idle.
RelayLinkManager: manages connections to multiple peers.
- get_or_connect: lazy connection establishment
- forward_to: send media packet to specific peer
- register/unregister_session: track which sessions use which links
- Auto-closes idle links on session unregister
Protocol: added SignalMessage::SessionForward { session_id,
target_fingerprint, source_relay } and SessionForwardAck { session_id,
room_name } for relay-link session setup signaling.
Building block for P3-T7 (call setup over mesh) which wires
route resolution + relay links + handshake into a complete flow.
62 relay tests + 42 proto tests passing (7 new relay_link tests).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RouteResolver queries PresenceRegistry to determine how to reach a target:
- Route::Local — connected to this relay
- Route::DirectPeer(addr) — on a directly connected peer relay
- Route::Chain(addrs) — multi-hop (structure ready, single-hop for now)
- Route::NotFound — not in any known relay
Protocol: added SignalMessage::RouteQuery { fingerprint, ttl } and
RouteResponse { fingerprint, found, relay_chain } for peer-to-peer
route queries over probe connections.
HTTP API: GET /route/:fingerprint returns JSON with route type + chain.
Relay handles incoming RouteQuery on probe connections: looks up locally,
replies with RouteResponse. TTL decremented for future multi-hop forwarding.
55 relay tests + 42 proto tests passing (7 new route tests).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PresenceRegistry tracks who is connected where:
- register_local/unregister_local for directly connected users
- update_peer for fingerprints reported by peer relays
- lookup returns Local or Remote(addr)
- expire_stale removes entries older than timeout
Gossip via probe connections:
- New SignalMessage::PresenceUpdate { fingerprints, relay_addr }
- Probes send local fingerprints every 10s alongside Ping/Pong
- Receiving relay updates its remote presence table
HTTP API on metrics port:
- GET /presence — all known fingerprints + locations
- GET /presence/:fingerprint — single lookup
- GET /peers — peer relays + their connected users
Wired into relay main:
- Registry created at startup
- register_local after auth+handshake
- unregister_local on disconnect
- Passed to probe mesh and metrics server
Also marks FC-10 as DONE in integration tracker.
48 relay tests + 42 proto tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ServeDir now falls back to index.html for unknown paths (SPA routing).
https://host:port/manwe loads the page with room input pre-filled as "manwe".
JS getRoom() already reads the path, now the page actually loads.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PTT mode was causing delayed/lost audio because:
1. Silence suppression ate the start of speech after PTT release
2. Jitter buffer target depth was too high for interactive use
Web bridge now uses:
- suppression_enabled: false (PTT handles silence at browser level)
- jitter_target: 3 (60ms vs ~1s default)
- jitter_max: 20 (400ms cap)
- jitter_min: 1 (start playing after 20ms)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>