profiles: a profile's details list all four of Claude Code's tiers, one left to follow the main model saying so (#480: opus and fable, following it, were missing from the details, so they read as not set) — a tier is a quiet field, empty while it follows the main model, and the details left every empty quiet field out. A field now says which one it follows (Follows, "model" for the tiers), and the details keep such a field, empty, with it; the panel shows it as "follows the main model (<model>)" (跟随主模型(…)), or "follows the main model" when the main is the agent's default. Other quiet fields (Codex's sign-in, subagents) are still left out while empty, and what applying a profile writes is unchanged. TestDetails and profile-details.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.

This commit is contained in:
yetone committed 2026-10-02 05:50:23 +08:00
1 parent 01d595817c
commit 973bf4eb2c
8 files changed
+65 -19

No files matched your search

+3
View File
@@ -53,6 +53,9 @@ type Field struct {
// Quiet fields are left out of listings while empty: they follow
// another field until set (Claude Code's per-tier models).
Quiet bool
// Follows is the key of the field a Quiet one takes after while empty
// ("model" for Claude Code's tiers), for a profile's details to say so.
Follows string
}
// Agent is one supported coding agent.
+1 -1
View File
@@ -573,7 +573,7 @@ func claudeIn(at place) *Agent {
}}
for _, tier := range claudeTiers {
fields = append(fields, Field{
Key: tier, Label: tier, Quiet: true,
Key: tier, Label: tier, Quiet: true, Follows: "model",
// empty while the tier follows the main model
Get: func() string {
if w := env(tierEnv(tier)); routed() && w != env("ANTHROPIC_MODEL") {
+3
View File
@@ -589,7 +589,10 @@ function profileDetail(p, footed) {
};
for (const f of g.fields || []) {
const effort = f.key === "effort" || f.label === "effort" || f.label === "thinking";
// a Claude Code tier left to follow the main model (#480), and to what
const main = f.follows && g.fields.find((x) => x.key === f.follows && !x.hidden)?.value;
if (f.hidden) row(t(f.label), "••••••", "pd-hidden");
else if (f.follows) row(t(f.label), main ? t("follows the main model ({model})", { model: main }) : t("follows the main model"), "pd-default");
else if (!f.value) row(t(f.label), t("agent default"), "pd-default");
else row(t(f.label), effort ? effortName({ value: f.value }) : f.value);
}
+2
View File
@@ -295,6 +295,8 @@ const I18N = {
"Apply": "应用",
"Apply {name} to the agents": "将 {name} 应用到各 Agent",
"No agent settings saved": "没有保存任何 Agent 的设置",
"follows the main model": "跟随主模型",
"follows the main model ({model})": "跟随主模型({model})",
"•••••• looks like a key or a token, and is not shown": "•••••• 看起来是密钥或令牌,不予显示",
"Updated {name} to the current setup": "已用当前配置更新 {name}",
"Profile name": "方案名称",
+3 -2
View File
@@ -347,8 +347,9 @@ field; Escape closes it; in English and Chinese.
window and in the tray panel with its list scrolled to its end: the details
open, applying nothing and leaving the chip where it was on the screen; they
list each agent by name, its fields under their labels (an empty one as the
agent's default, one that reads as a key as dots) and what the Library gives
it; a second click closes them; their Apply applies the profile, once; ↻ and
agent's default, one that reads as a key as dots, all four of Claude Code's
tiers, one left empty as following the main model and which that is, #480)
and what the Library gives it; a second click closes them; their Apply applies the profile, once; ↻ and
× stay on the chip. In Chromium and WebKit, English and Chinese:
```sh
+14 -5
View File
@@ -4,7 +4,9 @@
// could be seen only by applying it over the setup in use). A click on the
// chip opens its details: the agents' names, each one's fields under their
// labels (an empty one the agent's default, one that reads as a key shown as
// dots), what the Library gives it; nothing is applied, and the chip clicked
// dots, each of Claude Code's four tiers, one left to follow the main model
// saying so and what that is: #480, they were missing), what the Library
// gives it; nothing is applied, and the chip clicked
// stays where it is on the screen. Apply in the details applies it, once; a
// second click on the chip closes them; ↻ and × are still on the chip. In the
// window and in the tray panel (its list scrolled to its end), in English
@@ -23,7 +25,14 @@ const work = {
name: "work", summary: "codex gpt-5",
library: { servers: 1, skills: 1, instructions: true },
agents: [
{ id: "claude", name: "Claude Code", fields: [{ key: "model", label: "model", value: "" }], servers: ["github"], skills: ["review"] },
{ id: "claude", name: "Claude Code", fields: [
{ key: "model", label: "model", value: "magpie/a/main" },
{ key: "opus", label: "opus", value: "", follows: "model" },
{ key: "sonnet", label: "sonnet", value: "magpie/a/s" },
{ key: "haiku", label: "haiku", value: "", follows: "model" },
{ key: "fable", label: "fable", value: "", follows: "model" },
{ key: "subagent", label: "subagents", value: "" },
], servers: ["github"], skills: ["review"] },
{ id: "codex", name: "Codex", fields: [
{ key: "model", label: "model", value: "magpie/openai/gpt-5" },
{ key: "effort", label: "effort", value: "high" },
@@ -57,8 +66,8 @@ function server(lang, calls, count) {
}
const words = {
en: { model: "model", effort: "effort", high: "high", def: "agent default", servers: "MCP servers", skills: "Skills", instructions: "Instructions", on: "on", apply: "Apply" },
zh: { model: "模型", effort: "推理强度", high: "高", def: "Agent 默认值", servers: "MCP 服务器", skills: "技能", instructions: "指令", on: "开启", apply: "应用" },
en: { model: "model", effort: "effort", high: "high", def: "agent default", follows: "follows the main model (magpie/a/main)", subagents: "subagents", servers: "MCP servers", skills: "Skills", instructions: "Instructions", on: "on", apply: "Apply" },
zh: { model: "模型", effort: "推理强度", high: "高", def: "Agent 默认值", follows: "跟随主模型(magpie/a/main)", subagents: "子 agent", servers: "MCP 服务器", skills: "技能", instructions: "指令", on: "开启", apply: "应用" },
};
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
@@ -125,7 +134,7 @@ for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium",
assert.equal(read.apply, w.apply);
assert(read.inView, "the details must be drawn");
assert.deepEqual(read.groups, [
{ name: "Claude Code", rows: [`${w.model}=${w.def}`, `${w.servers}=github`, `${w.skills}=review`] },
{ name: "Claude Code", rows: [`${w.model}=magpie/a/main`, `opus=${w.follows}`, "sonnet=magpie/a/s", `haiku=${w.follows}`, `fable=${w.follows}`, `${w.subagents}=${w.def}`, `${w.servers}=github`, `${w.skills}=review`] },
{ name: "Codex", rows: [`${w.model}=magpie/openai/gpt-5`, `${w.effort}=${w.high}`, "token=••••••", `${w.instructions}=${w.on}`] },
]);
+17 -8
View File
@@ -24,13 +24,16 @@ type Group struct {
Instructions bool `json:"instructions,omitempty"`
}
// Item is one saved field. Value "" is the agent's own default. Hidden is a
// value that looked like a key or a token: it is left out, never shown.
// Item is one saved field. Value "" is the agent's own default, or, with
// Follows, the key of the field it takes after (a Claude Code tier that
// follows the main model, #480). Hidden is a value that looked like a key or
// a token: it is left out, never shown.
type Item struct {
Key string `json:"key"`
Label string `json:"label"`
Value string `json:"value"`
Hidden bool `json:"hidden,omitempty"`
Key string `json:"key"`
Label string `json:"label"`
Value string `json:"value"`
Follows string `json:"follows,omitempty"`
Hidden bool `json:"hidden,omitempty"`
}
// secretName is a field named for a credential; secretValue a value that
@@ -47,8 +50,11 @@ func secret(key, label, value string) bool {
}
// Details is what p holds, by agent: the agents magpie knows in the order it
// lists them, then any it no longer does by id. A quiet field left empty (it
// follows another one) is left out, as the agent's row leaves it out.
// lists them, then any it no longer does by id. A quiet field left empty is
// left out, as the agent's row leaves it out, unless it says which field it
// follows: Claude Code's four tiers are all there, one that follows the main
// model saying so (#480: opus and fable, following it, were missing, and
// read as not set). What applying the profile writes is unchanged.
func Details(p Profile) []Group {
known := map[string]*agent.Agent{}
var order []string
@@ -117,6 +123,9 @@ func Details(p Profile) []Group {
}
seen[f.Key] = true
if f.Quiet && v == "" {
if f.Follows != "" {
g.Fields = append(g.Fields, Item{Key: f.Key, Label: f.Label, Follows: f.Follows})
}
continue
}
add(f.Key, f.Label, v)
+22 -3
View File
@@ -11,7 +11,9 @@ import (
// A profile's details are its fields by agent, in the order magpie lists the
// agents and their fields, under the agents' names and the fields' labels,
// with what the library gives each; an empty quiet field is left out, an
// with what the library gives each; an empty quiet field is left out, but
// Claude Code's four tiers are all there, an empty one following the main
// model (#480: opus and fable, following it, were missing); an
// agent magpie no longer knows comes last by id, and nothing that reads as a
// key or a token is in them (#467).
func TestDetails(t *testing.T) {
@@ -23,6 +25,9 @@ func TestDetails(t *testing.T) {
"codex.login": "", // quiet: follows the model
"claude.model": "opus",
"claude.haiku": "",
"claude.opus": "",
"claude.sonnet": "magpie/a/s",
"claude.fable": "",
"gone.model": "m",
"gone.apiKey": "plain-looking",
"codex.extra": "sk-live-abcdef",
@@ -67,8 +72,22 @@ func TestDetails(t *testing.T) {
if _, ok := item(codex, "login"); ok {
t.Error("an empty quiet field is left out")
}
if _, ok := item(claude, "haiku"); ok {
t.Error("an empty tier is left out")
var tiers []string
for _, it := range claude.Fields {
switch it.Key {
case "opus", "sonnet", "haiku", "fable":
tiers = append(tiers, it.Key)
want := Item{Key: it.Key, Label: it.Key, Follows: "model"}
if it.Key == "sonnet" {
want = Item{Key: "sonnet", Label: "sonnet", Value: "magpie/a/s"}
}
if it != want {
t.Errorf("tier %+v, want %+v", it, want)
}
}
}
if !slices.Equal(tiers, []string{"opus", "sonnet", "haiku", "fable"}) {
t.Errorf("tiers %v, want all four in Claude Code's order, an empty one following the model", tiers)
}
for _, c := range []struct {
g Group