diff --git a/internal/server/server.go b/internal/server/server.go index 6c4dd18..6a0ca78 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1382,6 +1382,13 @@ func (s *Server) artifactContent(w http.ResponseWriter, r *http.Request) { writeStoreErr(w, err) return } + if expected := r.URL.Query().Get("expectedHash"); expected != "" { + sum := sha256.Sum256(content) + if hex.EncodeToString(sum[:]) != expected { + http.Error(w, "来源已更新,请从文件列表查看当前版本;任务中的引用片段仍可用于历史核验", http.StatusConflict) + return + } + } ct := contentTypeOf(a) w.Header().Set("Content-Type", ct) setArtifactHeaders(w, ct) diff --git a/internal/server/task_sources_test.go b/internal/server/task_sources_test.go new file mode 100644 index 0000000..9fc52bb --- /dev/null +++ b/internal/server/task_sources_test.go @@ -0,0 +1,38 @@ +package server + +import ( + "agent-system-backend/internal/store" + core "agent-system-core" + "crypto/sha256" + "encoding/hex" + "net/http" + "net/http/httptest" + "testing" +) + +func TestTaskSourceContentDoesNotSubstituteLatestVersion(t *testing.T) { + srv, st := newTestServer(t) + original := []byte("上线六月") + a := core.Artifact{Owner: store.OwnerDefault, ProjectID: "p", Name: "input.txt"} + if e := st.CreateArtifact(&a, original); e != nil { + t.Fatal(e) + } + sum := sha256.Sum256(original) + hash := hex.EncodeToString(sum[:]) + get := func() *httptest.ResponseRecorder { + r := httptest.NewRequest("GET", "/api/artifacts/"+a.ID+"/content?expectedHash="+hash, nil) + r.SetPathValue("id", a.ID) + w := httptest.NewRecorder() + srv.artifactContent(w, r) + return w + } + if w := get(); w.Code != http.StatusOK || w.Body.String() != string(original) { + t.Fatal("original version not readable", w.Code, w.Body.String()) + } + if e := st.UpdateArtifactContent(store.OwnerDefault, a.ID, []byte("上线七月")); e != nil { + t.Fatal(e) + } + if w := get(); w.Code != http.StatusConflict { + t.Fatal("latest content masquerades as processed version", w.Code, w.Body.String()) + } +} diff --git a/internal/server/work_checkpoint_eval_test.go b/internal/server/work_checkpoint_eval_test.go index c4ac1c4..a22157f 100644 --- a/internal/server/work_checkpoint_eval_test.go +++ b/internal/server/work_checkpoint_eval_test.go @@ -69,7 +69,8 @@ func evalMechanismStream(ctx context.Context, model string, msgs []agentllm.Mess count++ } } - matches := regexp.MustCompile(`sourceId=(src_[a-f0-9]+) blockIndex=(\d+)`).FindAllStringSubmatch(sourceText, -1) + matches := regexp.MustCompile(`sourceId=(src_[a-f0-9]+) blockIndex=(\d+)[^\n]*\n引用定位片段:([^\n]*)`).FindAllStringSubmatch(sourceText, -1) + matches = filterCheckpointSources(matches) if count < 2 && len(matches) > 0 { var block int fmt.Sscan(matches[0][2], &block) @@ -318,3 +319,13 @@ func evalProviderStream(route map[string]any, dir string) agentllm.StreamFunc { return out } } + +func filterCheckpointSources(matches [][]string) [][]string { + var out [][]string + for _, match := range matches { + if len(match) > 3 && strings.Contains(match[3], "上线六月") { + out = append(out, match) + } + } + return out +}