mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
feat(agent): a setup step that prepares fast Lab builds for the installed version, kept alive while it runs
This commit is contained in:
1 parent
eb3da7595f
commit
58c83968de
5 files changed
+47
-7
No files matched your search
@@ -378,7 +378,7 @@ object AgentHost {
|
||||
now = SystemClock.elapsedRealtime()
|
||||
appContext?.let { app ->
|
||||
lab = LabProgress.read(LinuxRuntime.rootDir(app))
|
||||
if (state == State.WORKING) AgentHostService.update(app)
|
||||
AgentHostService.update(app)
|
||||
}
|
||||
main.postDelayed(::tick, if (state == State.WORKING || lab != null) TICK_MS else IDLE_TICK_MS)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class AgentHostService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent?.action == ACTION_STOP) {
|
||||
if (AgentHost.lab?.running == true) AgentHost.cancelLabBuild(this)
|
||||
AgentHost.stop(this)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
@@ -30,19 +31,35 @@ class AgentHostService : Service() {
|
||||
private const val NOTIFICATION_ID = 4107
|
||||
private const val ACTION_STOP = "com.droiddeck.launcher.agent.STOP"
|
||||
private var lastText: String? = null
|
||||
@Volatile private var started = false
|
||||
/** Setup steps that keep the app alive while they run, besides the agent's turn. */
|
||||
private var setupSteps = 0
|
||||
|
||||
fun start(context: Context) {
|
||||
lastText = null
|
||||
started = true
|
||||
runCatching { context.startForegroundService(Intent(context, AgentHostService::class.java)) }
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
if (setupSteps > 0) return
|
||||
started = false
|
||||
context.stopService(Intent(context, AgentHostService::class.java))
|
||||
}
|
||||
|
||||
fun setupStarted(context: Context) {
|
||||
setupSteps++
|
||||
start(context)
|
||||
}
|
||||
|
||||
fun setupEnded(context: Context) {
|
||||
setupSteps = (setupSteps - 1).coerceAtLeast(0)
|
||||
if (AgentHost.state != AgentHost.State.WORKING) stop(context)
|
||||
}
|
||||
|
||||
/** Refreshes the notification with the current step; unchanged text is not re-posted. */
|
||||
fun update(context: Context) {
|
||||
if (AgentHost.state != AgentHost.State.WORKING) return
|
||||
if (!started) return
|
||||
val text = describe(context)
|
||||
if (text == lastText) return
|
||||
lastText = text
|
||||
@@ -52,6 +69,7 @@ class AgentHostService : Service() {
|
||||
private fun describe(context: Context): String {
|
||||
val lab = AgentHost.lab?.takeIf { it.running }
|
||||
if (lab != null) return context.getString(R.string.agent_lab_progress, lab.permille / 10, minutes(lab.etaSeconds.toLong()))
|
||||
if (AgentHost.state != AgentHost.State.WORKING) return context.getString(R.string.agent_setting_up)
|
||||
val tool = AgentHost.running()
|
||||
val elapsed = minutes((SystemClock.elapsedRealtime() - AgentHost.turnStarted) / 1000)
|
||||
return if (tool != null) "${tool.tool} · ${tool.detail}".take(120) + " · $elapsed" else context.getString(R.string.agent_thinking_for, elapsed)
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit
|
||||
* and to make Lab builds on the device.
|
||||
*/
|
||||
object AgentSetup {
|
||||
enum class Step(val required: Boolean) { CONNECTION(true), INSTALL(true), SIGN_IN(true), GITHUB(false), BUILD_TOOLS(false) }
|
||||
enum class Step(val required: Boolean) { CONNECTION(true), INSTALL(true), SIGN_IN(true), GITHUB(false), BUILD_TOOLS(false), PREPARE(false) }
|
||||
enum class Status { UNKNOWN, DONE, TODO, WORKING, FAILED }
|
||||
|
||||
val status = mutableStateMapOf<Step, Status>()
|
||||
@@ -48,8 +48,8 @@ object AgentSetup {
|
||||
for (step in Step.entries) if (status[step] != Status.WORKING) {
|
||||
when (step) {
|
||||
Step.INSTALL -> set(step, if (AgentHost.guestFileExists(app, harness.binary)) Status.DONE else Status.TODO)
|
||||
Step.BUILD_TOOLS -> set(step, if (AgentHost.guestFileExists(app, "/root/aapt2/aapt2") &&
|
||||
AgentHost.guestFileExists(app, "$CHECKOUT/.git")) Status.DONE else Status.TODO)
|
||||
Step.BUILD_TOOLS -> set(step, if (buildToolsInstalled(app)) Status.DONE else Status.TODO)
|
||||
Step.PREPARE -> set(step, if (prepared(app)) Status.DONE else Status.TODO)
|
||||
Step.CONNECTION -> probes += step to listOf("sh", "-c",
|
||||
"for i in 1 2 3; do adb connect \"\$ADB_SERIAL\" >/dev/null 2>&1; " +
|
||||
"adb -s \"\$ADB_SERIAL\" get-state 2>&1 | tee /dev/stderr | grep -q device && exit 0; sleep 1; done; exit 1")
|
||||
@@ -74,9 +74,11 @@ object AgentSetup {
|
||||
Step.GITHUB -> listOf("dd-gh-login")
|
||||
Step.BUILD_TOOLS -> listOf("sh", "-c", "set -e; [ -d $CHECKOUT/.git ] || git clone --filter=blob:none $REPO $CHECKOUT; " +
|
||||
"cd $CHECKOUT; [ -x tools/agent/setup ] || { echo 'This DroidDeck version has no tools/agent/setup yet'; exit 1; }; tools/agent/setup")
|
||||
Step.PREPARE -> listOf("sh", "-c", "cd $CHECKOUT && dd-lab-build --prepare")
|
||||
}
|
||||
set(step, Status.WORKING)
|
||||
detail.remove(step)
|
||||
AgentHostService.setupStarted(app)
|
||||
Thread({
|
||||
val result = runCatching {
|
||||
if (step == Step.INSTALL) installHelpers(app)
|
||||
@@ -89,6 +91,7 @@ object AgentSetup {
|
||||
child.waitFor()
|
||||
}
|
||||
main.post {
|
||||
AgentHostService.setupEnded(app)
|
||||
if (result.getOrNull() == 0) {
|
||||
detail.remove(step)
|
||||
status[step] = Status.UNKNOWN
|
||||
@@ -101,6 +104,14 @@ object AgentSetup {
|
||||
}, "agent-setup-${step.name.lowercase()}").start()
|
||||
}
|
||||
|
||||
fun buildToolsInstalled(app: Context) = AgentHost.guestFileExists(app, "/root/aapt2/aapt2") && AgentHost.guestFileExists(app, "$CHECKOUT/.git")
|
||||
|
||||
/** dd-lab-build compiled this install's commit, so a Lab build compiles only what changed. */
|
||||
private fun prepared(app: Context) = runCatching {
|
||||
File(LinuxRuntime.rootDir(app), "root/.cache/dd-lab-build-base").readText().trim()
|
||||
.startsWith(com.droiddeck.launcher.lab.LabOverlay.apkCommit.take(7))
|
||||
}.getOrDefault(false)
|
||||
|
||||
/** Drops what the checks found, which belonged to the previous harness. */
|
||||
fun forget() {
|
||||
status.clear()
|
||||
|
||||
@@ -65,7 +65,10 @@ internal fun AgentSetupView(modifier: Modifier, onDone: () -> Unit = {}) {
|
||||
steps.forEachIndexed { index, step ->
|
||||
val status = AgentSetup.status[step] ?: Status.UNKNOWN
|
||||
val (title, hint) = labels(step, AgentHost.harness.name)
|
||||
val detail = AgentSetup.detail[step] ?: hint
|
||||
val lab = AgentHost.lab?.takeIf { it.prepare && it.running }
|
||||
val detail = if (step == Step.PREPARE && lab != null)
|
||||
stringResource(R.string.agent_prepare_progress, lab.permille / 10, clock(lab.etaSeconds.toLong().coerceAtLeast(0)))
|
||||
else AgentSetup.detail[step] ?: hint
|
||||
CheckRow(
|
||||
when (status) {
|
||||
Status.DONE -> CheckState.OK
|
||||
@@ -74,7 +77,8 @@ internal fun AgentSetupView(modifier: Modifier, onDone: () -> Unit = {}) {
|
||||
},
|
||||
title, detail, divider = index < steps.lastIndex,
|
||||
) {
|
||||
val blocked = step == Step.SIGN_IN && AgentSetup.status[Step.INSTALL] != Status.DONE
|
||||
val blocked = (step == Step.SIGN_IN && AgentSetup.status[Step.INSTALL] != Status.DONE) ||
|
||||
(step == Step.PREPARE && AgentSetup.status[Step.BUILD_TOOLS] != Status.DONE)
|
||||
if ((status == Status.TODO || status == Status.FAILED) && !blocked) {
|
||||
val label = stringResource(if (status == Status.FAILED) R.string.agent_setup_retry else action(step))
|
||||
val run = { if (step == Step.CONNECTION) pair(context) else AgentSetup.run(context, step) }
|
||||
@@ -151,6 +155,7 @@ private fun labels(step: Step, harness: String): Pair<String, String> = when (st
|
||||
Step.SIGN_IN -> stringResource(R.string.agent_step_sign_in, harness) to stringResource(R.string.agent_step_sign_in_hint)
|
||||
Step.GITHUB -> stringResource(R.string.agent_step_github) to stringResource(R.string.agent_step_github_hint)
|
||||
Step.BUILD_TOOLS -> stringResource(R.string.agent_step_build) to stringResource(R.string.agent_step_build_hint)
|
||||
Step.PREPARE -> stringResource(R.string.agent_step_prepare) to stringResource(R.string.agent_step_prepare_hint)
|
||||
}
|
||||
|
||||
private fun action(step: Step): Int = when (step) {
|
||||
@@ -158,6 +163,7 @@ private fun action(step: Step): Int = when (step) {
|
||||
Step.INSTALL -> R.string.agent_action_install
|
||||
Step.SIGN_IN, Step.GITHUB -> R.string.agent_action_sign_in
|
||||
Step.BUILD_TOOLS -> R.string.agent_action_install
|
||||
Step.PREPARE -> R.string.agent_action_prepare
|
||||
}
|
||||
|
||||
/** The pairing DroidDeck already uses for the phantom process fix; the agent reuses its key. */
|
||||
|
||||
@@ -1164,6 +1164,7 @@
|
||||
<string name="agent_failed">Failed: %1$s</string>
|
||||
<string name="agent_exited">%1$s ended (exit %2$d)</string>
|
||||
<string name="agent_stopped">Stopped</string>
|
||||
<string name="agent_setting_up">Setting up the agent</string>
|
||||
<string name="agent_rebuild_lab">Rebuild my Lab build %1$s for the installed version of DroidDeck and restart on it.</string>
|
||||
<string name="agent_tokens">%1$s in · %2$s out</string>
|
||||
<string name="agent_thinking">Thinking</string>
|
||||
@@ -1220,6 +1221,10 @@
|
||||
<string name="agent_step_github_hint">Optional, to open pull requests</string>
|
||||
<string name="agent_step_build">Build tools</string>
|
||||
<string name="agent_step_build_hint">Optional, for Lab builds on this device · about 5 GB</string>
|
||||
<string name="agent_step_prepare">Fast Lab builds</string>
|
||||
<string name="agent_step_prepare_hint">Compiles this version ahead, so Lab builds take minutes · about 20 minutes once</string>
|
||||
<string name="agent_prepare_progress">Compiling · %1$d%% · about %2$s left</string>
|
||||
<string name="agent_action_prepare">Prepare</string>
|
||||
<string name="agent_action_pair">Pair</string>
|
||||
<string name="agent_action_install">Install</string>
|
||||
<string name="agent_action_sign_in">Sign in</string>
|
||||
|
||||
Reference in new issue
Block a user