Skip to content

feat(agent-runtime): candidate-list connection with build-machine IP injection - #148

Open
kitakkun wants to merge 1 commit into
mainfrom
feat/build-host-candidates
Open

feat(agent-runtime): candidate-list connection with build-machine IP injection#148
kitakkun wants to merge 1 commit into
mainfrom
feat/build-host-candidates

Conversation

@kitakkun

Copy link
Copy Markdown
Owner

Motivation

The primary zero-config goal: a physical device on the LAN should reach the debugger host with no
host/IP written anywhere
. Hardcoding the build machine's LAN IP in
connection { host = "192.168.x.x" } is machine-specific and brittle. This PR makes the agent try an
ordered list of candidate addresses and adds a Gradle plugin that injects the build machine's own
addresses at build time.

Candidate-list connection core (jetwhale-agent-runtime)

Connection targets are now an ordered candidate list, tried per connection attempt with a short
per-candidate timeout (2s) and walked the same way on every reconnect. The first that answers wins
and is logged at INFO. Order:

  1. Explicit host set in connection { } — when set, it wins and is tried first.
  2. Build-injected LAN addresses — the build machine's IPv4s (then hostname).
  3. localhost — fallback for emulators, simulators, and ADB-forwarded devices.

So one build runs on a physical device (reaches the build machine) and on an emulator (falls through
to localhost) with no code change.

Build-machine IP injection (com.kitakkun.jetwhale.agent Gradle plugin)

A new plugin apps apply:

plugins { id("com.kitakkun.jetwhale.agent") }

At build time it collects the machine's non-loopback IPv4 addresses + hostname and generates a source
file into the module's commonMain (or main) Kotlin source set:

public fun applyJetWhaleBuildEnvironment()  // registers them with JetWhaleBuildEnvironment

Linkage

Cross-module auto-run is not portable across all KMP targets (no top-level init on native/JS), so the
generated function is wired through the existing initialize path: call applyJetWhaleBuildEnvironment()
once before startJetWhale {} (the demo does this). When injection is disabled or the plugin was
never applied, the function is generated as a no-op, so the call site always compiles.

Configuration

jetwhale { injectBuildHostCandidates = false }  // e.g. for CI/release builds

Default is on. Explicit DSL host config always overrides injected candidates.

Staleness

Addresses are captured at build time — correct as long as the build machine keeps the same addresses
(the norm when one machine builds and debugs). When stale, the agent simply falls through to the next
candidate. Documented in KDoc and docs/guide/getting-started.md.

Demo

InitializeJetWhale.kt drops its hardcoded host entirely, proving the zero-config path on a physical
device and an emulator alike.

Tests

HostCandidateResolverTest covers ordering (injected-first vs explicit-wins), dedup, localhost-only,
and idempotent registration. The Gradle plugin's generation + source-set wiring is exercised
end-to-end by the demo build (:demo:shared:compileKotlinJvm compiles the generated source).

Verification

:jetwhale-gradle-plugin:build, :jetwhale-agent-runtime:jvmTest, compileKotlinIosArm64,
compileKotlinJs, compileKotlinLinuxX64, compileKotlinMingwX64, compileAndroidMain,
:demo:shared:compileKotlinJvm, checkLegacyAbi, spotlessApply all pass. ABI dumps updated
(new public JetWhaleBuildEnvironment).

Relationship to mDNS PR

Companion PR #146 adds mDNS/Bonjour discovery. It plugs in here as the optional last-resort candidate
source; this PR is the headline zero-config mechanism.

Copilot AI review requested due to automatic review settings July 18, 2026 20:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “zero-config” connection mechanism for JetWhale agents by (1) trying an ordered list of host candidates at runtime and (2) introducing a Gradle plugin that generates code to inject the build machine’s LAN addresses/hostname into the app at build time.

Changes:

  • Agent runtime now resolves an ordered, deduplicated host-candidate list (explicit host → build-injected addresses/hostname → localhost) and walks it on every (re)connect with a short per-candidate timeout.
  • New Gradle plugin com.kitakkun.jetwhale.agent generates applyJetWhaleBuildEnvironment() and wires generated sources into the consuming module’s Kotlin source set(s).
  • Demo + getting-started guide updated to use the generated no-op-safe initializer call and remove hardcoded host configuration.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
