agent: dsh's home patch layer, ~/.dsh/cordis.patch.yml, gets magpie's route and start model when it overrides the profiles' (#804, liuweifeng) — dsh applies $DSH_HOME/cordis.patch.yml over every profile's own, so a llm-pi-ai or agent-default-model entry there replaced what magpie wrote in profiles/*/cordis.patch.yml, and dsh ran the home layer's old snapshot while magpie read as connected. When the home layer has a llm-pi-ai entry, magpie's route is written in it beside the user's routes (a sync, a model picked, the 30 s round, and taken out on Disconnect); when it has an agent-default-model, the model and effort magpie set go there (the user's one stashed and put back when magpie's goes). A home layer without those entries is left as it is. Reading goes by the home layer too (the model and effort shown, Joined, the checks), and Check says when the home layer's llm-pi-ai lacks magpie's route. TestDshHomePatchLayer and TestDshHomePatchLayerUnwire fail on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn and TestUserPath flaked, pass alone).

This commit is contained in:
yetone committed 2026-10-05 00:39:40 +08:00
1 parent 3ffc99ef60
commit 9761248de3
2 files changed
+237 -11

No files matched your search

+121 -11
View File
@@ -123,7 +123,7 @@ func dshAt(at place) *Agent {
return false
}
_, items, err := dshRead(files[0])
return err == nil && dshWired(items)
return err == nil && dshWired(dshOver(dir, items))
},
// connected so, Disconnect takes the route out, the start magpie
// wrote and its key; the model picked in dsh stays its own
@@ -138,6 +138,9 @@ func dshAt(at place) *Agent {
return err
}
}
if err := dshMirrorHome(dir, nil, gw()); err != nil {
return err
}
return dshEnv(dir, false)
},
Fields: []Field{{
@@ -182,6 +185,90 @@ func dshProfiles(dir string) []string {
return append(web, rest...)
}
// dshHomePatch is dsh's home patch layer, $DSH_HOME/cordis.patch.yml: it is
// applied over every profile's own, so an entry there replaces the profile's
// of the same id, whatever magpie wrote in that one (#804).
func dshHomePatch(dir string) string { return filepath.Join(dir, "cordis.patch.yml") }
// dshOver is a profile's patch list as dsh goes by it: the home layer's
// entries after the profile's own, so dshFindLast finds the one dsh uses.
// It is for reading only.
func dshOver(dir string, items []dshItem) []dshItem {
_, home, err := dshRead(dshHomePatch(dir))
if err != nil || len(home) == 0 {
return items
}
return append(append([]dshItem{}, items...), home...)
}
// dshHomeHas reports whether the home layer has an entry of this id, which
// then goes over every profile's.
func dshHomeHas(dir, id string) bool {
_, home, err := dshRead(dshHomePatch(dir))
return err == nil && dshFindLast(home, id) >= 0
}
// dshMirrorHome gives the home layer what magpie wrote in the profiles, for
// the entries it overrides them with: magpie's route in a llm-pi-ai entry
// there (the user's routes beside it stay), and in an agent-default-model
// there the model magpie set. Without it dsh ran the home layer's snapshot
// while magpie read as connected (#804). An entry the home layer doesn't
// have is not added: the profiles' own then apply, as written.
func dshMirrorHome(dir string, models []catalog.Model, gw string) error {
files := dshProfiles(dir)
if len(files) == 0 {
return nil
}
home := dshHomePatch(dir)
head, items, err := dshRead(home)
if err != nil {
return nil
}
_, web, err := dshRead(files[0])
if err != nil {
return nil
}
before := dshJoin(items)
if dshFindLast(items, dshPiRow) >= 0 {
on := dshWired(web)
if !on || len(models) > 0 {
if items, err = dshPutRoute(items, on, models, gw); err != nil {
return err
}
}
}
if i := dshFindLast(items, "agent-default-model"); i >= 0 {
j := dshFindLast(web, "agent-default-model")
switch {
case j >= 0 && dshOurs(web[j]):
if !dshOurs(items[i]) {
stash(map[string]string{dshStashKey(home, "agent-default-model"): strings.Join(items[i].lines, "\n")})
}
items[i] = dshItem{id: "agent-default-model", magpie: true, lines: append([]string{}, web[j].lines...)}
case dshOurs(items[i]):
// magpie's start gone from the profiles: the user's back, or none
if old := unstash(dshStashKey(home, "agent-default-model")); old != "" {
items[i] = dshItem{id: "agent-default-model", lines: strings.Split(old, "\n")}
} else {
items = append(items[:i], items[i+1:]...)
}
}
}
if dshJoin(items) == before {
return nil
}
return dshWrite(home, head, items)
}
func dshJoin(items []dshItem) string {
var b strings.Builder
for _, it := range items {
b.WriteString(strings.Join(it.lines, "\n"))
b.WriteString("\n")
}
return b.String()
}
// dshItem is one entry of the patch list, as its lines.
type dshItem struct {
id string
@@ -437,8 +524,9 @@ func dshGet(dir string) string {
// dshStart reads the model a profile's sessions start on: the one last picked
// in dsh, saved in its settings, which go over every profile, else the
// profile's own agent-default-model entry.
// agent-default-model entry of the home layer or the profile's own.
func dshStart(dir string, items []dshItem) string {
items = dshOver(dir, items)
sel := edit.GetYAMLMap(filepath.Join(dir, "settings.yaml"), "agent-default-model")
if sel["model"] == "" {
if i := dshFindLast(items, "agent-default-model"); i >= 0 {
@@ -501,15 +589,24 @@ func dshCheck(dir, gw string) string {
get := func(string) (string, bool) { return base, base != "" }
return wiringOff("DeepSeek Harness", path, get, "baseURL", gw+"/v1")
}
_, items, _ := dshRead(files[0])
routeFile := files[0]
if dshHomeHas(dir, dshPiRow) {
routeFile = dshHomePatch(dir)
// the home layer's llm-pi-ai goes over every profile's: without
// magpie's route it lists none of magpie's models anywhere
if dshRouteIn(dshOver(dir, items)) == nil && dshWired(items) {
return "DeepSeek Harness's home patch layer (" + routeFile + ") sets llm-pi-ai over every profile without magpie's route, so sessions list none of magpie's models; Apply again writes it there too"
}
}
// a profile of dsh's (its desktop app's) without magpie's route:
// sessions there list dsh's own models alone
for _, f := range files[1:] {
if _, items, err := dshRead(f); err == nil && !dshWired(items) {
if _, items, err := dshRead(f); err == nil && !dshWired(dshOver(dir, items)) {
return "DeepSeek Harness's " + filepath.Base(filepath.Dir(f)) + " profile (" + f + ") has none of magpie's models, so sessions there list DeepSeek's own alone"
}
}
_, items, _ := dshRead(files[0])
route := dshRouteIn(items)
route := dshRouteIn(dshOver(dir, items))
get := func(k string) (string, bool) {
if v := yamlKey(route, k); v != nil && v.Kind == yaml.ScalarNode {
return v.Value, true
@@ -527,7 +624,7 @@ func dshCheck(dir, gw string) string {
if route == nil {
api = "" // dsh's DeepSeek row taken over: Chat Completions
}
if off := wiringOff("DeepSeek Harness", files[0], get, "baseURL", dshBaseURL(dshAPI(api), gw)); off != "" {
if off := wiringOff("DeepSeek Harness", routeFile, get, "baseURL", dshBaseURL(dshAPI(api), gw)); off != "" {
return off
}
// dsh counts a model its provider doesn't list as none at all and refuses
@@ -541,10 +638,13 @@ func dshCheck(dir, gw string) string {
if err != nil {
continue
}
r := dshRouteIn(it)
r := dshRouteIn(dshOver(dir, it))
if r == nil {
continue // no route yet: the sync this asks for writes one
}
if dshHomeHas(dir, dshPiRow) {
f = dshHomePatch(dir)
}
if ref, ok := strings.CutPrefix(dshStart(dir, it), magpieID+"/"); ok && !dshRouteLists(r, ref) {
// a route out of date is written again — Apply again, or picking
// a model of magpie's in dsh, does it; one naming a model magpie
@@ -611,6 +711,9 @@ func dshSet(dir, v, gw string) error {
return err
}
}
if err := dshMirrorHome(dir, models, gw); err != nil {
return err
}
// what an older dsh was given is no use now
if _, err := os.Stat(legacy); err == nil {
if err := dshSetFile(legacy, "", false, models, gw); err != nil {
@@ -1066,7 +1169,10 @@ func dshSync(dir, gw string) error {
return err
}
}
return dshFillNewProfiles(files, models, gw)
if err := dshFillNewProfiles(files, models, gw); err != nil {
return err
}
return dshMirrorHome(dir, models, gw)
}
// dshRouteAgain writes magpie's route again in one patch list where it is no
@@ -1160,7 +1266,11 @@ func dshWiredOnce() string {
}
var trouble []string
models := magpieModels("dsh")
for _, f := range dshProfiles(dir) {
files := dshProfiles(dir)
if len(files) > 0 {
files = append(files, dshHomePatch(dir)) // left as it is without magpie's route
}
for _, f := range files {
written, err := dshRouteAgain(f, models, gateway.URL())
if err != nil {
trouble = append(trouble, fmt.Sprintf("writing dsh's route again in %s: %s", f, dshWriteError(err)))
@@ -1351,7 +1461,7 @@ func dshGetEffort(dir string) string {
if err != nil {
return ""
}
return dshEffortOf(items)
return dshEffortOf(dshOver(dir, items))
}
// dshSetEffort writes v as the effort sessions start with: into
@@ -1409,7 +1519,7 @@ func dshSetEffort(dir, v, gw string) error {
return err
}
}
return nil
return dshMirrorHome(dir, magpieModels("dsh"), gw)
}
if v != "" && !contains(dshEfforts, v) {
+116
View File
@@ -0,0 +1,116 @@
package agent
import (
"os"
"path/filepath"
"strings"
"testing"
)
// #804 (liuweifeng): dsh applies its home patch layer, ~/.dsh/cordis.patch.yml,
// over every profile's, so a llm-pi-ai or agent-default-model entry there
// replaced what magpie wrote in the profiles, and dsh ran the home layer's
// old snapshot while magpie read as connected. magpie now writes those two
// entries there too when the home layer has them, the user's routes beside
// magpie's kept, and adds nothing to a home layer without them.
func TestDshHomePatchLayer(t *testing.T) {
home, dir, web := dshRouteHome(t)
desktop := filepath.Join(dir, "profiles", "desktop", "cordis.patch.yml")
os.MkdirAll(filepath.Dir(desktop), 0o755)
os.WriteFile(web, []byte("[]\n"), 0o644)
os.WriteFile(desktop, []byte("[]\n"), 0o644)
hp := filepath.Join(dir, "cordis.patch.yml")
userStart := "- id: agent-default-model\n config:\n provider: deepseek-official\n model: deepseek-v4-pro\n"
os.WriteFile(hp, []byte("# shared by web and desktop\n"+
"- id: llm-pi-ai\n name: \"@deepseek-ai/dsh-llm-pi-ai\"\n config:\n providers:\n mine:\n displayName: Mine\n apiKeyEnv: MINE_API_KEY\n api: openai-completions\n baseURL: https://mine.example/v1\n models:\n - id: m1\n"+
" magpie:\n displayName: Magpie\n apiKeyEnv: "+dshKeyRef+"\n api: openai-completions\n baseURL: "+gatewayV1()+"\n models:\n - id: old/gone\n"+
userStart), 0o644)
read := func(p string) string { b, _ := os.ReadFile(p); return string(b) }
a := dsh(home)
f := a.Field("model")
// the home layer's start goes over the profiles'
if f.Get() != "deepseek-v4-pro" {
t.Fatalf("get %q", f.Get())
}
if err := f.Set("magpie/deepseek/pro"); err != nil {
t.Fatal(err)
}
s := read(hp)
for _, want := range []string{"# shared by web and desktop\n", " mine:\n", "- id: deepseek/pro", "- id: deepseek/flash", "provider: magpie", `model: "deepseek/pro"`} {
if !strings.Contains(s, want) {
t.Fatalf("missing %q in the home layer:\n%s", want, s)
}
}
if strings.Contains(s, "old/gone") || strings.Contains(s, "deepseek-official") {
t.Fatalf("the home layer kept its snapshot:\n%s", s)
}
if f.Get() != "magpie/deepseek/pro" || a.Check() != "" || !a.Joined() {
t.Fatalf("get %q, check %q, joined %v", f.Get(), a.Check(), a.Joined())
}
// a home llm-pi-ai written without magpie's route (dsh's Models page) is
// said, and a sync puts the route back beside the user's
os.WriteFile(hp, []byte("- id: llm-pi-ai\n name: \"@deepseek-ai/dsh-llm-pi-ai\"\n config:\n providers:\n mine:\n displayName: Mine\n apiKeyEnv: MINE_API_KEY\n api: openai-completions\n baseURL: https://mine.example/v1\n models:\n - id: m1\n"), 0o644)
if c := a.Check(); !strings.Contains(c, "home patch layer") {
t.Fatalf("check %q", c)
}
if err := a.Sync(); err != nil {
t.Fatal(err)
}
if s := read(hp); !strings.Contains(s, " magpie:\n") || !strings.Contains(s, " mine:\n") || a.Check() != "" {
t.Fatalf("check %q, home layer:\n%s", a.Check(), s)
}
// back to dsh's own model: magpie's route leaves the home layer, the
// user's stays
if err := f.Set("deepseek-flash"); err != nil {
t.Fatal(err)
}
if s := read(hp); strings.Contains(s, " magpie:\n") || !strings.Contains(s, " mine:\n") {
t.Fatalf("home layer:\n%s", s)
}
// a home layer without those entries is left as it is
other := "- id: tool-shell\n config:\n timeout: 30\n"
os.WriteFile(hp, []byte(other), 0o644)
if err := f.Set("magpie/deepseek/flash"); err != nil {
t.Fatal(err)
}
if read(hp) != other || f.Get() != "magpie/deepseek/flash" {
t.Fatalf("get %q, home layer:\n%s", f.Get(), read(hp))
}
}
// A model and effort picked in magpie reach the home layer, and setting none
// gives it its own start back.
func TestDshHomePatchLayerUnwire(t *testing.T) {
home, dir, web := dshRouteHome(t)
os.WriteFile(web, []byte("[]\n"), 0o644)
hp := filepath.Join(dir, "cordis.patch.yml")
userStart := "- id: agent-default-model\n config:\n provider: deepseek-official\n model: deepseek-v4-pro\n"
os.WriteFile(hp, []byte(userStart), 0o644)
a := dsh(home)
if err := a.Field("model").Set("magpie/deepseek/pro"); err != nil {
t.Fatal(err)
}
if a.Field("model").Get() != "magpie/deepseek/pro" {
t.Fatalf("get %q", a.Field("model").Get())
}
// one of dsh's own models, and its effort, go there too
if err := a.Field("model").Set("deepseek-flash"); err != nil {
t.Fatal(err)
}
if err := a.Field("effort").Set("max"); err != nil {
t.Fatal(err)
}
if b, _ := os.ReadFile(hp); !strings.Contains(string(b), `model: "deepseek-flash"`) || !strings.Contains(string(b), `reasoningEffort: "max"`) || a.Field("effort").Get() != "max" {
t.Fatalf("effort %q, home layer:\n%s", a.Field("effort").Get(), b)
}
if err := a.Field("model").Set(""); err != nil {
t.Fatal(err)
}
if b, _ := os.ReadFile(hp); string(b) != userStart {
t.Fatalf("home layer:\n%s", b)
}
}