OXYGENHUB

Off charge, the runner leaves with a minute's notice ed6c64d23fca

Taken off charge, the phone finishes a job the hub expects to end within
a minute and hands back any other, its checkpoint saved; off Wi-Fi it
still hands back at once. Pins OxygenFlow's main (checkpoints,
leave_within).

refs #8
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-26 00:37 UTC
Signature
✔ signed by Andy McCall
Hash
ed6c64d23fca272d2ab54c611bc5995fa64d865d2e481ab1233028f631481d43
Depends on
0414607973cb 41e2e862abce 4aee5293461d 57c0824300db
Depended on by
8440d51a2275 26530d9758ee 62429abe5869 a49fd826e10b
In bookmarks
main
In tags
v0.1.0 v0.1.1 v0.1.2 v0.1.3 v0.1.4 v0.1.5 v0.1.6 v0.1.7 v0.1.8 v0.1.9
Edits
15 atoms, 0 tree operations, 6 files

Browse the tree at this change

modified CHANGELOG.md +1 −0
…
1 unchanged lines
2 2
3 3
## Unreleased
4 4
5 +
- **Off charge, the runner gives a minute's notice (#8, oxygenflow#76/#77):** taken off charge, the phone claims nothing more, finishes a job the hub expects to end within a minute and hands back any other to another runner, its checkpoint saved so the next attempt resumes where it stopped. Leaving Wi-Fi still hands back at once. Pins OxygenFlow's main (checkpoints, `leave_within`).
5 6
- **Diffs on a change (#21):** each file a change touched shows its diff in the app: context, removed and added lines with their old and new numbers, tinted, long lines wrapped, gaps between hunks marked. A small change (up to five files, 400 lines) opens every diff; a larger one starts folded and a file's row opens it. The icon on the row still opens the file at the change. From the hub's change API (hub #320); an older hub sends no lines, and the row says so.
6 7
- **The phone as an OxygenFlow runner (#8, first part):** Account → OxygenFlow runner → *Offer this phone as a runner* asks the hub for a one-time token (`POST /api/v1/user/runner-token`) and registers the phone as a runner for your own repositories, labelled `android`, offering the WASM tier. The runner is OxygenFlow's own, built into the app through rust-core (OxygenFlow is a subrepo, `oxygenflow/`, and rust-core's oxy-core is the one it pins), running WASM modules in a thread with wasmtime. It runs in a foreground service with a notification, only while the phone is charging on Wi-Fi (an unmetered network); coming off charge or off Wi-Fi drains it (it takes nothing more and finishes its job). Start and Stop on the card. The app is larger: the runner and wasmtime are about 29 MB per architecture.
7 8
- **Proposals on the dashboard (#18):** Waiting for your review (open proposals by others in your repositories you have not reviewed) and Your proposals (your open ones and drafts), as the hub's dashboard lists them, each opening the proposal screen where it can be landed. From the hub's `GET /api/v1/user/proposals`; a hub without it shows neither.
…
6 unchanged lines
modified app/src/main/java/io/oxygenhub/android/runner/PhoneRunner.kt +10 −8
…
41 unchanged lines
42 42
        /** Told to stop: finishing what it holds. */
43 43
        data class Draining(val why: String) : State
44 44
45 -
        /** Told to leave: handing its job back to the hub for another runner. */
46 -
        data class Leaving(val why: String) : State
45 +
        /** Told to leave: handing its job back to the hub for another runner, unless due within [noticeSecs]. */
46 +
        data class Leaving(val why: String, val noticeSecs: Long = 0) : State
47 47
48 48
        data class Failed(val message: String) : State
49 49
    }
…
82 unchanged lines
132 132
    }
133 133
134 134
    /**
135 -
     * Leave now (oxygenflow#76): stop what it holds and hand it back to the
136 -
     * hub, which gives it to another runner ahead of new work. For leaving
137 -
     * the allowed network: finishing would use the network the owner ruled out.
135 +
     * Leave (oxygenflow#76): claim nothing more and hand what it holds back
136 +
     * to the hub, which gives it to another runner ahead of new work. With
137 +
     * [noticeSecs], a job the hub expects to finish within it finishes
138 +
     * first: a minute off charge. None off the allowed network, where
139 +
     * finishing would use the network the owner ruled out.
138 140
     */
139 -
    fun leave(why: String) {
141 +
    fun leave(why: String, noticeSecs: Long = 0) {
140 142
        val now = _state.value
141 143
        if (now == State.Running || now is State.Draining) {
142 -
            _state.value = State.Leaving(why)
143 -
            runnerLeave(why)
144 +
            _state.value = State.Leaving(why, noticeSecs)
145 +
            runnerLeave(why, noticeSecs.toULong())
144 146
        }
145 147
    }
146 148
}
modified app/src/main/java/io/oxygenhub/android/runner/RunnerCard.kt +5 −1
…
62 unchanged lines
63 63
                is PhoneRunner.State.Waiting -> "Not running: ${s.why}." to MaterialTheme.colorScheme.onSurfaceVariant
64 64
                PhoneRunner.State.Running -> "Running as ${PhoneRunner.name()}: taking WASM jobs." to status.ok
65 65
                is PhoneRunner.State.Draining -> "Draining (${s.why}): finishing its job, then stopping." to status.warn
66 -
                is PhoneRunner.State.Leaving -> "Leaving (${s.why}): handing its job back for another runner, then stopping." to status.warn
66 +
                is PhoneRunner.State.Leaving -> if (s.noticeSecs > 0) {
67 +
                    "Leaving (${s.why}): finishing a job due within ${s.noticeSecs} s, handing back any other, then stopping." to status.warn
68 +
                } else {
69 +
                    "Leaving (${s.why}): handing its job back for another runner, then stopping." to status.warn
70 +
                }
67 71
                is PhoneRunner.State.Failed -> "Stopped: ${s.message}" to status.bad
68 72
            }
