Skip to content

Commit 104cf2d

Browse files
author
SqlRush
committed
Summarize telemetry events
1 parent 7d9d5b2 commit 104cf2d

4 files changed

Lines changed: 157 additions & 2 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1456,7 +1456,7 @@ M7 补充:terminal input parser 和 configurable keybinding name parser 现在
14561456
- Chrome/computer-use/voice/native integrations。
14571457
- enterprise/gated/platform-specific behavior。
14581458

1459-
当前状态:已新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot,并按 LSP 空 diagnostics 语义清空旧文件诊断;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,并提供 command/display/alias 安全解析;未启用时仍不注册或泄露 gated 工具 schema。实际 repl bridge server、session websocket/direct connect、LSP server manager、diagnostic producer、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
1459+
当前状态:已新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容,并已有安全 JSONL 读取、类型/模型过滤和汇总统计地基;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot,并按 LSP 空 diagnostics 语义清空旧文件诊断;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,并提供 command/display/alias 安全解析;未启用时仍不注册或泄露 gated 工具 schema。实际 repl bridge server、session websocket/direct connect、LSP server manager、diagnostic producer、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
14601460

14611461
## Recommended Next Steps
14621462

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ test/parity/ # golden tests against TS/official behavior
745745
- 每个 gated feature 独立开关测试。
746746
- 不启用 feature 时二进制行为和可见 schema 不泄露 gated 工具/命令。
747747

748-
- 本轮补充:新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot,并按 LSP 空 diagnostics 语义清空旧文件诊断;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,并提供 command/display/alias 安全解析;未启用时仍不注册或泄露 gated 工具 schema。实际 repl bridge server、session websocket/direct connect、LSP server manager、diagnostic producer、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
748+
- 本轮补充:新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容,并已有安全 JSONL 读取、类型/模型过滤和汇总统计地基;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot,并按 LSP 空 diagnostics 语义清空旧文件诊断;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,并提供 command/display/alias 安全解析;未启用时仍不注册或泄露 gated 工具 schema。实际 repl bridge server、session websocket/direct connect、LSP server manager、diagnostic producer、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
749749

750750
### M12: Parity hardening
751751

internal/telemetry/telemetry.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package telemetry
22

