Let a session resume itself when its usage limit lifts
Off by default and per session: it spends quota the moment quota exists,
with nobody watching, which is not a thing a default may decide. Switched
on from the session settings dialog, with the message it sends editable
("continue" unless something else is typed).
Running out of quota becomes a state rather than an error. The Claude
driver recognises its dialect's sentence -- `Claude AI usage limit
reached|1788546972` -- and reports `LimitReached` with the reset time it
gave; nothing above a driver matches on a string. The transcript draws it
as a divider, like a clear or a compaction.
The schedule is a plan to *ask*, never a plan to send. Both reset times
available are untrustworthy in the direction that matters -- the dialect's
is written when the turn fails, the endpoint's moves when the window does
-- so the wait ends in a question to the usage meter, and only `ok` with
no window at 100% sends anything. A window still spent reschedules to its
own reset time, which is what makes a limit that lifts late wait longer
and one that lifts early resume sooner. A meter that cannot be asked is a
longer wait too, never a send. A day after the limit was hit the wait
gives up and says so in the transcript, so a machine that can never be
asked is not retried for ever.
The schedule is persisted on the session: a five-hour window outlasts a
backend restart, and a wait forgotten across one never comes back.
Driven end to end with echo, never a real account: `/limit [minutes]`
reports the same event a real driver does and `/usage` sets what the meter
answers, deliberately separate so the two can disagree. The wait moved
from the dialect's two minutes to the meter's seven when the meter changed
its mind, and the message went out on the first check after the meter came
back under the limit.
Also makes the settings dialog scrollable, which these two controls made
necessary: at a 1.5x system font it clipped the last of them with nothing
on screen to say so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4821a02bd3
commit
6bdec6e785
17 files changed
+1405
-20
No files matched your search
@@ -293,6 +293,30 @@ pub struct SessionConfig {
|
||||
/// turned off in one tap where one that never arrived is not diagnosable.
|
||||
#[serde(default = "notify_default")]
|
||||
pub notify: bool,
|
||||
/// Whether a session stopped by the account's usage limit sends itself a
|
||||
/// message once the limit lifts, instead of waiting for a person.
|
||||
///
|
||||
/// Off unless somebody asked for it. It spends quota the moment it becomes
|
||||
/// available and it does so while nobody is looking, which is exactly the
|
||||
/// kind of thing that must not happen because a default said so.
|
||||
#[serde(default, skip_serializing_if = "not_set")]
|
||||
pub auto_resume: bool,
|
||||
/// What that message says. `None` is [`DEFAULT_RESUME_MESSAGE`], and stays
|
||||
/// reachable: it is this app's word, not one somebody chose, so clearing
|
||||
/// the field goes back to it rather than sending an empty message.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub auto_resume_message: Option<String>,
|
||||
/// The message this session owes itself once the limit lifts, and when to
|
||||
/// try. Written when a limit is hit, moved when the wait turns out to be
|
||||
/// wrong, and cleared when the message goes out or auto-resume is turned
|
||||
/// off -- see [`ScheduledResume`].
|
||||
///
|
||||
/// Persisted rather than held in memory because the wait outlives the
|
||||
/// process doing it: a five-hour window and a weekly one both routinely
|
||||
/// outlast a backend restart, and a resume forgotten across one is a
|
||||
/// session that silently never comes back.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub resume: Option<ScheduledResume>,
|
||||
/// Whether this session's process is stopped when the server exits, instead
|
||||
/// of being left running for the next start to adopt.
|
||||
///
|
||||
@@ -309,6 +333,28 @@ pub struct SessionConfig {
|
||||
pub created: f64,
|
||||
}
|
||||
|
||||
/// A message owed to a session whose account ran out, and when to try sending
|
||||
/// it.
|
||||
///
|
||||
/// `since` is the whole reason this is a struct: the wait is rescheduled every
|
||||
/// time the meter is asked and still says no, so `at` alone cannot say how long
|
||||
/// this has been going on -- and something has to, or a machine that can never
|
||||
/// be asked is retried until somebody notices. See `crate::resume`.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScheduledResume {
|
||||
/// Epoch seconds: when the limit is next worth checking. Never a promise
|
||||
/// that the message goes out then -- the meter is asked first.
|
||||
pub at: f64,
|
||||
/// Epoch seconds the limit was hit.
|
||||
pub since: f64,
|
||||
}
|
||||
|
||||
/// What an auto-resume says when nothing else was chosen. One word, because
|
||||
/// the session already knows what it was doing and this is only the nudge that
|
||||
/// lets it carry on.
|
||||
pub const DEFAULT_RESUME_MESSAGE: &str = "continue";
|
||||
|
||||
fn notify_default() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -486,6 +532,9 @@ mod tests {
|
||||
effort: None,
|
||||
params: BTreeMap::new(),
|
||||
notify: true,
|
||||
auto_resume: false,
|
||||
auto_resume_message: None,
|
||||
resume: None,
|
||||
throwaway: false,
|
||||
created: 1234.5,
|
||||
}],
|
||||
|
||||
Reference in new issue
Block a user