X2 C1-3(自排工具面与提示词): - request_think(thinktool.go,照 transcode/tool.go 范式)+ reason 必填/urgency 闭集 + 双守门 (安全闸 + owner 判定:群聊/陌生人一律拒) - **成对**进两张唤醒态允许集(quiet + awake;CAPACITY_TOOL_SETS 是整体替换) - 提示词抽共用段落 + 自排前缀优先(不枚举工具清单);面板加 self_wake 标签与筛选轴 - 4 个变异自检(拿掉 owner 判定/只加一张表/放宽 urgency/前端标签恒等) X3 收口(与 X2 共用 wire_services.go ⇒ 一并落): - 删掉第三份 SSRF 实现(guard.go),改由 io.ValidateRequestURL 唯一判定 + 薄门面(保两个错误身份) - 浏览器会话挂进 a.close(真机:真 Chrome 被关、无残留、生产未扰动) - a.wireBrowser() 归位到 wire_runtime.go,删掉 ioToolOptions 的临时挂点 - 5 个变异自检 顺带补提交 C1-2 漏掉的 internal/config/runtimeconfig/backthink_test.go(ed50053 漏 add)。
142 lines
5.9 KiB
Go
142 lines
5.9 KiB
Go
package backthink
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// 本文件是 design/12 §N C.3 的**提示词判据**:三处指引必须共用同一批段落(抽取而不是
|
||
// 三次复制),自排轮必须说清自己是谁排的、只排一次、有上限;并且**不得**枚举工具清单
|
||
// 或写死上限数字(design/04 §4.1 + C.3 的「文案不写具体上限数字」)。
|
||
|
||
// promptsUnderTest 返回三处指引的(名字 → 文本)。
|
||
func promptsUnderTest() map[string]string {
|
||
return map[string]string{
|
||
"通用轮": guidancePrompt("heartbeat"),
|
||
"启动轮": guidancePrompt(StartupSummary),
|
||
"自排轮": guidancePrompt(selfWakeSummary("稍后回看她的日程")),
|
||
}
|
||
}
|
||
|
||
// TestPromptsShareTheExtractedParagraphs 断言「三处引用共用段落」是**结构事实**:
|
||
// 每处文本都必须逐字包含 toolFaceHint() 与它该有的 proactiveDecisionTail(...)。
|
||
//
|
||
// 变异:把某一处的共用段落改成复制粘贴的另一份措辞 ⇒ 本用例变红(这正是「三次复制必须
|
||
// 抽取」要防的漂移)。
|
||
func TestPromptsShareTheExtractedParagraphs(t *testing.T) {
|
||
t.Parallel()
|
||
face := toolFaceHint()
|
||
withUrgency := proactiveDecisionTail(true)
|
||
withoutUrgency := proactiveDecisionTail(false)
|
||
|
||
for name, p := range promptsUnderTest() {
|
||
if !strings.Contains(p, face) {
|
||
t.Fatalf("%s 没有逐字引用共用的工具面段落(toolFaceHint)", name)
|
||
}
|
||
want := withUrgency
|
||
if name == "启动轮" {
|
||
want = withoutUrgency
|
||
}
|
||
if !strings.Contains(p, want) {
|
||
t.Fatalf("%s 没有逐字引用共用的决策尾段落", name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestPromptsAdvertiseTheSelfWakeTool 断言三处都指名了 `request_think` 与自查入口
|
||
// `capability_list`:工具注册了而提示词不说,她不知道能自排(C1-3 的目的就是让她真能自排)。
|
||
func TestPromptsAdvertiseTheSelfWakeTool(t *testing.T) {
|
||
t.Parallel()
|
||
for name, p := range promptsUnderTest() {
|
||
if !strings.Contains(p, ThinkToolName) {
|
||
t.Fatalf("%s 没有提到 %s(她不知道能给自己排思考)", name, ThinkToolName)
|
||
}
|
||
if !strings.Contains(p, "capability_list") {
|
||
t.Fatalf("%s 没有给出可验证的自查入口 capability_list", name)
|
||
}
|
||
if !strings.Contains(p, "上限") {
|
||
t.Fatalf("%s 没有说明自排有次数上限", name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestPromptsDoNotWriteTheLimitNumber 断言提示词里**不写具体上限数字**(默认 4 次/小时
|
||
// 也不写):提示词与配置分叉就是撒谎的源头,所以只写「有上限」。
|
||
//
|
||
// 变异:在任一提示词里写下默认上限字面量 ⇒ 本用例变红。
|
||
func TestPromptsDoNotWriteTheLimitNumber(t *testing.T) {
|
||
t.Parallel()
|
||
digits := fmt.Sprintf("%d", defaultSelfWakeLimit)
|
||
for name, p := range promptsUnderTest() {
|
||
if strings.Contains(p, digits) {
|
||
t.Fatalf("%s 把上限数字 %s 写进了提示词:上限由配置决定,写死必然漂移", name, digits)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestGuidancePrompt_RoutesSelfWakeByPrefix 断言路由:带 SelfWakeSummaryPrefix 的触发
|
||
// 走自排专属指引并**原样带回她写下的理由**;其余稳定标识不受影响(两个标识不重叠)。
|
||
func TestGuidancePrompt_RoutesSelfWakeByPrefix(t *testing.T) {
|
||
t.Parallel()
|
||
const reason = "稍后回看她提过的体检安排"
|
||
p := guidancePrompt(selfWakeSummary(reason))
|
||
|
||
if !strings.Contains(p, "[自主思考 · 你自己排的一轮]") {
|
||
t.Fatalf("自排轮没有走专属指引:%s", p)
|
||
}
|
||
if !strings.Contains(p, "理由:"+reason) {
|
||
t.Fatalf("自排轮没有带回她写下的理由:%s", p)
|
||
}
|
||
if strings.Contains(p, "刚刚回来") {
|
||
t.Fatalf("自排轮落到了启动轮指引上:%s", p)
|
||
}
|
||
if strings.Contains(p, "无人在旁") {
|
||
t.Fatalf("自排轮落到了通用轮指引上:%s", p)
|
||
}
|
||
if !strings.Contains(p, proactiveDecisionTail(true)) {
|
||
t.Fatalf("自排轮的结论必须能留在穿透路径上(含 [紧急:…] 行):%s", p)
|
||
}
|
||
if !strings.Contains(p, "一轮只排一次") {
|
||
t.Fatalf("自排轮没有说清「一轮只排一次、不要连排」:%s", p)
|
||
}
|
||
|
||
// 启动轮仍走自己的分支(前缀判定没有把它吞掉)。
|
||
if q := guidancePrompt(StartupSummary); !strings.Contains(q, "刚刚回来") {
|
||
t.Fatalf("启动轮路由被自排前缀判定破坏了:%s", q)
|
||
}
|
||
}
|
||
|
||
// TestSelfWakeReasonOf_FallsBackHonestly 断言理由缺失时给**如实的**占位,而不是编一个。
|
||
func TestSelfWakeReasonOf_FallsBackHonestly(t *testing.T) {
|
||
t.Parallel()
|
||
if got := selfWakeReasonOf(SelfWakeSummaryPrefix); got == "" || !strings.Contains(got, "没有留下理由") {
|
||
t.Fatalf("空理由的占位 = %q, want 如实的「没有留下理由」", got)
|
||
}
|
||
if got := selfWakeReasonOf(selfWakeSummary(" 看看 ")); got != "看看" {
|
||
t.Fatalf("理由 = %q, want 去空白后的 %q", got, "看看")
|
||
}
|
||
}
|
||
|
||
// TestStartupPrompt_KeepsItsUrgencyScope 钉住启动轮的**刻意取舍**:它的引导不含
|
||
// `[紧急:…]` 行(见 startupPrompt 的注释 —— 启动轮的口径是「打个招呼」,把档位声明写进
|
||
// 它的引导等于给启动轮开一条主动穿透安静时段的路,属产品口径变更,不在 C1-3 授权内)。
|
||
//
|
||
// 主人若决定统一三处,把 startupPrompt 的实参改成 true 并**同时**更新本用例即可。
|
||
func TestStartupPrompt_KeepsItsUrgencyScope(t *testing.T) {
|
||
t.Parallel()
|
||
p := guidancePrompt(StartupSummary)
|
||
if strings.Contains(p, proactiveDecisionTail(true)) {
|
||
t.Fatalf("启动轮的决策尾不该含 [紧急:…] 行:%s", p)
|
||
}
|
||
if !strings.Contains(p, "[我决定:push]") || !strings.Contains(p, "[渠道:") {
|
||
t.Fatalf("启动轮的决策尾缺了基本的两行:%s", p)
|
||
}
|
||
// 通用轮与自排轮必须有那一行(否则她无法在自排轮上匿名穿过安静时段)。
|
||
for _, name := range []string{"通用轮", "自排轮"} {
|
||
if !strings.Contains(promptsUnderTest()[name], "[紧急:") {
|
||
t.Fatalf("%s 的决策尾缺了 [紧急:…] 行", name)
|
||
}
|
||
}
|
||
}
|