* fix(sdk-android): add @JvmOverloads to Uatu.start
Java callers can now invoke start(application) without supplying a
Configuration, matching the Kotlin default-arg ergonomics.
* feat(agent): add protocol_version to HELLO handshake
ProtocolVersion=1 lives on Message and is set by Hello(). Server.Accept
rejects mismatches with a clear error. SDK upgrades that don't change
the wire format keep the same protocol_version; bump on breaking changes.
* test(agent): assert protocol_version in Hello round-trip
* feat(sdk-android): send protocol_version=1 in HELLO
Mirrors agent.ProtocolVersion on the Go side. Bump in lockstep with
the Go constant when the wire format breaks.
* chore(sample-app): pull @uatu/spec from npm next tag
Replaces the file: dep. Copy-paste users can now npm install against
the registry. The release workflow publishes pre-release tags to
npm dist-tag 'next', so the sample tracks the latest rc without
manual version bumps. Lockfile currently resolves to 0.0.1-rc3.
* build(sdk-android): bump AGP 8.11.0 → 8.13.0
Required by com.vanniktech.maven.publish 0.36.0, bumped in the
following commit to pull a Dokka 2.x that handles JDK 21+ version
strings.
* build(sdk-android): migrate to vanniktech 0.36 + Dokka v2 javadoc
Fixes the `javaDocReleaseGeneration` crash on JDK 21+ hosts
(IllegalArgumentException: 25.0.2 in the bundled IntelliJ
JavaVersion.parse). Root cause: AGP's JavaDocGenerationTask pulled
an old Dokka whose vendored util-lang predated JDK 9+ version
strings. Fixed upstream in Dokka 2.1.0 (Kotlin/dokka#4202).
Changes in this commit:
- Bump vanniktech-maven-publish 0.30.0 → 0.36.0, which drops Dokka
v1 support and the SonatypeHost parameter (Central Portal is
now the default).
- Apply org.jetbrains.dokka and org.jetbrains.dokka-javadoc 2.2.0
so the javadoc jar is generated by Dokka 2.x directly, not AGP's
bundled-Dokka path.
- Switch AndroidSingleVariantLibrary to the explicit
JavadocJar.Dokka("dokkaGeneratePublicationJavadoc") form; the
old `publishJavadocJar = true` boolean now maps to plain
javadoc, which still goes through AGP's broken path on Android
source sets.
- Enable V2Enabled in gradle.properties (required by 0.36+) and
silence the transitional opt-in warning.
Verified: `make release-android-local` on openjdk 25.0.2 produces
aar, sources jar, javadoc jar, pom, and module; `./gradlew
:sdk-android:testDebugUnitTest :sidecar:test` still pass.
* fix(cli): fall back to node_modules for @uatu/spec resolution
Drop the hard failure when the uatu source tree is not reachable from
the spec file. Users integrating uatu in their own app have @uatu/spec
installed via npm; esbuild now resolves it from node_modules.
* build(gradle): drop :sample-app include from root settings
The sample now has its own Gradle project in examples/sample-app/android.
* build(sample-app): vendor gradle wrapper
Users running the sample build the APK via ./gradlew from inside the
sample-app's own android/ directory, no repo-root wrapper required.
* build(sample-app): make gradle project standalone
Drop the project(':sdk-android') dependency in favor of the Maven
Central coordinate io.github.priyanshujain:sdk-android. The sample now
owns its settings.gradle.kts and gradle.properties, so it builds
without any pieces of the uatu source tree.
* chore(sample-app): declare @uatu/spec npm dependency
Mirrors what a downstream user would put in their own package.json.
Uses file: for pre-release development; becomes a normal semver pin
once @uatu/spec ships to npm.
* docs(sample-app): rewrite justfile and add README
Justfile drops repo_root; all recipes run against the local gradle
wrapper and uatu from PATH. README is scoped to what a user needs to
run the sample against their own device.
* docs(manual): update sample install steps to standalone layout
./gradlew :sample-app:installDebug no longer exists; the sample owns
its own wrapper under android/.
* feat(sdk): log when Uatu.start succeeds
Silent SDK start makes the "SDK didn't connect" failure mode
impossible to debug. One INFO line at start time is enough.
* fix(sidecar): launch via am start -W instead of monkey
monkey -p <pkg> -c LAUNCHER 1 is unreliable on API 36+: it reports no
error but silently fails to start the activity, so the SDK never runs
and the CLI times out on the SDK-accept handshake.
Resolve the launcher activity via `cmd package resolve-activity
--brief` and launch it with `am start -W -n`. -W makes the call block
until the activity is up, which also makes the subsequent SDK
accept timing deterministic.
* feat(cli): auto-resolve Android device; boot AVD if none connected
--avd becomes optional. Resolution order:
- use any already-connected adb device;
- else if --avd names an existing AVD, boot it and wait for boot;
- else if --avd is missing or names no AVD, error with a clear message.
Falls back to $ANDROID_HOME/emulator/emulator when the binary is not on
PATH, so a standard Android SDK install works without extra shell setup.
* docs(sample-app): AVD is optional; document both paths
just test runs against any connected device. If none, pass AVD=<name>
to have uatu boot the emulator for you.
* feat(cli): auto-discover Android SDK; auto-pick the lone AVD
adb and emulator are looked up via PATH, then $ANDROID_HOME,
$ANDROID_SDK_ROOT, ~/Library/Android/sdk, ~/Android/Sdk, and the
Homebrew cask path. The discovered platform-tools directory is
prepended to the sidecar's PATH so its adb subprocess calls work too.
When --avd isn't passed and no device is connected, the CLI picks the
sole local AVD and boots it. Multiple AVDs → error listing them.
* build(make): add `make install` that go-installs uatu onto PATH
Puts `uatu` into $GOBIN (or $GOPATH/bin) so the sample and any local
dev flow can call it without PATH= prefixes.
* docs(sample-app): zero-config just test; dotenv-load for persistence
Drop the expectation that users prefix commands with PATH=, ANDROID_HOME=,
or AVD=. `just test` now works as-is; optional knobs can be pinned in a
.env file alongside the justfile.
* fix(sample-app): auto-detect ANDROID_HOME for Gradle tasks
The Go CLI finds the SDK itself, but AGP still needs ANDROID_HOME to
resolve `sdk.dir`. The justfile now resolves it from env or canonical
install paths before invoking ./gradlew, so `just install` works out
of the box on a standard Android SDK setup.
* gitignore runs directory for sample app
* feat(cli): add Version var and version subcommand
* build(gradle): introduce uatu.version property for lockstep releases
* build(sdk-android): swap GitHub Packages for vanniktech Maven Central plugin
* build(spec-api): make package publish-ready for npm
* ci(release): add goreleaser config for cross-platform uatu CLI builds
* ci: add ci and release GitHub Actions workflows
* ci: restrict ci.yml to PR + workflow_dispatch (no direct push to master)
* docs(release): add local release targets, env example, and install docs
* build(sdk-android): make signAllPublications conditional on signing key
* ci(release): stage sidecar JAR at embed path before go build
* chore(spec-api): regenerate package-lock for updated package.json
* ci: install protoc-gen-go plugins before buf generate
* ci: bump Node to 22 (required for --experimental-strip-types)
Coordinates: dev.uatu:sdk-android:0.0.1. Credentials read from
GH_TOKEN/GH_USERNAME (or GITHUB_TOKEN/GITHUB_ACTOR for CI).
.env is gitignored so local tokens stay out of git.
Consumers add the maven repo + debugImplementation in their
build.gradle and we're done.
Choreographer.getInstance() is per-thread and throws on threads
without a Looper. The previous impl called it from the SocketClient
reader thread, which surfaced as snapshot failures with empty
state during the sample-app round trip. Now the reader thread
posts a Runnable to the main Handler that, on the main thread,
gets the Choreographer and posts the frame callback.
Single-activity Android app that calls Uatu.start() in
SampleApplication.onCreate and registers three extractors
(app_state, click_count, uptime_millis). MainActivity has a
button so click_count exercises non-trivial state.
Uatu singleton owns an internal UatuRuntime that wires the socket
client, pauser, and extractor registry. start() sends HELLO on
connect, responds to PAUSE by running extractors inside the pause
window and replying with STATE, and releases the gate on RESUME.
LinkedHashMap preserves extractor registration order so trace
output is deterministic. Failed extractors log a warning and
record null rather than aborting the whole snapshot.
Worker thread calls pauseAndSnapshot with an extractor closure;
the Pauser posts a frame callback that runs extractors on the
main thread and then blocks that thread on a semaphore until
release() fires. FrameCallbackPoster abstracts Choreographer so
JVM unit tests drive it with a single-thread executor, while the
Android runtime uses ChoreographerPoster.
SocketClient owns a background reader thread, dispatches incoming
messages to a handler, and auto-reconnects with exponential backoff
when the transport drops. AgentTransport abstracts the socket so
JVM unit tests drive the client through piped streams while the
Android runtime uses LocalSocket over the abstract namespace.
Send is exposed to consumers via a MessageSender passed to
onConnected. Writes are serialized under the output stream lock.
Uses org.json (already on Android; org.json:json test dep for JVM
unit tests). Field names and framing mirror internal/agent/protocol.go
so Go-encoded frames decode on the SDK without a shared schema file.