首页
/ Cline SDK 遥测规范:Greptile 规则驱动的 OpenTelemetry 事件治理实践

Cline SDK 遥测规范:Greptile 规则驱动的 OpenTelemetry 事件治理实践

2026-09-06 12:49:13作者:农烁颖Land

本文以 Cline 仓库中 .greptile/rules.md 这份遥测标准文档为主体,完整讲解 Cline SDK 的 OpenTelemetry 事件体系:事件唯一事实源(CORE_TELEMETRY_EVENTS)与类型化 capture*() 助手的用法、激活漏斗(activation funnel)与各事件的发射职责归属、task.completed 的语义锚定、CLI 目录顺序约束、Hub 守护进程遥测与认证生命周期完整性要求。读完本文,你将掌握在 Cline 代码库中新增一个合规遥测事件的完整流程,并理解这些规则如何用 Greptile 配置自动化的方式在 PR 层面强制执行。

1. 规则文件在仓库中的位置与组成

.greptile/ 目录包含三个协作文件:

需要特别说明的一点(原文档明确声明):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)

从源码结构看,这条链路是真实存在的:

  1. 事件目录 + 类型化助手core-events.ts(857 行)导出 CORE_TELEMETRY_EVENTS 冻结常量对象和所有 capture*() 助手函数。
  2. 接口契约sdk/packages/shared/src/services/telemetry.ts 定义 ITelemetryService 接口(capturecaptureRequiredrecordCounterrecordHistogramrecordGaugeflushdispose),并在其中声明了 OTLP 配置项——otlpEndpointotlpProtocol(当前 SDK 支持有限制,为 "http/json")、按信号拆分的 otlpMetricsEndpoint / otlpLogsEndpoint / otlpTracesEndpoint 及各自的 otlpHeaders
  3. 多适配器扇出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 → 调用方 propertiesmetadatadistinct_id(若已设置)→ 最后固定覆盖 device_id(默认由 resolveCoreDeviceId() 生成的确定性设备标识)。这解释了为什么 rules.md 强调"每个 host 只允许一个遥测服务"——distinct-id 状态、opt-out 跟踪和 flush 归属都绑在这个单例上。

3. 事件唯一事实源与新增事件的三步流程

CORE_TELEMETRY_EVENTS 是全部事件名的唯一事实源。rules.md 列举的核心分组为 CLIENTSESSIONUSERTASKHOOKSWORKSPACE;对照 core-events.ts 当前源码,目录实际还包含 AGENTSDKFEATURE_FLAGS 三个分组。各分组的代表性事件:

分组 事件常量 事件名
CLIENT EXTENSION_ACTIVATED user.extension_activated
SESSION STARTED / ENDED session.started / session.ended
USER AUTH_STARTEDTELEMETRY_OPT_OUT user.auth_starteduser.auth_succeededuser.auth_faileduser.auth_logged_outuser.opt_out
TASK CREATEDCOMPLETEDCONVERSATION_TURNTOKEN_USAGETOOL_USED task.createdtask.completedtask.conversation_turntask.tokenstask.tool_used
WORKSPACE INITIALIZED / INIT_ERROR / PATH_RESOLVED workspace.initializedworkspace.init_errorworkspace.path_resolved
HOOKS DISCOVERY_COMPLETED hooks.discovery_completed
SDK ERRORTOOL_TIMEOUT sdk.tool_timeoutsdk.plan_mode_command_blocked

核心纪律:调用点永远不要使用原始字符串字面量作为事件名。 新增事件必须走三步流程:

  1. CORE_TELEMETRY_EVENTS 中添加常量;
  2. 在其旁新增类型化 capture*() 助手(properties 参数带类型);
  3. 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_activatedtask.conversation_turn)和属性接口(如 WorkspaceInitializedPropertiesroot_countvcs_typesinit_duration_ms)中得到印证。

