control board: show the model each seat is running (#1503)
Jason asked for the running model (sonnet, opus, gpt-6-astra) on the board. readSession keeps model and provider from the log's latest model_change entry or assistant turn, whichever is later, so a /model switch shows on the next scan; scanAgent exposes both; the page shows the model under the agent name with the provider in the hover and a Model detail row. Blank when the log names none. Live check on a scratch board against the real data root: all 42 rows carried a model. Board 91/91. Sonnet review caught a double-escaped hover title; fixed. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
@@ -63,6 +63,7 @@
|
||||
.seen-tag{font-size:.72rem;color:var(--muted);margin-left:6px;vertical-align:middle}
|
||||
.msg-error{color:var(--danger)}
|
||||
.msg-text,.msg-error{display:block;max-width:36ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.model{display:block;color:var(--muted);font-size:.75em;white-space:nowrap}
|
||||
.task-text{display:block;max-width:28ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.unknown{color:var(--muted);font-style:italic}
|
||||
.source{color:var(--muted);font-size:.75em;margin-left:.35em;white-space:nowrap}
|
||||
@@ -189,6 +190,11 @@
|
||||
function sourceLabel(source) { return SOURCE_LABEL[source] || source; }
|
||||
function sourceTag(source) { return source ? '<span class="source" title="source: ' + esc(source) + '">' + esc(sourceLabel(source)) + "</span>" : ""; }
|
||||
function fromSource(source) { return source ? " (from " + esc(sourceLabel(source)) + ")" : ""; }
|
||||
// The model a seat is running, e.g. "gpt-6-astra" with the provider in the
|
||||
// hover title. modelText() already escapes, so the title takes it as is.
|
||||
// Empty when the log has not named one yet.
|
||||
function modelTag(rec) { return rec.model ? '<span class="model" title="' + modelText(rec) + '">' + esc(rec.model) + "</span>" : ""; }
|
||||
function modelText(rec) { return rec.model ? (rec.provider ? esc(rec.provider) + "/" : "") + esc(rec.model) : "unknown"; }
|
||||
function registeredText(reg) {
|
||||
if (!reg) return "no (not started through mosaic launch)";
|
||||
var parts = ["started " + esc(reg.startedAt || "—")];
|
||||
@@ -222,7 +228,7 @@
|
||||
var main =
|
||||
'<tr class="' + cls + '">' +
|
||||
projectCell +
|
||||
'<td><button type="button" class="row-toggle" data-idx="' + idx + '" data-key="' + esc(key) + '" aria-expanded="' + (open ? "true" : "false") + '" aria-controls="detail-' + idx + '">' + esc(rec.agent) + "</button></td>" +
|
||||
'<td><button type="button" class="row-toggle" data-idx="' + idx + '" data-key="' + esc(key) + '" aria-expanded="' + (open ? "true" : "false") + '" aria-controls="detail-' + idx + '">' + esc(rec.agent) + "</button>" + modelTag(rec) + "</td>" +
|
||||
"<td>" + badge(rec.state) + seenControl(rec) + "</td>" +
|
||||
"<td>" + esc(humanAge(rec.ageSeconds)) + "</td>" +
|
||||
"<td>" + task + "</td>" +
|
||||
@@ -241,6 +247,7 @@
|
||||
"<dt>Task</dt><dd>" + (rec.task ? esc(rec.task) : "unknown") + fromSource(rec.taskSource) + "</dd>" +
|
||||
"<dt>Active project</dt><dd>" + (rec.activeProject ? esc(rec.activeProject) : "unknown") + fromSource(rec.activeProjectSource) + "</dd>" +
|
||||
"<dt>Workspace</dt><dd>" + (rec.workspace ? esc(rec.workspace) : "unknown") + fromSource(rec.workspaceSource) + "</dd>" +
|
||||
"<dt>Model</dt><dd>" + modelText(rec) + "</dd>" +
|
||||
"<dt>Registered</dt><dd>" + registeredText(rec.registered) + "</dd>" +
|
||||
"<dt>Session cwd</dt><dd>" + esc(rec.cwd || "—") + "</dd>" +
|
||||
"<dt>Tmux session</dt><dd>" + tmux + "</dd>" +
|
||||
|
||||
@@ -71,6 +71,7 @@ export function readSession(file) {
|
||||
const lines = readFileSync(file, "utf8").split("\n");
|
||||
let sessionId = null, cwd = null, lastTimestamp = null, lastMessage = null, lastAssistantText = null, lastError = null;
|
||||
let firstUserText = null;
|
||||
let model = null, provider = null;
|
||||
let skippedLines = 0;
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
@@ -85,8 +86,18 @@ export function readSession(file) {
|
||||
if (entry.type === "session") {
|
||||
sessionId = entry.id ?? sessionId;
|
||||
cwd = entry.cwd ?? cwd;
|
||||
} else if (entry.type === "model_change") {
|
||||
// pi logs a model switch (/model, or the launch default) as its own
|
||||
// entry: { provider, modelId }. The latest one is the model in use.
|
||||
if (typeof entry.modelId === "string" && entry.modelId) model = entry.modelId;
|
||||
if (typeof entry.provider === "string" && entry.provider) provider = entry.provider;
|
||||
} else if (entry.type === "message" && entry.message) {
|
||||
lastMessage = entry.message;
|
||||
if (entry.message.role === "assistant") {
|
||||
// Each assistant turn also names the model that produced it.
|
||||
if (typeof entry.message.model === "string" && entry.message.model) model = entry.message.model;
|
||||
if (typeof entry.message.provider === "string" && entry.message.provider) provider = entry.message.provider;
|
||||
}
|
||||
if (entry.message.role === "user" && firstUserText === null) {
|
||||
const text = userText(entry.message.content);
|
||||
if (text.trim()) firstUserText = collapse(text);
|
||||
@@ -98,7 +109,7 @@ export function readSession(file) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return { file, sessionId, cwd, lastTimestamp, lastMessage, lastAssistantText, lastError, firstUserText, skippedLines };
|
||||
return { file, sessionId, cwd, lastTimestamp, lastMessage, lastAssistantText, lastError, firstUserText, model, provider, skippedLines };
|
||||
}
|
||||
|
||||
// A user message's text: pi writes either a plain string or a list of blocks.
|
||||
@@ -341,6 +352,11 @@ export function scanAgent(spec, { isAlive = tmuxInspect, now = () => new Date(),
|
||||
sessionFile: file,
|
||||
sessionId: session?.sessionId ?? null,
|
||||
cwd,
|
||||
// The model the seat is running, from the log's latest model_change entry
|
||||
// or assistant turn, whichever came last. null when the log has neither
|
||||
// (a seat with no log, or one that has not answered yet).
|
||||
model: session?.model ?? null,
|
||||
provider: session?.provider ?? null,
|
||||
task,
|
||||
taskSource,
|
||||
workspace,
|
||||
|
||||
Reference in New Issue
Block a user