ci / vet-and-test (pull_request) Successful in 48s
Why: Real-run before/after evidence (phase01 card) showed later turns never re-read the uploaded source (0 original reads in plan-followup) because the attachment only existed in the current turn's injection and the context window hard default was 32000 chars - a 25k-char transcript tripped the clear-on-first request, replacing old tool results with a pointer the model has no tool to follow. MiniMax is an interleaved-thinking model whose docs require <think> blocks in assistant history; dropping them degrades later turns. Evidence: SELF - new tests TestThinkReplayWrapsAssistantHistory, TestContextWindowScalesFromTokens, TestThinkReplayEnabled, TestPersistInputTextsAndListWorkspaceInputs; go test ./internal/... passes; live before/after comparison on the golden task set recorded on the phase02 task card per the new policy clause. Impact: models.chat.contextWindow/outputReserve/contextBuffer (tokens, defaults 128k/16k/4k) drive the in-run clear threshold, converted to chars; cleared tool results spill to <sandbox>/.agent/results with readable paths; attachment parse text persists as inputs/<name>.md and the env section lists all workspace inputs every turn; context.thinkReplay (auto|on|off, auto = MiniMax) replays thinking blocks into request-side assistant history only.
66 lines
2.7 KiB
Go
66 lines
2.7 KiB
Go
package runmgr
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
core "agent-system-core"
|
|
)
|
|
|
|
// 附件解析文本要落 inputs/<原名>.md 且跨轮可见:后续轮上下文里原件只剩占位时,
|
|
// 模型仍能读到原文(实测「根据纪要制定方案」轮 0 次读原件,只能编细节)。
|
|
func TestPersistInputTextsAndListWorkspaceInputs(t *testing.T) {
|
|
root := t.TempDir()
|
|
if _, err := StageWorkspaceInputs(root, "alice", "conv1", []WorkspaceFile{
|
|
{Name: "会议.docx", Content: []byte("bin")},
|
|
}, "", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
long := strings.Repeat("会议结论先入库。", 40) // >200 字
|
|
docs := []core.ParsedDocument{{
|
|
Meta: core.DocMeta{Filename: "会议.docx"},
|
|
Blocks: []core.DocBlock{{Type: core.BlockPara, Text: long}},
|
|
}}
|
|
PersistInputTexts(root, "alice", "conv1", docs)
|
|
|
|
extracted := filepath.Join(root, "alice", "conv1", "inputs", "会议.md")
|
|
b, err := os.ReadFile(extracted)
|
|
if err != nil {
|
|
t.Fatalf("extracted text not written: %v", err)
|
|
}
|
|
if !strings.Contains(string(b), "会议结论先入库") {
|
|
t.Fatalf("extracted text wrong: %q", string(b)[:50])
|
|
}
|
|
|
|
// 短文本(解析失败/过短)与已是文本类的原件不写。
|
|
short := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "短.docx"}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: "太短"}}}}
|
|
PersistInputTexts(root, "alice", "conv1", short)
|
|
textKind := []core.ParsedDocument{{Meta: core.DocMeta{Filename: "笔记.md"}, Blocks: []core.DocBlock{{Type: core.BlockPara, Text: long}}}}
|
|
PersistInputTexts(root, "alice", "conv1", textKind)
|
|
if _, err := os.Stat(filepath.Join(root, "alice", "conv1", "inputs", "短.md")); err == nil {
|
|
t.Fatal("short parse should not be persisted")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "alice", "conv1", "inputs", "笔记.md.md")); err == nil {
|
|
t.Fatal("text-kind originals should not get a duplicate extract")
|
|
}
|
|
|
|
// 跨轮列出:包含原件 + 解析文本,而不是只有本轮 staging。
|
|
got := ListWorkspaceInputs(root, "alice", "conv1", []string{"inputs/会议.docx"})
|
|
joined := strings.Join(got, ",")
|
|
if !strings.Contains(joined, "inputs/会议.docx") || !strings.Contains(joined, "inputs/会议.md") {
|
|
t.Fatalf("listing should include original + extracted: %v", got)
|
|
}
|
|
// 目录缺失时回落 staging 列表。
|
|
fallback := ListWorkspaceInputs(root, "alice", "nope", []string{"inputs/only.txt"})
|
|
if len(fallback) != 1 || fallback[0] != "inputs/only.txt" {
|
|
t.Fatalf("fallback listing broken: %v", fallback)
|
|
}
|
|
// root 未配置:全部 no-op。
|
|
PersistInputTexts("", "alice", "conv1", docs)
|
|
if got := ListWorkspaceInputs("", "alice", "conv1", []string{"inputs/a.txt"}); got[0] != "inputs/a.txt" {
|
|
t.Fatalf("unconfigured root should pass through staged: %v", got)
|
|
}
|
|
}
|