Cline SDK 遥测规范:Greptile 规则驱动的 OpenTelemetry 事件治理实践
本文以 Cline 仓库中 .greptile/rules.md 这份遥测标准文档为主体,完整讲解 Cline SDK 的 OpenTelemetry 事件体系:事件唯一事实源(CORE_TELEMETRY_EVENTS)与类型化 capture*() 助手的用法、激活漏斗(activation funnel)与各事件的发射职责归属、task.completed 的语义锚定、CLI 目录顺序约束、Hub 守护进程遥测与认证生命周期完整性要求。读完本文,你将掌握在 Cline 代码库中新增一个合规遥测事件的完整流程,并理解这些规则如何用 Greptile 配置自动化的方式在 PR 层面强制执行。
1. 规则文件在仓库中的位置与组成
.greptile/ 目录包含三个协作文件:
- rules.md:本文主体。它解释结构化规则"为什么"存在,为代码审查 Agent(Greptile)提供上下文,避免误报;
- config.json:声明
strictness: 2、triggerOnUpdates: true、statusCheck: true,以及 4 条可执行的检查规则(下文第 3 节逐条讲解); - files.json:列出遥测相关的"关键文件清单",告诉审查工具哪些文件是评审遥测变更时的事实依据(ground truth),包括 core-events.ts、shared 层的 ITelemetryService 接口、TelemetryService 参考实现、OpenTelemetryProvider 以及 sdk/ARCHITECTURE.md 与 sdk/AGENTS.md。
需要特别说明的一点(原文档明确声明):Cline SDK 不依赖原始 cline/cline 仓库的遥测栈,两者是平行且独立的实现;这份 .greptile/ 配置只覆盖 SDK 一侧。
2. 遥测技术栈:OpenTelemetry 是唯一传输通道
rules.md 给出的事件流向图:
core-events.ts (event catalog + typed helpers)
↓
ITelemetryService (sdk/packages/shared) ← 接口契约
↓
TelemetryService (sdk/packages/core) ← 多适配器扇出
↓
OpenTelemetryAdapter → OpenTelemetryProvider ← OTLP 传输
↓
OTLP endpoint (collector or vendor)
从源码结构看,这条链路是真实存在的:
- 事件目录 + 类型化助手:core-events.ts(857 行)导出
CORE_TELEMETRY_EVENTS冻结常量对象和所有capture*()助手函数。 - 接口契约:sdk/packages/shared/src/services/telemetry.ts 定义
ITelemetryService接口(capture、captureRequired、recordCounter、recordHistogram、recordGauge、flush、dispose),并在其中声明了 OTLP 配置项——otlpEndpoint、otlpProtocol(当前 SDK 支持有限制,为"http/json")、按信号拆分的otlpMetricsEndpoint/otlpLogsEndpoint/otlpTracesEndpoint及各自的otlpHeaders。 - 多适配器扇出:TelemetryService.ts 实现
ITelemetryService,其capture()会把属性合并后遍历this.adapters逐个emit:
capture(input: { event: string; properties?: TelemetryProperties }): void {
const properties = this.buildAttributes(input.properties);
for (const adapter of this.adapters) {
adapter.emit(input.event, properties);
}
}
buildAttributes() 的合并顺序值得注意:commonProperties → 调用方 properties → metadata → distinct_id(若已设置)→ 最后固定覆盖 device_id(默认由 resolveCoreDeviceId() 生成的确定性设备标识)。这解释了为什么 rules.md 强调"每个 host 只允许一个遥测服务"——distinct-id 状态、opt-out 跟踪和 flush 归属都绑在这个单例上。
3. 事件唯一事实源与新增事件的三步流程
CORE_TELEMETRY_EVENTS 是全部事件名的唯一事实源。rules.md 列举的核心分组为 CLIENT、SESSION、USER、TASK、HOOKS、WORKSPACE;对照 core-events.ts 当前源码,目录实际还包含 AGENT、SDK、FEATURE_FLAGS 三个分组。各分组的代表性事件:
| 分组 | 事件常量 | 事件名 |
|---|---|---|
CLIENT |
EXTENSION_ACTIVATED |
user.extension_activated |
SESSION |
STARTED / ENDED |
session.started / session.ended |
USER |
AUTH_STARTED … TELEMETRY_OPT_OUT |
user.auth_started、user.auth_succeeded、user.auth_failed、user.auth_logged_out、user.opt_out 等 |
TASK |
CREATED、COMPLETED、CONVERSATION_TURN、TOKEN_USAGE、TOOL_USED 等 |
task.created、task.completed、task.conversation_turn、task.tokens、task.tool_used 等 |
WORKSPACE |
INITIALIZED / INIT_ERROR / PATH_RESOLVED |
workspace.initialized、workspace.init_error、workspace.path_resolved |
HOOKS |
DISCOVERY_COMPLETED |
hooks.discovery_completed |
SDK |
ERROR、TOOL_TIMEOUT 等 |
sdk.tool_timeout、sdk.plan_mode_command_blocked 等 |
核心纪律:调用点永远不要使用原始字符串字面量作为事件名。 新增事件必须走三步流程:
- 在
CORE_TELEMETRY_EVENTS中添加常量; - 在其旁新增类型化
capture*()助手(properties参数带类型); - 在
core-events.test.ts中新增单元测试,断言该事件走尊重 opt-out 的capture路径,而非captureRequired。测试惯例的表述是"emits X as a normal opt-out-respecting event"——因为 opt-out 由OptedOutTelemetryService强制执行,其capture是 no-op。只有确实需要绕过 opt-out 的事件才能用captureRequired,且必须在测试中显式断言这一点。
命名规范:所有事件名和属性一律使用 snake_case。 这一点可以从 CORE_TELEMETRY_EVENTS 的字面量(user.extension_activated、task.conversation_turn)和属性接口(如 WorkspaceInitializedProperties 的 root_count、vcs_types、init_duration_ms)中得到印证。
配套的结构化规则(config.json)sdk-no-raw-event-strings 会把这条纪律变成自动检查:任何在 telemetry.capture()、telemetry.captureRequired()、recordCounter()/recordHistogram()/recordGauge() 调用中出现、且未引用 CORE_TELEMETRY_EVENTS 的字符串字面量都会被标记(severity: medium,scope 覆盖 sdk/packages/core/src/**、sdk/packages/agents/src/**、apps/cli/src/**、apps/vscode/src/**)。
4. 激活漏斗与发射职责归属
下游分析依赖的标准漏斗:
user.extension_activated
→ workspace.initialized
→ workspace.path_resolved (gated on multi-root)
→ task.created
→ task.conversation_turn (one per turn, source: "user" | "assistant")
→ task.completed (source: "submit_and_exit" | "shutdown")
发射职责(emission ownership)的划分:
user.extension_activated:每个 host 进程发射一次,由 host 专属助手负责——CLI 用captureCliExtensionActivated,VS Code 用captureExtensionActivated。workspace.initialized/workspace.init_error:由prepareLocalRuntimeBootstrap中的按进程去重发射器负责;host 不得重复发射。workspace.path_resolved:仅当WorkspaceManager暴露多于一个根(multi-root)时,从默认工具执行器发射。task.*:由 sdk/packages/core/src/cline-core/ 和 sdk/packages/core/src/runtime/ 中的核心会话生命周期代码发射;host 不得重复发射。
这一"核心发射、host 不重复"的划分,正好对应 config.json 中 sdk-session-lifecycle-telemetry 规则(severity: high,scope 限定在 clines-core/** 与 runtime/**):会话开始/结束/状态迁移的新代码路径必须调用 captureTaskCreated、captureTaskCompleted、captureConversationTurnEvent、captureTokenUsage 等类型化助手,禁止内联裸的 telemetry.capture()——类型化助手保证了 payload 形状的一致性。
user.extension_activated 的"每进程一次"约束在 CLI 侧有源码印证:apps/cli/src/utils/telemetry.ts 中 captureCliExtensionActivated 内部通过 wasActivationCaptured() / markActivationCaptured()(来自 telemetry.activation-gate)做记忆化去重,注释明确写着"only the first call emits",并且它走普通 capture 路径,因此尊重用户的遥测 opt-out 设置。
5. task.completed 的语义:锚定在"助手声明完成"的时刻
这是 rules.md 中最容易被误解的一条。task.completed 标记的是助手声明任务完成的时刻,而不是 SDK 会话记录被最终落盘的时刻:
- 本地 runtime 在观察到一次成功的
submit_and_exit工具调用时发射该事件(submit_and_exit是原 Clineattempt_completion工具在 SDK 中的对应物),此时source为"submit_and_exit"; - 对于没有调用显式完成工具就结束的非交互式运行,
shutdownSession作为兜底发射,source为"shutdown"; - 每个会话保证至多发射一次
task.completed;source字段("submit_and_exit" | "shutdown")是分析归因(analytics attribution)的必备字段。
这一设计意图在 sdk/ARCHITECTURE.md 中也有对应记录(files.json 将其标注为"completion semantics (submit_and_exit anchoring)"的 ground truth)。
6. CLI 目录顺序规则:先定目录,再发激活事件
CLI 接受 --config <dir> 参数。规则要求:必须在调用 captureCliExtensionActivated() 之前先执行 @cline/shared/storage 的 setClineDir(...) 和 setHomeDir(...),否则遥测单例持久化的 distinct-id 及其他磁盘上的遥测状态会落在 ~/.cline 下,而不是用户指定的 config 目录。
标准模式见 apps/cli/src/main.ts 的 runCli():
const configDir = resolveConfigDirArg(cliArgs);
const { setClineDir, setHomeDir } = await import("@cline/shared/storage");
if (configDir) {
setClineDir(configDir);
}
setHomeDir(homedir());
// Capture activation telemetry only after config/home directory selection
// has been applied, so the telemetry singleton's persisted distinct-id
// (and any other storage it touches) lands under the user-selected
// `--config <dir>` rather than the default home/config location.
captureCliExtensionActivated();
源码里有一个值得展开的细节:--config 采用两遍扫描策略。resolveConfigDirArg()(main.ts)在 commander 解析之前就快速扫描 process.argv 提取 config 目录(同时兼容 --config <dir> 与 --config=<dir> 两种写法),因为 setClineDir() 必须先于任何读取 home/config 目录的代码运行。此外,auth 子命令的 action 中还会防御性地再次 setClineDir(opts.config.trim())(main.ts),确保 commander 解析出的 opts.config(包含 --config=<dir> 形式)在任何基于 ~/.cline 构造 provider settings manager 之前始终生效。
7. Hub 守护进程遥测:谁宿主运行时,谁就拥有自己的遥测
分离运行的 hub 守护进程(sdk/packages/core/src/hub/daemon/entry.ts)宿主着 LocalRuntimeHost,为每个 hub 支撑的会话发射 task.conversation_turn 和 task.tokens——因此守护进程必须拥有自己的 ITelemetryService。它通过 createHubDaemonTelemetry()(sdk/packages/core/src/hub/daemon/telemetry.ts)构建,该实现有三个关键设计点,均可在源码中核实:
- 身份周期性重解析:守护进程经常在用户登录前启动(甚至跨账号切换存活),因此缓存的 cline 账号身份以
IDENTITY_REFRESH_INTERVAL_MS = 5 * 60 * 1000(5 分钟)的间隔重解析,而不是只在启动时解析一次;当accountId:organizationId组合键变化时才重新identifyAccount,保证长生命周期守护进程的 org 归因是最新的。 - 全退出路径 flush:
dispose()在每次关闭路径上都会执行 flush,包括启动失败路径;且 flush 与DISPOSE_FLUSH_TIMEOUT_MS = 5_000的硬截止赛跑(Promise.race),防止挂死的 exporter 拖住一个已经崩溃的守护进程(它持有 hub 端口)。 - 可区分的元数据:
cline_type取"hub"而非"cli",platform取"cline-hub-daemon"——因为守护进程宿主的会话可能由 CLI、桌面应用或 connector 触发,其事件必须与 CLI 进程自身的事件区分开。
rules.md 对此给出了明确的变更警报:任何移除这段接线、在守护进程内构造运行时宿主却不传入其遥测句柄、或新增跳过 flush 的退出路径的改动,都会导致 hub 支撑的会话静默丢失生命周期遥测("this exact bug shipped once"——这个 bug 确实上线过一次)。
8. 认证生命周期完整性:四个阶段,一个都不能少
sdk/packages/core/src/auth/ 下的每个认证 provider 都必须使用类型化助手发射全部四个认证生命周期事件(对应 config.json 中 sdk-auth-telemetry-completeness 规则,severity: high):
| 阶段 | 助手 | 触发位置 |
|---|---|---|
| 流程入口 | captureAuthStarted(provider) |
OAuth 流程函数顶部 |
| 令牌成功 | captureAuthSucceeded(provider) + identifyAccount(...) |
令牌交换成功之后 |
| 令牌错误 | captureAuthFailed(provider, errorMessage) |
catch 块中 |
| 令牌失效 | captureAuthLoggedOut(provider, reason) |
invalid_grant 或显式登出时 |
这四个助手确实存在于 core-events.ts(captureAuthStarted、captureAuthSucceeded、captureAuthFailed、captureAuthLoggedOut),identifyAccount 也在同文件(L368)。文档指定以 sdk/packages/core/src/auth/cline.ts 和 sdk/packages/core/src/auth/codex.ts 作为四个阶段齐全的标准范例:新增一个认证流程文件而缺少其中任何一个阶段时,PR 会被标记。
9. 每个 host 只有一个遥测服务
- VS Code:所有调用方都经过 apps/vscode/src/services/telemetry/index.ts 中的惰性
telemetryService代理,首次使用时构造一次。不允许各个 controller 自行构造ITelemetryService——那会割裂 distinct-id 状态、opt-out 跟踪和 flush 归属。 - CLI:同样的模式通过 apps/cli/src/utils/telemetry.ts 的
getCliTelemetryService()单例实现。首次调用时以cline_type: "cli"的元数据创建配置(createClineTelemetryServiceConfig+createConfiguredTelemetryHandle),通过registerDisposable注册disposeCliTelemetryService作为进程退出时的清理钩子;若事后传入 logger,则追加TelemetryLoggerSink适配器。
这与第 2 节中 TelemetryService.buildAttributes() 将 distinct_id 与 device_id 合并进每个事件的机制形成闭环:多实例意味着同一进程内会出现多个 distinct-id 与多个 flush 责任方,激活漏斗的归因随即失真。
10. 误报豁免:哪些情况不算违规
rules.md 专门列出三类 Greptile 可能误报的场景,出现时不视为违规:
- host 专属助手包装内部类型化调用:例如
captureCliExtensionActivated包装captureExtensionActivated——内层助手才是那个"类型化调用",外层只是 host 适配。第 6 节的 CLI 激活正是这种模式。 enterprise.*事件:从 apps/cli/src/utils/enterprise.ts 发射的enterprise.*事件是 enterprise 侧事件,尚未进入CORE_TELEMETRY_EVENTS,被单独跟踪。- 测试断言中的裸事件名字符串:新测试文件在
expect(...)断言中使用原始事件名是允许的——测试需要引用字符串来断言"到底发射了什么"。
11. 结构化规则总览与落地方式
config.json 将上述纪律固化为 4 条可执行规则,配合 strictness: 2、triggerOnUpdates: true、statusCheck: true(即更新时自动触发、并在 PR 状态检查中报告):
| 规则 ID | 要求 | Scope | 严重级 |
|---|---|---|---|
sdk-tool-handler-telemetry |
新增会执行用户可见动作(写文件、执行命令、改状态、调外部 API)的工具处理器必须调用 captureToolUsage() 或发射 task.tool_used;纯只读 helper 和 getter 豁免;拿不准时"优先加埋点" |
sdk/packages/agents/src/**、sdk/packages/core/src/** |
high |
sdk-session-lifecycle-telemetry |
会话开始/结束/状态迁移路径必须使用类型化助手,禁止内联裸 telemetry.capture() |
sdk/packages/core/src/cline-core/**、sdk/packages/core/src/runtime/** |
high |
sdk-no-raw-event-strings |
事件名字符串必须来自 CORE_TELEMETRY_EVENTS;新事件先加目录、再建助手 |
core / agents / cli / vscode 四个包 | medium |
sdk-auth-telemetry-completeness |
新 OAuth/认证 provider 必须齐备四个生命周期事件 | sdk/packages/core/src/auth/** |
high |
captureToolUsage 确实存在于 core-events.ts,captureTaskCreated 在 L394——规则中引用的每个助手都是目录中已实现的函数,而非规划中的 API。
这套配置的实质效果是:rules.md 提供"为什么"(设计意图、踩过的坑、职责归属),config.json 提供"查什么"(可执行的模式匹配与范围限定),files.json 提供"对照什么"(评审遥测变更时的权威文件清单)。三者叠加,让遥测这类"看起来无害、实际上容易漂移"的横切关注点,在 PR 层面获得与业务代码同级的约束力。对于在其他项目中搭建类似 AI 辅助代码审查规则(如 .greptile/ 或同类 PR 审查配置)的开发者,这个"结构化规则 + 解释性文档 + 事实源清单"的三段式组织方式本身就是一个值得借鉴的模板。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00