From f677de1a9dae15fe547bd4f3d70373f2f2629823 Mon Sep 17 00:00:00 2001 From: prode Date: Mon, 8 Jun 2026 13:52:13 -0300 Subject: [PATCH] feat(web): show agent/skill model and effort in the dashboard Surfaces the model/effort frontmatter (added by #24) in the read-only web dashboard. The session overview now reads `model`/`effort` for agents and skills into the Artifact metadata, and ResourceView renders them in the header alongside tools. Follow-up to agent-skill-model-effort. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/session/session.go | 6 ++++ internal/session/session_test.go | 31 +++++++++++++++++++ .../frontend/src/components/ResourceView.tsx | 10 ++++++ internal/web/frontend/src/types.ts | 2 ++ 4 files changed, 49 insertions(+) diff --git a/internal/session/session.go b/internal/session/session.go index 007bfb5..adfb3fb 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -60,6 +60,8 @@ type Artifact struct { Path string `json:"path"` Description string `json:"description,omitempty"` // agents, skills, steering(auto) Tools string `json:"tools,omitempty"` // agents + Model string `json:"model,omitempty"` // agents, skills (optional override) + Effort string `json:"effort,omitempty"` // agents, skills (optional override) Inclusion string `json:"inclusion,omitempty"` // steering: always|fileMatch|manual|auto } @@ -289,6 +291,8 @@ func listAgents(dir, root string) []Artifact { fm := readFrontmatter(filepath.Join(dir, out[i].Name)) out[i].Description = fm.AsString("description", "") out[i].Tools = fm.AsString("tools", "") + out[i].Model = fm.AsString("model", "") + out[i].Effort = fm.AsString("effort", "") } return out } @@ -311,6 +315,8 @@ func listSkills(dir, root string) []Artifact { for i := range out { fm := readFrontmatter(filepath.Join(dir, out[i].Name, "SKILL.md")) out[i].Description = fm.AsString("description", "") + out[i].Model = fm.AsString("model", "") + out[i].Effort = fm.AsString("effort", "") } return out } diff --git a/internal/session/session_test.go b/internal/session/session_test.go index 58ccf9d..8b29f40 100644 --- a/internal/session/session_test.go +++ b/internal/session/session_test.go @@ -112,6 +112,37 @@ func TestOverviewArtifactMetadata(t *testing.T) { } } +func TestOverviewArtifactModelEffort(t *testing.T) { + root := writeWorkspace(t, map[string]string{ + ".claude/agents/heavy.md": "---\nname: heavy\ndescription: d\ntools: Read\nmodel: opus\neffort: high\n---\n# Agent\n", + ".claude/agents/plain.md": "---\nname: plain\ndescription: d\ntools: Read\n---\n# Agent\n", + ".claude/skills/deep/SKILL.md": "---\nname: deep\ndescription: d\nmodel: sonnet\neffort: max\n---\n# Skill\n", + ".claude/skills/lite/SKILL.md": "---\nname: lite\ndescription: d\n---\n# Skill\n", + }) + ov := LoadOverview(root) + + // Agents sorted: heavy then plain. + if len(ov.Agents) != 2 { + t.Fatalf("agents = %+v", ov.Agents) + } + if ov.Agents[0].Model != "opus" || ov.Agents[0].Effort != "high" { + t.Errorf("heavy agent model/effort = %q/%q, want opus/high", ov.Agents[0].Model, ov.Agents[0].Effort) + } + if ov.Agents[1].Model != "" || ov.Agents[1].Effort != "" { + t.Errorf("plain agent should omit model/effort, got %q/%q", ov.Agents[1].Model, ov.Agents[1].Effort) + } + // Skills sorted: deep then lite. + if len(ov.Skills) != 2 { + t.Fatalf("skills = %+v", ov.Skills) + } + if ov.Skills[0].Model != "sonnet" || ov.Skills[0].Effort != "max" { + t.Errorf("deep skill model/effort = %q/%q, want sonnet/max", ov.Skills[0].Model, ov.Skills[0].Effort) + } + if ov.Skills[1].Model != "" || ov.Skills[1].Effort != "" { + t.Errorf("lite skill should omit model/effort, got %q/%q", ov.Skills[1].Model, ov.Skills[1].Effort) + } +} + func TestOverviewEmptyWorkspace(t *testing.T) { root := t.TempDir() ov := LoadOverview(root) // no specs/, no .claude/ diff --git a/internal/web/frontend/src/components/ResourceView.tsx b/internal/web/frontend/src/components/ResourceView.tsx index 78bbd1f..aec366a 100644 --- a/internal/web/frontend/src/components/ResourceView.tsx +++ b/internal/web/frontend/src/components/ResourceView.tsx @@ -57,6 +57,16 @@ export function ResourceView({ tools: {artifact.tools} )} + {artifact.model && ( + + model: {artifact.model} + + )} + {artifact.effort && ( + + effort: {artifact.effort} + + )} {artifact.path} diff --git a/internal/web/frontend/src/types.ts b/internal/web/frontend/src/types.ts index 0ea092f..e366829 100644 --- a/internal/web/frontend/src/types.ts +++ b/internal/web/frontend/src/types.ts @@ -19,6 +19,8 @@ export interface Artifact { path: string description?: string // agents, skills, steering(auto) tools?: string // agents + model?: string // agents, skills (optional override) + effort?: string // agents, skills (optional override) inclusion?: string // steering: always | fileMatch | manual | auto }