package main // relay_policy.go 的单元测试。 // // 这三个函数是 Node 侧 lib/relay-policy.js 的 Go 对应物,**语义必须一致** —— // 一个平台自动回信、另一个不回,同一条 Agent 链的行为就取决于对方是谁, // 那是无法排查的。所以这里的判据与那边的 relay-policy.test.mjs 逐条对齐。 import ( "strings" "testing" ) func TestAutoRelay_HumanSenderRelays(t *testing.T) { d := autoRelayDecision(true, "jianf") if !d.relay { t.Fatalf("人类来信必须自动转发,got reason=%q", d.reason) } } func TestAutoRelay_AgentSenderDoesNot(t *testing.T) { d := autoRelayDecision(false, "dsh") if d.relay { t.Fatal("Agent 间通信必须由模型主动 send_mail —— 两边都自动回会无休止互相唤醒") } if !strings.Contains(d.reason, "dsh") { t.Errorf("日志要说清是谁,got %q", d.reason) } if !strings.Contains(d.reason, "Agent") { t.Errorf("理由要点明这是 Agent 来信,got %q", d.reason) } } // 「本轮没有回信」有三种原因,日志里必须能分辨。 func TestAutoRelay_NoRecipientHasDistinctReason(t *testing.T) { d := autoRelayDecision(true, "") if d.relay { t.Fatal("不知道回给谁时不该发") } if !strings.Contains(d.reason, "不知道回给谁") { t.Errorf("理由必须与「对方是 Agent」区分得开,got %q", d.reason) } } func TestReplyInstruction_HumanPromisesAutoRelay(t *testing.T) { s := replyInstruction(true, "") if !strings.Contains(s, "回信不用你自己发") { t.Errorf("人类来信要告诉模型插件会代劳,got %q", s) } // 承诺了就必须真的做 if !autoRelayDecision(true, "jianf").relay { t.Error("提示词与决策不一致 —— 承诺了自动转发却不转") } } func TestReplyInstruction_AgentSaysPluginWontReply(t *testing.T) { s := replyInstruction(false, "") if strings.Contains(s, "回信不用你自己发") { t.Error("这句话在 Agent → Agent 时是假的 —— 说了它模型就会把话说完然后停手") } if !strings.Contains(s, "不会替你回信") { t.Errorf("必须明说插件不代劳,got %q", s) } if !strings.Contains(s, "send_mail") { t.Errorf("必须给出唯一可行的做法,got %q", s) } } func TestReplyInstruction_AgentDiscouragesEmptyCourtesy(t *testing.T) { s := replyInstruction(false, "") if !strings.Contains(s, "收到") || !strings.Contains(s, "确认") { t.Errorf("要点名那种没有信息量的回复,got %q", s) } if !strings.Contains(s, "互相客套") { t.Errorf("要说清后果,否则模型不知道为什么被劝阻,got %q", s) } } func TestReplyInstruction_CarriesReplyAddress(t *testing.T) { with := replyInstruction(false, "dsh@/x.别名") if !strings.Contains(with, "dsh@/x.别名") { t.Error("要它自己发信却不给地址,它会拼一个 .new 出来 —— 那会静默开新会话") } without := replyInstruction(false, "") if strings.Contains(without, "(回信地址:)") { t.Error("没有地址时不该留一个空括号") } } func TestInboundHeadline_ReplyIsNotNewWork(t *testing.T) { h := inboundHeadline("m-1", false, false) if !strings.Contains(h, "回复") { t.Errorf("要说清这是回复,got %q", h) } if !strings.Contains(h, "不是新任务") { t.Errorf("把回复当新任务处理正是互相客套的起点,got %q", h) } } // in_reply_to 是最强信号:它同时出现时压过补投标记。 func TestInboundHeadline_ReplyBeatsCatchup(t *testing.T) { h := inboundHeadline("m-1", true, true) if !strings.Contains(h, "回复") { t.Errorf("in_reply_to 优先,got %q", h) } } func TestInboundHeadline_MarksAgentSender(t *testing.T) { if !strings.Contains(inboundHeadline("", false, false), "Agent") { t.Error("Agent 来信要在标题里标出来") } if strings.Contains(inboundHeadline("", true, false), "Agent") { t.Error("人类来信不该带这个括号 —— 那是噪音") } } func TestInboundHeadline_CatchupExplained(t *testing.T) { h := inboundHeadline("", true, true) if !strings.Contains(h, "积压") { t.Errorf("补投要说明,否则模型按「刚到的」语气回,got %q", h) } }