69 73
            Text(line, style = MaterialTheme.typography.bodyMedium, color = colour)
…
33 unchanged lines
modified app/src/main/java/io/oxygenhub/android/runner/RunnerService.kt +6 −5
…
22 unchanged lines
23 23
/**
24 24
 * Runs the phone's OxygenFlow runner (#8) in the foreground, with a
25 25
 * notification saying so, while the phone is on charge and on Wi-Fi.
26 -
 * Leaving the charger drains it (it claims nothing more, finishes and
27 -
 * reports the job it holds); leaving Wi-Fi hands the job back to the hub
28 -
 * for another runner (oxygenflow#76). While its owner wants it, the service
26 +
 * Leaving the charger gives it a minute: it claims nothing more, finishes
27 +
 * a job the hub expects to end within it and hands back any other, its
28 +
 * checkpoint saved; leaving Wi-Fi hands the job back at once
29 +
 * (oxygenflow#76). While its owner wants it, the service
29 30
 * then waits, and serves again once the phone is back on charge and Wi-Fi.
30 31
 */
31 32
class RunnerService : Service() {
…
6 unchanged lines
38 39
        override fun onReceive(context: Context, intent: Intent) {
39 40
            when (intent.action) {
40 41
                Intent.ACTION_POWER_DISCONNECTED -> {
41 -
                    PhoneRunner.drain("taken off charge")
42 -
                    notify("Draining: taken off charge")
42 +
                    PhoneRunner.leave("taken off charge", noticeSecs = 60)
43 +
                    notify("Leaving: taken off charge")
43 44
                }
44 45
                Intent.ACTION_POWER_CONNECTED -> serveIfAllowed()
45 46
            }
…
133 unchanged lines
replaced oxygenflow +0 −0

No line changes.

modified rust-core/src/lib.rs +8 −6
…
61 unchanged lines
62 62
    flow::runner::stop();
63 63
}
64 64
65 -
/// Leave now (oxygenflow#76): claim nothing more, stop the jobs it holds
66 -
/// and hand them back to the hub, which gives them to another runner ahead
67 -
/// of new work; [`runner_serve`] then returns. For leaving the allowed
68 -
/// network, where finishing would use the network the owner ruled out.
65 +
/// Leave (oxygenflow#76): claim nothing more, and hand the jobs it holds
66 +
/// back to the hub, which gives them to another runner ahead of new work;
67 +
/// [`runner_serve`] then returns. With `notice_secs`, a job the hub expects
68 +
/// to finish within the notice finishes first (its checkpoint saved if
69 +
/// not). 0 for leaving the allowed network, where finishing would use the
70 +
/// network the owner ruled out; a minute for leaving the charger.
69 71
#[uniffi::export]
70 -
pub fn runner_leave(reason: String) {
71 -
    flow::runner::leave(&reason);
72 +
pub fn runner_leave(reason: String, notice_secs: u64) {
73 +
    flow::runner::leave_within(&reason, std::time::Duration::from_secs(notice_secs));
72 74
}
73 75
74 76
/// The tiers this phone offers (the WASM tier), and why not the others.
…
8 unchanged lines