返回 DeepSeek-Reasonix
event_tail.go
根目录 / internal / taskmonitor / event_tail.go
1 package taskmonitor
2
3 import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "errors"
8 "io"
9 "os"
10 "path/filepath"
11 )
12
13 type EventTail struct {
14 Items []TaskEvent
15 NextOffset int64
16 Reset bool
17 }
18
19 // ReadEventTail reads only complete JSONL lines after a catalog byte
20 // checkpoint. It preserves FileStore's identifier and symlink defenses.
21 func (s *FileStore) ReadEventTail(ctx context.Context, projectDir, taskID string, offset int64) (EventTail, error) {
22 out := EventTail{Items: []TaskEvent{}, NextOffset: offset}
23 if err := ctx.Err(); err != nil {
24 return out, err
25 }
26 id, err := safeID(taskID)
27 if err != nil {
28 return out, err
29 }
30 root, err := s.taskRoot(projectDir)
31 if err != nil {
32 return out, err
33 }
34 path := filepath.Join(root, id, "events.jsonl")
35 if err := rejectSymlinkChain(root, path); err != nil {
36 return out, err
37 }
38 f, err := os.Open(path)
39 if os.IsNotExist(err) {
40 return out, nil
41 }
42 if err != nil {
43 return out, err
44 }
45 defer f.Close()
46 info, err := f.Stat()
47 if err != nil {
48 return out, err
49 }
50 if offset < 0 || offset > info.Size() {
51 offset, out.NextOffset, out.Reset = 0, 0, true
52 }
53 if _, err := f.Seek(offset, io.SeekStart); err != nil {
54 return out, err
55 }
56 reader := bufio.NewReader(f)
57 for {
58 line, readErr := reader.ReadBytes('\n')
59 if len(line) > 0 && line[len(line)-1] == '\n' {
60 out.NextOffset += int64(len(line))
61 line = line[:len(line)-1]
62 var event TaskEvent
63 if json.Unmarshal(line, &event) == nil {
64 out.Items = append(out.Items, event)
65 }
66 }
67 if errors.Is(readErr, io.EOF) {
68 break
69 }
70 if readErr != nil {
71 return out, readErr
72 }
73 }
74 return out, nil
75 }
76
76 lines GO