jetwhale-gradle-plugin/src/main/kotlin/com/kitakkun/jetwhale/gradle/JetWhaleAgentPlugin.kt New plugin: registers extension, generates build-environment source, wires into Kotlin source sets.
jetwhale-gradle-plugin/src/main/kotlin/com/kitakkun/jetwhale/gradle/GenerateBuildEnvironmentTask.kt Task that renders the generated applyJetWhaleBuildEnvironment() Kotlin source.
jetwhale-gradle-plugin/build.gradle.kts Adds Kotlin Gradle plugin compileOnly dependency and registers com.kitakkun.jetwhale.agent.
jetwhale-agent-runtime/src/commonMain/kotlin/com/kitakkun/jetwhale/agent/runtime/JetWhaleServiceDsl.kt Switches startup to pass resolved host candidates; tracks whether host was explicitly set.
jetwhale-agent-runtime/src/commonMain/kotlin/com/kitakkun/jetwhale/agent/runtime/JetWhaleMessagingService.kt Updates messaging-service API to accept ordered host candidates.
jetwhale-agent-runtime/src/commonMain/kotlin/com/kitakkun/jetwhale/agent/runtime/DefaultJetWhaleMessagingService.kt Implements candidate walking with per-candidate timeout and logging.
jetwhale-agent-runtime/src/commonMain/kotlin/com/kitakkun/jetwhale/agent/runtime/JetWhaleBuildEnvironment.kt New public registry for build-injected host candidates.
jetwhale-agent-runtime/src/commonMain/kotlin/com/kitakkun/jetwhale/agent/runtime/HostCandidateResolver.kt New resolver to order/dedup candidates (explicit vs injected vs localhost).
jetwhale-agent-runtime/src/jvmTest/kotlin/com/kitakkun/jetwhale/agent/runtime/HostCandidateResolverTest.kt Tests ordering, deduplication, and idempotent registration.
jetwhale-agent-runtime/api/jvm/jetwhale-agent-runtime.api ABI update for new public JetWhaleBuildEnvironment.
jetwhale-agent-runtime/api/jetwhale-agent-runtime.klib.api KLIB ABI update for new public JetWhaleBuildEnvironment.
docs/guide/getting-started.md Documents zero-config candidate resolution + CI disable switch.
demo/shared/src/commonMain/kotlin/com/kitakkun/jetwhale/demo/shared/InitializeJetWhale.kt Calls applyJetWhaleBuildEnvironment() and removes hardcoded host in demo.
demo/shared/build.gradle.kts Applies com.kitakkun.jetwhale.agent plugin for demo module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

injectEnabled.set(extension.injectBuildHostCandidates)
// Captured at configuration time on the build machine; staleness is acceptable (see class
// KDoc) and the values are task inputs so a machine/address change re-runs generation.
hostName.set(resolveHostName())
Comment on lines +56 to +57
// Captured at configuration time on the build machine; staleness is acceptable (see class
// KDoc) and the values are task inputs so a machine/address change re-runs generation.
Comment on lines 36 to 37
artifactId = "jetwhale-gradle-plugin"
name = "JetWhale Gradle Plugin"
…injection

Connection targets become an ordered candidate list tried per attempt with
a short per-candidate timeout (2s), walked the same way on reconnect:
explicit `host` (when set, wins) -> build-injected LAN addresses ->
localhost fallback. The winning candidate is logged at INFO.

A new `com.kitakkun.jetwhale.agent` Gradle plugin captures the build
machine's non-loopback IPv4 addresses and hostname at build time and
generates `applyJetWhaleBuildEnvironment()` into the applied module's
Kotlin source set, which registers them via the new public
`JetWhaleBuildEnvironment` registry. Call it once before `startJetWhale {}`
and a physical device reaches the build machine with no host/IP written;
emulators/simulators fall through to localhost. Injection is toggled with
`jetwhale { injectBuildHostCandidates = false }` (e.g. for CI); when off the
generated function is a no-op.

The demo drops its hardcoded host to prove the zero-config path.

mDNS discovery (separate PR) plugs in as the optional last-resort candidate
source.
@kitakkun
kitakkun force-pushed the feat/build-host-candidates branch from 80c890d to e140fb6 Compare July 19, 2026 05:28
@kitakkun kitakkun added feature agent Indicates that the issue is related to the agent-side (debug-target application) implementation. labels Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Indicates that the issue is related to the agent-side (debug-target application) implementation. feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants