anvilsign in

collin/browser-terminal-extension · 878ba185

Show the Claude model in the omnibar's pane rows

Collin Richards · 2026-08-18 06:44 UTC · 878ba18584ec9e968a19bb1f746cec27371e9d66 · parent 81bf09bf · browse files

modifieddaemon/src/agents.rs+25 −0
⋯ 62 unchanged lines
6363 /// Session title, when Claude has one.
6464 #[serde(default, skip_serializing_if = "Option::is_none")]
6565 pub name: Option<String>,
66+ /// The model id, e.g. `claude-opus-4-1-20250805`. Only `SessionStart`
67+ /// carries it, and even there it is not guaranteed, so this is carried
68+ /// forward across every later event the way `mode` and `name` are; it goes
69+ /// stale if the session switches models mid-conversation, but a stale
70+ /// answer beats none.
71+ #[serde(default, skip_serializing_if = "Option::is_none")]
72+ pub model: Option<String>,
6673 /// One line of the last thing the user asked, from `UserPromptSubmit`. It
6774 /// is what [`crate::pane_title`] writes to the pane title, and what the
6875 /// sidebar falls back to when a session has no name yet — which is most of
⋯ 119 unchanged lines
188195 _ => None,
189196 },
190197 name: str_field("session_name").or_else(|| carry(|r| r.name.clone())),
198+ model: str_field("model").or_else(|| carry(|r| r.model.clone())),
191199 // Only `UserPromptSubmit` carries one; every other event keeps the one
192200 // already on disk, so the title stays put for the whole turn instead of
193201 // blinking out on the first tool call.
⋯ 200 unchanged lines
394402 });
395403 }
396404
405+ /// `model` only ever arrives on `SessionStart`; every later event has to
406+ /// keep reporting it anyway.
407+ #[test]
408+ fn model_survives_events_that_omit_it() {
409+ with_temp_dir(|| {
410+ event(serde_json::json!({
411+ "session_id": "a", "hook_event_name": "SessionStart",
412+ "cwd": "/tmp/p", "model": "claude-opus-4-1-20250805",
413+ }));
414+ let r = event(serde_json::json!({
415+ "session_id": "a", "hook_event_name": "PreToolUse", "tool_name": "Bash",
416+ }))
417+ .unwrap();
418+ assert_eq!(r.model.as_deref(), Some("claude-opus-4-1-20250805"));
419+ });
420+ }
421+
397422 #[test]
398423 fn session_end_removes_the_record() {
399424 with_temp_dir(|| {
⋯ 84 unchanged lines
modifieddaemon/src/status.rs+5 −0
⋯ 62 unchanged lines
6363 /// asked it. The same string [`crate::pane_title`] gives to tmux, so a
6464 /// session reads the same in the sidebar and in the status line.
6565 pub title: Option<String>,
66+ /// The model id, when `SessionStart` reported one. See
67+ /// [`crate::agents::Record::model`] for why it can be stale or absent.
68+ pub model: Option<String>,
6669 }
6770
6871 /// The tmux user option the sidebar keeps a session's colour in.
⋯ 367 unchanged lines
436439 tool: record.and_then(|r| r.tool.clone()),
437440 message: record.and_then(|r| r.message.clone()),
438441 title: record.and_then(|r| r.name.clone().or_else(|| r.prompt.clone())),
442+ model: record.and_then(|r| r.model.clone()),
439443 })
440444 })
441445 .collect();
⋯ 21 unchanged lines
463467 tool: None,
464468 message: None,
465469 name: None,
470+ model: None,
466471 prompt: None,
467472 frame: 0,
468473 updated,
⋯ 49 unchanged lines
modifieddaemon/src/window_status.rs+1 −0
⋯ 173 unchanged lines
174174 tool: None,
175175 message: None,
176176 name: None,
177+ model: None,
177178 prompt: None,
178179 frame,
179180 updated: 0,
⋯ 24 unchanged lines
modifiedextension/sidebar.js+17 −2
⋯ 3473 unchanged lines
34743474 // knows an id for, and they are the ones worth addressing individually — the
34753475 // rest of a window's panes are reached by going to the window.
34763476 for (const a of lastAgents) {
3477+ const family = modelFamily(a.model);
34773478 // What you would search for is what it is doing, not "pane %12".
3478- const text = [a.title, a.message, a.tool, a.name].filter(Boolean).join(" ");
3479+ const text = [a.title, a.message, a.tool, a.name, family].filter(Boolean).join(" ");
34793480 const score = omniScore(text, q);
34803481 if (score < 0 || !q) continue;
34813482 out.push({
34823483 kind: "pane",
34833484 label: a.title || agentLabel(a),
3484- meta: `${a.session} · ${a.window} · claude ${a.state}`,
3485+ meta: `${a.session} · ${a.window} · claude ${a.state}${family ? ` · ${family}` : ""}`,
34853486 state: a.state,
34863487 score: score + 50,
34873488 run: () => tmuxCommand({ cmd: "focus", pane: a.pane }),
⋯ 1453 unchanged lines
49414942 return MODE_SHORT[mode] ?? mode;
49424943 }
49434944
4945+// A model id is `claude-opus-4-1-20250805` or similar — a version and a date
4946+// the omnibar has no room for and the user did not ask about. The family name
4947+// is the one part of it that answers "which model", so that is all this pulls
4948+// out.
4949+/** @param {string | null | undefined} model */
4950+function modelFamily(model) {
4951+ if (!model) return null;
4952+ const m = model.toLowerCase();
4953+ if (m.includes("opus")) return "opus";
4954+ if (m.includes("sonnet")) return "sonnet";
4955+ if (m.includes("haiku")) return "haiku";
4956+ return null;
4957+}
4958+
49444959 /** @param {TbOkFrame} msg */
49454960 function renderSessions(msg) {
49464961 if (!msg.tmux) return;
⋯ 704 unchanged lines
modifiedextension/types/globals.d.ts+6 −0
⋯ 327 unchanged lines
328328 tool?: string | null;
329329 message?: string | null;
330330 title?: string | null;
331+ /**
332+ * The model id, e.g. `claude-opus-4-1-20250805`. Only ever reported at
333+ * `SessionStart`, so it can be stale after a mid-session model switch, and
334+ * absent entirely for a session that started before this field existed.
335+ */
336+ model?: string | null;
331337 }
332338
333339 /**
⋯ 170 unchanged lines