配套的结构化规则(config.jsonsdk-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.jsonsdk-session-lifecycle-telemetry 规则(severity: high,scope 限定在 clines-core/**runtime/**):会话开始/结束/状态迁移的新代码路径必须调用 captureTaskCreatedcaptureTaskCompletedcaptureConversationTurnEventcaptureTokenUsage 等类型化助手,禁止内联裸的 telemetry.capture()——类型化助手保证了 payload 形状的一致性。

user.extension_activated 的"每进程一次"约束在 CLI 侧有源码印证:apps/cli/src/utils/telemetry.tscaptureCliExtensionActivated 内部通过 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 是原 Cline attempt_completion 工具在 SDK 中的对应物),此时 source"submit_and_exit"
  • 对于没有调用显式完成工具就结束的非交互式运行,shutdownSession 作为兜底发射,source"shutdown"
  • 每个会话保证至多发射一次 task.completedsource 字段("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/storagesetClineDir(...)setHomeDir(...),否则遥测单例持久化的 distinct-id 及其他磁盘上的遥测状态会落在 ~/.cline 下,而不是用户指定的 config 目录。

标准模式见 apps/cli/src/main.tsrunCli()

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_turntask.tokens——因此守护进程必须拥有自己的 ITelemetryService。它通过 createHubDaemonTelemetry()sdk/packages/core/src/hub/daemon/telemetry.ts)构建,该实现有三个关键设计点,均可在源码中核实:

  1. 身份周期性重解析:守护进程经常在用户登录前启动(甚至跨账号切换存活),因此缓存的 cline 账号身份以 IDENTITY_REFRESH_INTERVAL_MS = 5 * 60 * 1000(5 分钟)的间隔重解析,而不是只在启动时解析一次;当 accountId:organizationId 组合键变化时才重新 identifyAccount,保证长生命周期守护进程的 org 归因是最新的。
  2. 全退出路径 flushdispose() 在每次关闭路径上都会执行 flush,包括启动失败路径;且 flush 与 DISPOSE_FLUSH_TIMEOUT_MS = 5_000 的硬截止赛跑(Promise.race),防止挂死的 exporter 拖住一个已经崩溃的守护进程(它持有 hub 端口)。
  3. 可区分的元数据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.jsonsdk-auth-telemetry-completeness 规则,severity: high):

阶段 助手 触发位置
流程入口 captureAuthStarted(provider) OAuth 流程函数顶部
令牌成功 captureAuthSucceeded(provider) + identifyAccount(...) 令牌交换成功之后
令牌错误 captureAuthFailed(provider, errorMessage) catch 块中
令牌失效 captureAuthLoggedOut(provider, reason) invalid_grant 或显式登出时

这四个助手确实存在于 core-events.tscaptureAuthStartedcaptureAuthSucceededcaptureAuthFailedcaptureAuthLoggedOut),identifyAccount 也在同文件(L368)。文档指定以 sdk/packages/core/src/auth/cline.tssdk/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.tsgetCliTelemetryService() 单例实现。首次调用时以 cline_type: "cli" 的元数据创建配置(createClineTelemetryServiceConfig + createConfiguredTelemetryHandle),通过 registerDisposable 注册 disposeCliTelemetryService 作为进程退出时的清理钩子;若事后传入 logger,则追加 TelemetryLoggerSink 适配器。

这与第 2 节中 TelemetryService.buildAttributes()distinct_iddevice_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: 2triggerOnUpdates: truestatusCheck: 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.tscaptureTaskCreatedL394——规则中引用的每个助手都是目录中已实现的函数,而非规划中的 API。

这套配置的实质效果是:rules.md 提供"为什么"(设计意图、踩过的坑、职责归属),config.json 提供"查什么"(可执行的模式匹配与范围限定),files.json 提供"对照什么"(评审遥测变更时的权威文件清单)。三者叠加,让遥测这类"看起来无害、实际上容易漂移"的横切关注点,在 PR 层面获得与业务代码同级的约束力。对于在其他项目中搭建类似 AI 辅助代码审查规则(如 .greptile/ 或同类 PR 审查配置)的开发者,这个"结构化规则 + 解释性文档 + 事实源清单"的三段式组织方式本身就是一个值得借鉴的模板。

登录后查看全文
热门项目推荐
相关项目推荐