Documentation
¶
Overview ¶
fallback.go —— 「回合结束但没有协议 trailer」时的共用裁决。
职责:
- 构造无 trailer 且 git 有新提交时的回合结果(一律 OK=false)
- 构造该结果的失败原因文案,保证判定依据 / git 实况 / 正文尾部三者齐全
边界:
- 不查 git(那是 GitTurnStatus),不判断 hasNew:调用方把结论传进来
- 不处理 !hasNew 与 git 查询失败两条分支:它们仍转 question,由各 adapter 自行处置(各家的空文本守卫与原生提问抑制不同,强行统一会丢掉这些差异)
- 纯函数:不打日志。判定结果由各 adapter 在调用点记录(那里才有 taskID)
为什么这段判定必须共用:四个 adapter 曾各写一份,已经漂移——opencode 的 summary 取回合末 200 字,grok/codex 取一句固定文案,同一个判定给协调者看的 东西完全不同。四份副本各自漂移正是 B74 这类问题的温床。
Package turn 提供 executor 无关的「回合协议」:教模型协议的 prompt 模板、 解析模型输出的 trailer、回合取证与文本工具。
职责:
- RenderPrompt:把实现计划渲染成带回合纪律(提问/收尾/不切分支)的启动 prompt
- ParseTrailer:从回合末文本宽容提取协议 JSON(ask/finish)
- GitTurnStatus:trailer 缺失时以「是否有新提交」作事实裁决
- 文本截断与 render.log 追加等两 adapter 共用的小工具
边界:
- 不认识任何具体 executor(opencode/grok/claude),不发请求、不起进程
- 不做状态机迁移、不写 store:只做纯变换与两个受限 I/O(git 只读、日志追加)
为什么 prompt 模板与 ParseTrailer 必须同包:教模型协议的 prompt 与解析协议的 代码是同一契约的两半,分居两处必然出现「改纪律只改一半」的漂移——两个 executor 的协调者会看到不一样的东西。
Index ¶
- Constants
- func AppendRender(renderLogPath, delta string) error
- func ClampQuestion(text string) string
- func GitTurnStatus(repoPath, startCommit string) (branch, commit string, hasNew bool, err error)
- func NoTrailerFailReason(branch, commit, text string) string
- func NoTrailerResult(sessionID, branch, commit, text string) *executor.Result
- func RenderPrompt(taskID, planContent string) (string, error)
- func TailRunes(s string, n int) string
- func TruncateMarked(s string, n int) string
- func TruncateRunes(s string, n int) string
- type Trailer
Constants ¶
const QuestionTextLimit = 8000
QuestionTextLimit 是交给协调者的回合文本上限。兜底分类会把整个回合原文当 question 发出,一个失控的长回合会直接灌进工单行与协调者终端;全文始终在 任务目录的 render.log 里,截断不丢证据。
为什么导出:opencode 的 regression_group_a_test.go 直接断言这个上限, 搬包后它得能从 turn 引到同一个值——两处各写一个 8000 就会悄悄漂移。
Variables ¶
This section is empty.
Functions ¶
func AppendRender ¶
AppendRender 把 delta 追加到 renderLogPath(不存在则创建,权限 0644)。
注意:调用方通常在高频文本增量路径上调用本函数,失败应只 Warn 不中断回合 ——可见性是增强能力,不值得为它挂掉任务。
func ClampQuestion ¶
ClampQuestion 把兜底分类产出的整段回合文本收敛到 QuestionTextLimit, 超出时追加尾缀指明全文去处。
为什么**不能**复用 TruncateMarked:两者的「全文在哪」不同,尾缀因此必须不同。
- TruncateMarked 用于 permission 文本,全文在工单里(B6 契约:工单存全文、 事件截断),协调者 `handoff show` 就能拿到,`…(已截断)` 足够;
- 本函数用于 question 文本,全文**不在工单里**,只在任务目录的 render.log。 不指路 = 协调者拿到半截文本且不知道去哪找全文,证据链断掉。
这段尾缀是逐字从 opencode 现有实现搬来的,opencode 的 regression_group_a_test.go 断言 `strings.Contains(ev.Text, "render.log")`, 改字面量即回归。
func GitTurnStatus ¶
GitTurnStatus 返回工作区当前分支、HEAD commit,以及相对 startCommit 是否有新提交。
参数:
- repoPath: 任务工作目录(仓库或 worktree 路径)
- startCommit: 回合起点的 HEAD;空串表示起点未知,此时 hasNew 恒为 false
返回:分支名、HEAD hash、是否有新提交、错误
为什么需要它:模型可能不守收尾纪律(不输出 trailer)。此时唯一可信的是 git 实况——有新提交才可能是「干完了」,没有就该交协调者,绝不替模型宣布完成。
func NoTrailerFailReason ¶
NoTrailerFailReason 构造「回合未输出协议 trailer」的失败原因文案。
参数:
- branch, commit: GitTurnStatus 查到的 git 实况
- text: 回合正文全文(本函数负责截尾)
返回:一条同时包含判定依据、git 实况、正文尾部的文案
注意:三者缺一,协调者就得回去翻日志——这条要求来自 spec §3.2,不是格式偏好。
func NoTrailerResult ¶
NoTrailerResult 构造「无 trailer 但 git 有新提交」时的回合结果。
参数:
- sessionID: executor 会话标识,供续接与归档
- branch, commit: GitTurnStatus 查到的 git 实况
- text: 回合正文全文
返回:OK=false 的结果,git 实况保留在结构化字段里
为什么是 OK=false 而不是 OK=true:模型没有宣布完成,handoff 不替它宣布。 翻转不给协调者增加任何一次操作——OK 与 !OK 都落到 waiting_review, 而 done 与 continue 在该状态下都合法。变的只是那条事件从「已完成,摘要如下」 (邀请协调者不看 diff 就 done)变成「有新提交,但模型未按纪律宣布完成」 (要求看一眼)。代价为零,收益是不再有假完成。
注意:Branch/CommitHash 必须继续填。翻转若把 git 实况降级成一段自由文本, 协调者与任何下游都无法再结构化地取用它。
func RenderPrompt ¶
RenderPrompt 渲染带回合纪律的启动 prompt。
参数:
- taskID: 任务 ID,写入 prompt 标题行
- planContent: 实现计划全文(dispatch 侧已把 --prompt 附加指令拼在其后), 原样嵌入「实现计划」段,本函数不再二次拼接
返回:渲染后的 prompt 全文;模板执行失败时返回错误
func TruncateMarked ¶
TruncateMarked 按 rune 截断到 n,确实截断时追加 executor.TruncationMarker。
为什么必须带标记:上层据此 fail-closed——权限文本含标记说明裁决者看到的是 不完整命令,危险片段可能落在截断之外,黑名单与廉价模型都不可信。
Types ¶
type Trailer ¶
type Trailer struct {
Question string // ask 类型:需要人决策的问题
Branch string // finish 类型:提交所在分支
Commit string // finish 类型:提交 hash
Summary string // finish 类型:50 字内摘要
}
Trailer 是从回合末消息文本提取出的协议数据。
func ParseTrailer ¶
ParseTrailer 从回合末文本宽容提取协议 JSON(ask/finish)。
提取分两级:
- 主路径:最后一个非空行,从该行第一个 { 起解码一个 JSON 值(容忍前缀 与后缀正文)
- 回退:主路径无果时,取最后一个「以 { 开头」的行按整行解码(旧规则)
返回:
- kind: "ask"(附 Question)| "finish"(附 Branch/Commit/Summary)| "none"
- t: 解析出的协议数据;kind 为 "none" 时为零值
注意:
- 放宽只作用于最后一个非空行:正文中间复述协议 JSON 不会被误当成结论
- 找不到或 JSON 损坏时返回 "none",绝不 panic(模型输出不可信,防御在边界上做)
- 纯函数:不打日志,由调用方记录提取结果