package main import ( "os" "path/filepath" "strings" "testing" ) // ★ read_inbox 在**非邮件轮次**里必须仍能带上 workspace(2026-10-01) // // ## 缺陷 // // homeagent 的工具是**全局注册**的(webui / a2a / 对话都能调),而 // `currentWorkspace` 只在 `InjectInputSync` 期间有值 ⇒ 从对话里调 // `read_inbox` 拼不出 `&workspace=` ⇒ 服务端 400。 // // 实测(2026-10-01 14:32:26):近 24h **3 次失败、0 次成功**。 // 另三桥没这个问题,因为它们的工具**只在邮件驱动回合里被装配** // —— 那是架构差异,不是「某个桥忘了写」。 // // ## 为什么回落到进程 cwd 是对的取值 // // homed 按调用上下文以子进程方式拉起插件,那个进程的 cwd 就是 // **调用方的工作目录**(实测:对话里那个桥 cwd=/home/program/agentmail)。 // // ⚠ 它是**默认**不是**保证**:库里 17 个工作区,cwd 只反映当前这一个调用方。 // 收窄语义不变(仍只列这个工作区的信)—— 跨工作区误读正是 // 2026-09-26 那个缺陷本身,拿不准时宁可少列。 func TestEffectiveWorkspaceFallsBackToProcessCwd(t *testing.T) { p := &Plugin{} // 场景 A:邮件轮次内 —— currentWorkspace 有值,**必须优先用它** p.currentWorkspace = "/home/program/agentmail" if got := p.effectiveWorkspace(); got != "/home/program/agentmail" { t.Fatalf("轮次内必须用信封上的 path 位,实际 %q", got) } // 场景 B:对话里(currentWorkspace 为空)—— 回落到 cwd p.currentWorkspace = "" got := p.effectiveWorkspace() if got == "" { t.Fatal("★ 对话里必须有 workspace 可用,否则 read_inbox 必然 400") } cwd, _ := os.Getwd() if got != cwd { t.Fatalf("回落值应等于进程 cwd:得到 %q,cwd=%q", got, cwd) } if !filepath.IsAbs(got) { t.Fatalf("回落值必须是绝对路径(服务端要求),实际 %q", got) } } func TestEffectiveWorkspaceTrimsBlanks(t *testing.T) { // 空白串不是有效取值 —— 当作没有,回落到 cwd。 // 否则会拼出 `workspace=%20%20`,服务端拿到一个不存在的目录。 p := &Plugin{currentWorkspace: " "} got := p.effectiveWorkspace() cwd, _ := os.Getwd() if strings.TrimSpace(got) == "" { t.Fatal("空白 currentWorkspace 不得被当作有效取值") } if got != cwd { t.Fatalf("空白应回落到 cwd,得到 %q", got) } } func TestInboxURLAlwaysCarriesWorkspace(t *testing.T) { // 这条是**端到端形状**:拼出来的 URL 必须带 workspace。 // 上面两格验的是取值,这一格验的是「真的拼进去了」—— // 2026-09-26 那次就是取值对了、拼接处漏了,两者是不同的形状。 p := &Plugin{} u := p.inboxURL("unread", 5) if !strings.Contains(u, "workspace=") { t.Fatalf("★ inboxURL 必须带 workspace(缺了服务端直接 400):%s", u) } } func TestScopeQuerySkippedOutsideTurnButInboxNever(t *testing.T) { // scopeQuery 在非轮次时**整体**返回空串(那是刻意的:没有会话可收窄), // 但 inboxURL 仍必须带 workspace —— 两者不是同一个开关。 // 混起来就会出现「非轮次时 workspace 也一起没了」,即 2026-10-01 那个缺陷。 p := &Plugin{} if q := p.scopeQuery("&"); q != "" { t.Fatalf("非轮次时 scopeQuery 应为空,实际 %q", q) } if !strings.Contains(p.inboxURL("unread", 5), "workspace=") { t.Fatal("★ 非轮次时 inboxURL 仍必须带 workspace") } }