agents: magpie omp auto sets omp's thinking to auto, and the panel's effort slider shows a level it doesn't list as it is (#339)

* agents: `magpie omp auto` sets omp's thinking to auto, and the panel's effort slider shows a level it doesn't list as it is

omp keeps defaultThinkingLevel at minimal … max or auto (omp picks a
level each turn), but magpie offered only the six levels. So
`magpie omp auto` found no field offering auto and took it for a model:
modelRoles.default became the literal "auto". auto is now offered
first, as omp's own picker has it, and kept out of ompEfforts, which a
model's thinking levels in models.yml are filtered by.

The tray panel's effort slider drew a value that isn't one of its
options (an agent with no effort set, a level magpie doesn't list) at
the first stop, named as the lowest level, and a touch there wrote that
level over it. It now gets a stop of its own, as the window's picker
gives the current value, so it reads as it is and a touch leaves it.

* agents: the note on omp's thinking picker holds for omp 16.3.5 too

omp 16.3.5 takes auto for defaultThinkingLevel as 18.4.4 does (config/settings-schema.ts there), as checked on #339.
This commit is contained in:
cyxer000 authored and GitHub committed 2026-10-01 00:20:19 +08:00
1 parent 49f02c6f03
commit abbffd73a4
6 files changed
+164 -5

No files matched your search

+5 -2
View File
@@ -168,7 +168,10 @@ func omp(home string) *Agent {
},
}, {
// the thinking level sessions start with, as omp's settings save
// it; unset omp takes high (it also knows auto, its own pick)
// it; unset omp takes high. auto has omp pick a level each turn:
// not a level a model lists, so it is offered here and kept out of
// ompEfforts, which a model's thinking levels are filtered by. First,
// as omp's own picker has it (16.3.5 and 18.4.4 alike)
Key: "effort", Label: "thinking",
Get: func() string { v, _ := edit.GetYAML(path, "defaultThinkingLevel"); return v },
Set: func(v string) error {
@@ -177,7 +180,7 @@ func omp(home string) *Agent {
}
return edit.SetYAML(path, edit.KV{Path: "defaultThinkingLevel", Value: v})
},
Options: func(map[string]string) []Option { return static(ompEfforts...) },
Options: func(map[string]string) []Option { return static(append([]string{"auto"}, ompEfforts...)...) },
}},
}
}
+5 -2
View File
@@ -1536,7 +1536,10 @@ function effortBars(f) {
// shows at once, bars and all; the write follows, in order, so a slow agent
// config never holds the thumb (巨卡).
function effortSeg(a, f) {
const options = f.options;
// a value that isn't one of the levels (unset, or one magpie doesn't list)
// is a stop of its own, as the picker's current value is: shown as the
// lowest level, a touch wrote that level over it
const options = f.options.some((o) => o.value === f.value) ? f.options : [{ value: f.value }, ...f.options];
const last = Math.max(1, options.length - 1);
const box = el("div", "effort-control");
const head = el("div", "effort-head");
@@ -1562,7 +1565,7 @@ function effortSeg(a, f) {
ends.setAttribute("aria-hidden", "true");
ends.append(el("span", "", effortName(options[0])), el("span", "", effortName(options[options.length - 1])));
box.append(head, track, ends);
let at = Math.max(0, options.findIndex((o) => o.value === f.value));
let at = options.findIndex((o) => o.value === f.value);
const show = (i) => {
at = i;
track.style.setProperty("--p", i / last);
+1
View File
@@ -198,6 +198,7 @@ const I18N = {
"high": "高",
"xhigh": "很高",
"max": "最高",
"auto": "自动",
"{label}: {value}": "{label}:{value}",
"Profiles": "方案",
"+ Save current": "+ 保存当前",
+6 -1
View File
@@ -455,11 +455,16 @@ and says what came of it, Anthropic's `not_limited` in words. The Routing
page's story names a Claude reset used by itself as Claude's. No click moves
the page, nothing has a left-border accent; English and Chinese, Chromium and
WebKit.
`panel-effort.test.cjs` opens a row in the tray panel whose effort is not one
of the levels offered (omp at auto, an agent with none set): the slider shows
it as it is ("auto", "default"; 自动 in Chinese) at a stop of its own, a touch
there posts nothing — it had shown the lowest level and a touch wrote it —
and the next stop is the lowest level. Chromium and WebKit.
With Node.js and Playwright available:
```sh
node --test internal/gui/tests/menu-scroll.test.cjs internal/gui/tests/click-scroll.test.cjs internal/gui/tests/panel-fold.test.cjs internal/gui/tests/panel-routing.test.cjs internal/gui/tests/gateway-fold.test.cjs internal/gui/tests/routing-kind.test.cjs internal/gui/tests/balance-fix.test.cjs internal/gui/tests/cli-update.test.cjs internal/gui/tests/login-import.test.cjs internal/gui/tests/agy-launch.test.cjs internal/gui/tests/currency.test.cjs internal/gui/tests/usage-ledger.test.cjs internal/gui/tests/update-check.test.cjs internal/gui/tests/omarchy.test.cjs internal/gui/tests/brand.test.cjs node --test internal/gui/tests/signin-callback.test.cjs internal/gui/tests/routing-steady.test.cjs internal/gui/tests/old-webkit.test.cjs internal/gui/tests/session-terminal.test.cjs internal/gui/tests/text-size.test.cjs internal/gui/tests/zcode-site.test.cjs internal/gui/tests/rail-tip.test.cjs internal/gui/tests/sync-update.test.cjs internal/gui/tests/plugin-signin.test.cjs internal/gui/tests/plugin-market.test.cjs internal/gui/tests/s3-sync.test.cjs internal/gui/tests/routing-wb-refused.test.cjs internal/gui/tests/market-have.test.cjs internal/gui/tests/sessions-today.test.cjs internal/gui/tests/routing-side-calls.test.cjs internal/gui/tests/import-all-skills.test.cjs internal/gui/tests/routing-flood.test.cjs internal/gui/tests/routing-busy.test.cjs internal/gui/tests/own-skill.test.cjs internal/gui/tests/routing-manual.test.cjs internal/gui/tests/tray-usages.test.cjs internal/gui/tests/provider-remove.test.cjs internal/gui/tests/signin-link.test.cjs internal/gui/tests/tray-inuse.test.cjs internal/gui/tests/codex-auto-reset.test.cjs internal/gui/tests/routing-auto-reset.test.cjs internal/gui/tests/claude-resets.test.cjs internal/gui/tests/library-toggle-status.test.cjs internal/gui/tests/library-all.test.cjs internal/gui/tests/routing-reroute-title.test.cjs internal/gui/tests/plain-names.test.cjs
node --test internal/gui/tests/menu-scroll.test.cjs internal/gui/tests/click-scroll.test.cjs internal/gui/tests/panel-fold.test.cjs internal/gui/tests/panel-routing.test.cjs internal/gui/tests/gateway-fold.test.cjs internal/gui/tests/routing-kind.test.cjs internal/gui/tests/balance-fix.test.cjs internal/gui/tests/cli-update.test.cjs internal/gui/tests/login-import.test.cjs internal/gui/tests/agy-launch.test.cjs internal/gui/tests/currency.test.cjs internal/gui/tests/usage-ledger.test.cjs internal/gui/tests/update-check.test.cjs internal/gui/tests/omarchy.test.cjs internal/gui/tests/brand.test.cjs node --test internal/gui/tests/signin-callback.test.cjs internal/gui/tests/routing-steady.test.cjs internal/gui/tests/old-webkit.test.cjs internal/gui/tests/session-terminal.test.cjs internal/gui/tests/text-size.test.cjs internal/gui/tests/zcode-site.test.cjs internal/gui/tests/rail-tip.test.cjs internal/gui/tests/sync-update.test.cjs internal/gui/tests/plugin-signin.test.cjs internal/gui/tests/plugin-market.test.cjs internal/gui/tests/s3-sync.test.cjs internal/gui/tests/routing-wb-refused.test.cjs internal/gui/tests/market-have.test.cjs internal/gui/tests/sessions-today.test.cjs internal/gui/tests/routing-side-calls.test.cjs internal/gui/tests/import-all-skills.test.cjs internal/gui/tests/routing-flood.test.cjs internal/gui/tests/routing-busy.test.cjs internal/gui/tests/own-skill.test.cjs internal/gui/tests/routing-manual.test.cjs internal/gui/tests/tray-usages.test.cjs internal/gui/tests/provider-remove.test.cjs internal/gui/tests/signin-link.test.cjs internal/gui/tests/tray-inuse.test.cjs internal/gui/tests/codex-auto-reset.test.cjs internal/gui/tests/routing-auto-reset.test.cjs internal/gui/tests/claude-resets.test.cjs internal/gui/tests/library-toggle-status.test.cjs internal/gui/tests/library-all.test.cjs internal/gui/tests/routing-reroute-title.test.cjs internal/gui/tests/plain-names.test.cjs internal/gui/tests/panel-effort.test.cjs
```
If Playwright is installed outside the repository, set `NODE_PATH` to the
+101
View File
@@ -0,0 +1,101 @@
// Run with Node's test runner and Playwright on the module path; see README.md.
// The tray panel's effort slider shows an agent's effort as it is when that
// isn't one of the levels offered — omp at auto before magpie listed it, an
// agent with none set — rather than as the lowest level, which a touch on
// the slider then wrote over it. A touch where it stands posts nothing; the
// next stop is the lowest level. In English and Chinese. No backend: the API
// is faked here.
const assert = require("node:assert/strict");
const fs = require("node:fs/promises");
const path = require("node:path");
const { test } = require("node:test");
const { chromium, webkit } = require("playwright");
const assets = path.resolve(__dirname, "../assets");
const levels = ["minimal", "low", "medium", "high", "xhigh", "max"].map((value) => ({ value }));
const agent = (id, name, effort) => ({
id, name, path: "/test/" + id,
fields: [{ key: "model", label: "model", value: "anthropic/claude-opus-5-5:max", options: [] },
{ key: "effort", label: "thinking", value: effort, options: levels }],
});
const state = { agents: [agent("omp", "omp", "auto"), agent("pi", "Pi", "")], profiles: [] };
function server(lang, sets) {
return async (route) => {
const req = route.request(), url = new URL(req.url());
if (url.pathname === "/boot.js") return route.fulfill({ contentType: "text/javascript", body: `window.bootPrefs = {lang:"${lang}",theme:"light",web:true};` });
if (url.pathname === "/wails/runtime.js") return route.fulfill({ contentType: "text/javascript", body: "export const Window = {};" });
if (url.pathname === "/api/state") return route.fulfill({ json: { ...state, settings: { lang, theme: "light" } } });
if (url.pathname === "/api/set") { sets.push(req.postDataJSON()); return route.fulfill({ json: { ...state, settings: { lang, theme: "light" } } }); }
if (url.pathname === "/api/usage/quotas") return route.fulfill({ json: [] });
if (url.pathname === "/api/groups") return route.fulfill({ json: { groups: [] } });
if (url.pathname === "/api/providers") return route.fulfill({ json: { providers: [], gateway: { running: true } } });
if (url.pathname.startsWith("/api/")) return route.fulfill({ json: {} });
const file = path.join(assets, url.pathname === "/" ? "index.html" : url.pathname);
const contentType = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".png": "image/png" }[path.extname(file)];
await route.fulfill({ body: await fs.readFile(file), contentType });
};
}
const row = (id) => `.row.agent[data-id="${id}"]`;
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
test(engine + ": the panel's effort as it is", async (t) => {
assert(["chromium", "webkit"].includes(engine), "BROWSER must be chromium or webkit");
const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" }));
const errors = [];
t.after(async () => {
if (errors.length) console.log(errors);
await browser.close();
});
// the row opened, its slider, and a click on the slider's first stop
const open = async (lang, sets, id) => {
const page = await (await browser.newContext({ viewport: { width: 440, height: 560 } })).newPage();
page.setDefaultTimeout(5000);
page.on("pageerror", (e) => errors.push(e.message));
await page.route("**/*", server(lang, sets));
await page.goto("http://magpie.test/?mode=panel");
await page.locator(`${row(id)} .ag-sum`).click();
await page.waitForTimeout(700);
const box = page.locator(`${row(id)} .ag-open .effort-control`);
const slider = box.locator(".eslide");
const first = async () => {
const r = await box.locator(".rail").evaluate((e) => e.getBoundingClientRect().toJSON());
await page.mouse.click(r.left + 1, r.top + r.height / 2);
await page.waitForTimeout(300);
};
return { page, box, slider, first };
};
await t.test("omp at auto", async () => {
const sets = [];
const { box, slider, first } = await open("en", sets, "omp");
assert.equal(await box.locator(".effort-head b").textContent(), "auto");
assert.equal(await slider.getAttribute("aria-valuetext"), "auto");
assert.equal(await box.locator(".effort-ends span").first().textContent(), "auto");
await first();
assert.deepEqual(sets, [], "a touch where it stands wrote a level over auto");
await slider.press("ArrowRight");
await slider.page().waitForTimeout(300);
assert.deepEqual(sets, [{ agent: "omp", field: "effort", value: "minimal" }]);
});
await t.test("none set", async () => {
const sets = [];
const { box, first } = await open("en", sets, "pi");
assert.equal(await box.locator(".effort-head b").textContent(), "default");
await first();
assert.deepEqual(sets, []);
});
await t.test("in Chinese", async () => {
const sets = [];
const { box, first } = await open("zh", sets, "omp");
assert.equal(await box.locator(".effort-head b").textContent(), "自动");
await first();
assert.deepEqual(sets, []);
});
assert.deepEqual(errors, []);
});
}
+46
View File
@@ -0,0 +1,46 @@
package main
import (
"os"
"path/filepath"
"testing"
"github.com/yetone/magpie/internal/agent"
"gopkg.in/yaml.v3"
)
// `magpie omp auto` asks for omp's own thinking level, auto (omp picks one
// each turn), and so goes to the thinking field: taken as a model, it made
// modelRoles.default the literal "auto", which omp has no model by.
func TestOmpAutoIsAThinkingLevel(t *testing.T) {
groupsHome(t)
for _, k := range []string{"PI_CODING_AGENT_DIR", "PI_CONFIG_DIR", "OMP_PROFILE", "PI_PROFILE"} {
t.Setenv(k, "")
}
dir := filepath.Join(os.Getenv("HOME"), ".omp", "agent")
os.MkdirAll(dir, 0o755)
config := filepath.Join(dir, "config.yml")
os.WriteFile(config, []byte("modelRoles:\n default: anthropic/claude-opus-5-5:max\ndefaultThinkingLevel: high\n"), 0o644)
a, err := agent.Find("omp")
if err != nil {
t.Fatal(err)
}
f := fieldForValue(a, "auto")
if f == nil || f.Key != "effort" {
t.Fatalf("auto went to %v, not the thinking level", f)
}
if err := set(a, f.Key, "auto"); err != nil {
t.Fatal(err)
}
b, _ := os.ReadFile(config)
var c struct {
ModelRoles map[string]string `yaml:"modelRoles"`
DefaultThinkingLevel string `yaml:"defaultThinkingLevel"`
}
if err := yaml.Unmarshal(b, &c); err != nil {
t.Fatal(err)
}
if c.DefaultThinkingLevel != "auto" || c.ModelRoles["default"] != "anthropic/claude-opus-5-5:max" {
t.Fatalf("config:\n%s", b)
}
}