33
import (
4+
"bufio"
45
"encoding/json"
6+
"errors"
57
"os"
68
"path/filepath"
79
"sort"
10+
"strings"
811
"time"
912

1013
"ccgo/internal/contracts"
@@ -29,6 +32,23 @@ type Event struct {
2932
Error string `json:"error,omitempty"`
3033
}
3134

35+
type Filter struct {
36+
Type string
37+
Model string
38+
Limit int
39+
}
40+
41+
type Summary struct {
42+
Total int `json:"total"`
43+
ByType map[string]int `json:"by_type,omitempty"`
44+
ByModel map[string]int `json:"by_model,omitempty"`
45+
ToolEvents int `json:"tool_events,omitempty"`
46+
ToolErrors int `json:"tool_errors,omitempty"`
47+
ErrorEvents int `json:"error_events,omitempty"`
48+
Compactions int `json:"compactions,omitempty"`
49+
TokenWarnings int `json:"token_warnings,omitempty"`
50+
}
51+
3252
func SessionPath(sessionPath string, sessionID contracts.ID) string {
3353
if sessionPath == "" || sessionID == "" {
3454
return ""
@@ -60,6 +80,94 @@ func Append(path string, event Event) error {
6080
return err
6181
}
6282

83+
func Load(path string) ([]Event, error) {
84+
if path == "" {
85+
return nil, os.ErrInvalid
86+
}
87+
file, err := os.Open(path)
88+
if errors.Is(err, os.ErrNotExist) {
89+
return nil, nil
90+
}
91+
if err != nil {
92+
return nil, err
93+
}
94+
defer file.Close()
95+
var events []Event
96+
scanner := bufio.NewScanner(file)
97+
for scanner.Scan() {
98+
line := strings.TrimSpace(scanner.Text())
99+
if line == "" {
100+
continue
101+
}
102+
var event Event
103+
if err := json.Unmarshal([]byte(line), &event); err != nil {
104+
return nil, err
105+
}
106+
events = append(events, event)
107+
}
108+
if err := scanner.Err(); err != nil {
109+
return nil, err
110+
}
111+
return events, nil
112+
}
113+
114+
func FilterEvents(events []Event, filter Filter) []Event {
115+
eventType := strings.TrimSpace(filter.Type)
116+
model := strings.TrimSpace(filter.Model)
117+
out := make([]Event, 0, len(events))
118+
for _, event := range events {
119+
if eventType != "" && event.Type != eventType {
120+
continue
121+
}
122+
if model != "" && event.Model != model {
123+
continue
124+
}
125+
out = append(out, event)
126+
if filter.Limit > 0 && len(out) >= filter.Limit {
127+
break
128+
}
129+
}
130+
return out
131+
}
132+
133+
func Summarize(events []Event) Summary {
134+
summary := Summary{
135+
Total: len(events),
136+
ByType: map[string]int{},
137+
ByModel: map[string]int{},
138+
}
139+
for _, event := range events {
140+
if event.Type != "" {
141+
summary.ByType[event.Type]++
142+
}
143+
if event.Model != "" {
144+
summary.ByModel[event.Model]++
145+
}
146+
if event.ToolUseID != "" {
147+
summary.ToolEvents++
148+
}
149+
if event.ToolResultErr {
150+
summary.ToolErrors++
151+
}
152+
if event.Error != "" {
153+
summary.ErrorEvents++
154+
}
155+
if event.CompactTrigger != "" {
156+
summary.Compactions++
157+
}
158+
if event.TokenState != "" {
159+
summary.TokenWarnings++
160+
}
161+
}
162+
if len(summary.ByType) == 0 {
163+
summary.ByType = nil
164+
}
165+
if len(summary.ByModel) == 0 {
166+
summary.ByModel = nil
167+
}
168+
return summary
169+
}
170+
63171
func SortedMapKeys(values map[string]any) []string {
64172
keys := make([]string, 0, len(values))
65173
for key := range values {

internal/telemetry/telemetry_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,50 @@ func TestAppendWritesJSONLine(t *testing.T) {
5959
t.Fatalf("progress keys = %#v", got.ProgressKeys)
6060
}
6161
}
62+
63+
func TestLoadFilterAndSummarize(t *testing.T) {
64+
path := filepath.Join(t.TempDir(), "sess_1", "telemetry.jsonl")
65+
for _, event := range []Event{
66+
{SessionID: "sess_1", Type: "user_message"},
67+
{SessionID: "sess_1", Type: "assistant_message", Model: "sonnet"},
68+
{SessionID: "sess_1", Type: "tool_result", ToolUseID: "toolu_1", ToolResultErr: true, Error: "failed"},
69+
{SessionID: "sess_1", Type: "compact", CompactTrigger: "manual"},
70+
{SessionID: "sess_1", Type: "token_warning", TokenState: "warning"},
71+
} {
72+
if err := Append(path, event); err != nil {
73+
t.Fatal(err)
74+
}
75+
}
76+
events, err := Load(path)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
if len(events) != 5 {
81+
t.Fatalf("events = %#v", events)
82+
}
83+
filtered := FilterEvents(events, Filter{Type: "assistant_message", Model: "sonnet", Limit: 1})
84+
if len(filtered) != 1 || filtered[0].Type != "assistant_message" {
85+
t.Fatalf("filtered = %#v", filtered)
86+
}
87+
summary := Summarize(events)
88+
if summary.Total != 5 ||
89+
summary.ByType["tool_result"] != 1 ||
90+
summary.ByModel["sonnet"] != 1 ||
91+
summary.ToolEvents != 1 ||
92+
summary.ToolErrors != 1 ||
93+
summary.ErrorEvents != 1 ||
94+
summary.Compactions != 1 ||
95+
summary.TokenWarnings != 1 {
96+
t.Fatalf("summary = %#v", summary)
97+
}
98+
}
99+
100+
func TestLoadMissingFile(t *testing.T) {
101+
events, err := Load(filepath.Join(t.TempDir(), "missing.jsonl"))
102+
if err != nil {
103+
t.Fatal(err)
104+
}
105+
if events != nil {
106+
t.Fatalf("events = %#v", events)
107+
}
108+
}

0 commit comments

Comments
 (0)