fix(tui): repaint the header context window on a /model switch

After `/model`, the header kept the previous model's window ("15K /
1.0M" on a 262K model) until the next turn or a restart. The pager
caches the window inside its context snapshot (streaming `totalTokens`
updates refresh it from the catalog) and nothing refreshed the cache
when the session's model changed. Comparing cached numbers against
catalog windows cannot tell a catalog-derived total from a shell
effective limit (KIGI_DEBUG_CONTEXT_WINDOW, header upgrades), so the
shell, which installs the effective window, now reports it:

- kigi-shell: `handle_set_session_model` returns the installed window;
  `model_switch::apply` stamps it as `totalContextTokens` in the
  `session/set_model` response meta and carries it on the
  `ModelChanged` broadcast (`context_window`, serde-default: old pagers
  ignore it, old leaders omit it). The response meta previously
  serialized the actor's inner `Result` and reported success on an
  actor failure; that error now propagates.
- kigi-tui: `Effect::SwitchModel` reads the meta; `SwitchModelComplete`
  carries `Ok(Option<u64>)`; the completion handler and the follower
  `ModelChanged` arm call `AgentView::apply_context_window`, which
  re-derives the snapshot (pct, free tokens) and reports whether it
  changed so a follower redraws on a window-only change.
- Tests: the bare `/model <name>` dispatch path asserts the snapshot and
  the drawn header; follower with/without the field; same-model new
  window redraw; actor returns the window; serde round trip.

Claude-Session: https://claude.ai/code/session_01RUqV8HVGXcAETyYbZ2tPQ4
This commit is contained in:
雷电芽衣 committed 2026-09-13 00:15:14 -04:00
1 parent ff92a00fea
commit b957e12370
16 files changed
+288 -27

No files matched your search

@@ -978,13 +978,13 @@ pub(in crate::app::dispatch) fn handle_switch_model_complete(
agent_id: AgentId,
model_id: acp::ModelId,
effort: Option<ReasoningEffort>,
result: Result<(), SwitchModelError>,
result: Result<Option<u64>, SwitchModelError>,
prev_model_id: Option<acp::ModelId>,
) -> Vec<Effect> {
if let Some(agent) = app.agents.get_mut(&agent_id) {
agent.session.model_switch_pending = false;
let mut effects = match result {
Ok(()) => {
Ok(context_window) => {
agent.session.user_model_preference = Some(model_id.clone());
let display_name = agent
.session
@@ -996,6 +996,9 @@ pub(in crate::app::dispatch) fn handle_switch_model_complete(
let prev_model = agent.session.models.current.clone();
let prev_effort = agent.session.models.reasoning_effort;
agent.session.models.set_current(model_id.clone(), effort);
if let Some(total) = context_window {
agent.apply_context_window(total);
}
let resolved_effort = agent.session.models.reasoning_effort;
let unchanged =
prev_model.as_ref() == Some(&model_id) && prev_effort == resolved_effort;