返回 DeepSeek-Reasonix
diagnostics.go
根目录 / desktop / internal / browserops / diagnostics.go
1 package browserops
2
3 import (
4 "encoding/json"
5 "errors"
6 "io"
7 "os"
8 "sort"
9 "time"
10 )
11
12 // DiagnosticOperation deliberately omits grants, document tokens, argument
13 // digests and free-form errors. Tool errors remain in the redacted transcript.
14 type DiagnosticOperation struct {
15 OperationID string `json:"operationId"`
16 TabID string `json:"tabId"`
17 Action string `json:"action"`
18 State State `json:"state"`
19 ReservedAt time.Time `json:"reservedAt"`
20 SettledAt time.Time `json:"settledAt"`
21 }
22 type DiagnosticSnapshot struct {
23 Available bool `json:"available"`
24 Truncated bool `json:"truncated"`
25 FieldsTruncated bool `json:"fieldsTruncated"`
26 Matched int `json:"matched"`
27 Limit int `json:"limit"`
28 Operations []DiagnosticOperation `json:"operations"`
29 Coverage string `json:"coverage"`
30 }
31
32 func diagnosticSnapshot(file ledgerFile, scope string) DiagnosticSnapshot {
33 const limit = 200
34 out := DiagnosticSnapshot{Available: true, Limit: limit, Operations: []DiagnosticOperation{}, Coverage: "Only records with explicit diagnostic attribution; pre-upgrade or downgraded records cannot be attributed. Reserved states are observed without recovery or replay."}
35 if scope == "" {
36 out.Available = false
37 return out
38 }
39 for _, op := range file.Operations {
40 if op == nil || op.DiagnosticScope != scope {
41 continue
42 }
43 out.Matched++
44 bounded := func(value string, limit int) string {
45 if len(value) > limit {
46 out.FieldsTruncated = true
47 return value[:limit]
48 }
49 return value
50 }
51 out.Operations = append(out.Operations, DiagnosticOperation{bounded(op.ID, 100), bounded(op.TabID, 160), bounded(op.Action, 64), State(bounded(string(op.State), 32)), op.ReservedAt, op.SettledAt})
52 // Keep memory bounded even when the on-disk ledger is large.
53 sort.Slice(out.Operations, func(i, j int) bool {
54 if out.Operations[i].ReservedAt.Equal(out.Operations[j].ReservedAt) {
55 return out.Operations[i].OperationID < out.Operations[j].OperationID
56 }
57 return out.Operations[i].ReservedAt.After(out.Operations[j].ReservedAt)
58 })
59 if len(out.Operations) > limit {
60 out.Operations = out.Operations[:limit]
61 }
62 }
63 out.Truncated = out.Matched > limit
64 return out
65 }
66
67 func (l *Ledger) Diagnostics(scope string) DiagnosticSnapshot {
68 l.mu.Lock()
69 defer l.mu.Unlock()
70 return diagnosticSnapshot(l.file, scope)
71 }
72
73 // ReadDiagnostics never calls Open: an export must not settle reserved writes.
74 func ReadDiagnostics(path, scope string) (DiagnosticSnapshot, error) {
75 f, err := os.Open(path)
76 if err != nil {
77 return DiagnosticSnapshot{}, err
78 }
79 defer f.Close()
80 const budget = 64 << 20
81 data, err := io.ReadAll(io.LimitReader(f, budget+1))
82 if err != nil {
83 return DiagnosticSnapshot{}, err
84 }
85 if len(data) > budget {
86 return DiagnosticSnapshot{}, errors.New("browser ledger exceeds diagnostic read budget")
87 }
88 var file ledgerFile
89 if err = json.Unmarshal(data, &file); err != nil {
90 return DiagnosticSnapshot{}, err
91 }
92 if file.Version != ledgerVersion {
93 return DiagnosticSnapshot{}, errors.New("unsupported browser ledger version")
94 }
95 return diagnosticSnapshot(file, scope), nil
96 }
97
97 lines GO