1 import Base 2 3 # How long the loop sleeps after a tick (autoswitch.py _next_delay and 4 # _respect_poll_plan). Bend has no random source, so the +-10 % jitter of the 5 # normal cadence is not applied: the delay is the interval itself. 6 7 # What a tick came to; the `--once` exit code is `exit_code`. 8 9 type Outcome is Data: 10 Switched{} 11 NoAction{} 12 Blocked{} 13 Failed{} 14 15 # What the tick tells the sleeper: when the soonest reset is (all exhausted), 16 # whether waiting a long time is right (no candidates), whether the active 17 # token is expired on an idle Claude Code, and the active row's next poll. 18 type Info is Data: 19 Info{outcome: Outcome, sleep_until: Maybe<&2, U32>, blocked_long: Bool, idle_slow: Bool, next_poll_at: Maybe<&2, U32>} 20 21 def exit_code(o: Outcome) -> U32: 22 match o: 23 case Switched{}: 24 0 25 case Failed{}: 26 1 27 case NoAction{}: 28 2 29 case Blocked{}: 30 3 31 32 def no_reset_fallback() -> U32: 33 300 34 35 def max_sleep() -> U32: 36 600 37 38 def urgent() -> U32: 39 60 40 41 def sel(+c: Bool, +x: U32, +y: U32) -> U32: 42 match c: 43 case True{}: 44 x 45 case False{}: 46 y 47 48 def max_u(+a: U32, +b: U32) -> U32: 49 sel(U32.is_gt(a, b), a, b) 50 51 def min_u(+a: U32, +b: U32) -> U32: 52 sel(U32.is_lt(a, b), a, b) 53 54 # a - b, 0 when b is later. 55 def since(+a: U32, +b: U32) -> U32: 56 sel(U32.is_gt(a, b), (a - b : U32), 0) 57 58 # Shorten a normal sleep to the active row's own next poll, never under 60 s. 59 def respect_plan(+next_poll: Maybe<&2, U32>, +now: U32, +delay: U32) -> U32: 60 match next_poll: 61 case None{}: 62 delay 63 case Some{t}: 64 min_u(delay, max_u(since(t, now), urgent())) 65 66 def blocked_delay(+until: Maybe<&2, U32>, +long: Bool, +now: U32, +interval: U32, +normal: U32) -> U32: 67 match until: 68 case Some{t}: 69 min_u(max_u(since(t, now), interval), max_sleep()) 70 case None{}: 71 sel(long, max_u(interval, no_reset_fallback()), normal) 72 73 def next_delay2(o: Outcome, info: Info, +now: U32, +interval: U32) -> U32: 74 match o info: 75 case Blocked{} Info{oc, until, long, idle, np}: 76 blocked_delay(until, long, now, interval, respect_plan(np, now, interval)) 77 case NoAction{} Info{oc, until, long, idle, np}: 78 sel(idle, max_u(interval, no_reset_fallback()), respect_plan(np, now, interval)) 79 case _ Info{oc, until, long, idle, np}: 80 respect_plan(np, now, interval) 81 82 def next_delay(info: Info, +now: U32, +interval: U32) -> U32: 83 match info: 84 case Info{+oc, until, long, idle, np}: 85 next_delay2(oc, info, now, interval) 86 87 # A `sleep` event is emitted only when the delay is over 1.5 intervals. 88 def announce(+delay: U32, +interval: U32) -> Bool: 89 U32.is_gt((delay * 2 : U32), (interval * 3 : U32))