Cline 事件驱动自动化本地验证:详解 local-manual-test 事件规范与 cron.event.ingest 接入链路
本篇技术文章以 Cline SDK 的本地手动事件测试规范 local-manual-test.event.md 为主体,逐字段解析这份事件驱动(event-driven)自动化规格的全部配置项,并结合 CronEventIngress 源码与核心测试用例,讲清一个事件从 ingestEvent 注入到匹配规范、去重节流、入队执行的完整链路。读完本文,你可以在不依赖任何外部服务(GitHub、Webhook 等)的前提下,完成一次事件驱动自动化的端到端本地冒烟验证。
一、规范本体:local-manual-test.event.md 是什么
事件驱动规范是 Cline 自动化体系中的第二类 spec(第一类是按 cron 表达式周期执行的 .cron.md)。它由 YAML frontmatter(声明触发条件与运行参数)+ 正文(作为该次运行的任务提示词)组成。sdk/examples/cron/README.md 将 local-manual-test 定位为:"Local test spec for verifying event-driven automation without external services"——即一个零外部依赖的本地事件测试模板。
完整规范内容如下(原文件仅 28 行,此处完整继承并逐项展开):
---
id: local-manual-test
title: Local Manual Event Test
workspaceRoot: /absolute/path/to/repo
cwd: /absolute/path/to/repo
event: local.manual_test
filters:
topic: cron-feature-2
debounceSeconds: 0
dedupeWindowSeconds: 60
cooldownSeconds: 0
maxParallel: 1
mode: act
enabled: true
modelSelection:
providerId: cline
modelId: anthropic/claude-opus-4.7
timeoutSeconds: 300
maxIterations: 5
tags:
- automation
- local-test
metadata:
owner: platform
source: local-smoke-test
---
Use the normalized trigger event context to confirm event-driven automation is
working locally. Summarize the event id, subject, topic, and payload message.
这份规范的关键设计点:
event: local.manual_test是一个纯本地事件类型,不绑定任何外部事件源;filters.topic: cron-feature-2要求注入事件时 payload/attributes 中带有topic: "cron-feature-2"才会命中;debounceSeconds: 0表示事件到达即触发,不做合并等待;- 正文提示词明确要求 Agent 输出 event id、subject、topic、payload message——这正好构成了"事件上下文是否被规范化注入到会话"的验收断言。
二、字段逐项解析:每个参数控制什么
结合 README.md 的 Field Reference 与 cron-event-ingress.ts 的实现,各字段语义如下:
| 字段 | 本规范取值 | 含义与行为 |
|---|---|---|
id |
local-manual-test |
规范唯一标识,字母数字与连字符;用于去重键、报告与查询 |
title |
Local Manual Event Test |
人类可读标题,出现在运行记录中 |
workspaceRoot |
绝对路径占位符 | 运行会话挂载的项目根目录,复制模板后必须改成本地仓库路径 |
cwd |
同 workspaceRoot |
会话工作目录 |
event |
local.manual_test |
必填。事件类型,只有该类型的归一化事件才会进入本规范的候选集 |
filters |
{ topic: cron-feature-2 } |
可选过滤条件,支持点路径;逐项与事件的 attributes → payload → 信封字段做匹配 |
debounceSeconds |
0 |
合并窗口:窗口内的后续同键事件不新建 run,而是把已入队 run 的 scheduledFor 后移(见下文源码);0 表示立即触发 |
dedupeWindowSeconds |
60 |
去重窗口:同一 dedupe key 在 60 秒内只触发一次,窗口内的后续事件被抑制 |
cooldownSeconds |
0 |
运行级冷却:距该规范上一次运行不足 N 秒则抑制;0 表示不冷却 |
maxParallel |
1 |
该规范最大并发 run 数,1 即串行 |
mode |
act |
运行模式:act(可执行工具)/ plan(只规划)/ yolo(默认) |
enabled |
true |
开关;禁用后对账(reconcile)阶段跳过该规范 |
modelSelection |
cline 提供商 + anthropic/claude-opus-4.7 |
覆盖本次运行的模型/提供商,不写则用全局默认 |
timeoutSeconds |
300 |
单次运行 5 分钟超时 |
maxIterations |
5 |
迭代上限——测试规范故意设小,保证冒烟失败时快速收敛 |
tags / metadata |
automation、local-test;owner: platform、source: local-smoke-test |
分组标签与自由元数据,不影响触发逻辑 |
其中值得注意的工程取舍是:dedupeWindowSeconds: 60 与 debounceSeconds: 0 组合——事件到达立即入队,但 60 秒内重复事件(相同 dedupe key)会被丢弃。这意味着连发 10 次测试事件只会跑一次,验证"即时触发"的同时也验证了去重逻辑。
2.1 dedupe key 从哪里来
cron-event-ingress.ts 的 normalizeEvent 中,若事件未显式提供 dedupeKey,引擎会按以下规则派生:
const dedupeKey =
trimOrUndefined(event.dedupeKey) ??
`${eventType}:${source}:${subject ?? eventId}`;
即 local.manual_test:local:manual smoke test 这类形式。因此同一 subject 的重复事件自然落进同一个去重窗口,这正是 dedupeWindowSeconds: 60 能生效的前提。
2.2 filters 如何匹配
resolveFilterValue(cron-event-ingress.ts#L113-L144)按优先级解析过滤键:先查 event.attributes 的直接键,再查 event.payload 的直接键,然后按点路径依次在"事件信封合成对象"、attributes、payload 中逐级下钻。matchesExpected 支持数组"任意命中"与对象"逐键全等"的递归匹配。对本规范而言,注入事件只要在 attributes(或 payload)中带 topic: "cron-feature-2",filters 即命中;否则被记录为 filter_mismatch 抑制项。
三、本地跑通:从零到触发一次运行
以下操作步骤完整继承自 README.md 中针对 local-manual-test 的 Usage 章节。
第 1 步:复制并修改规范
mkdir -p ~/.cline/cron/events
cp sdk/examples/cron/events/local-manual-test.event.md ~/.cline/cron/events/
编辑复制后的文件,把 workspaceRoot / cwd 两个占位符 /absolute/path/to/repo 改成本地仓库的绝对路径,需要时调整 modelSelection。规范放在 .cline/cron/events/ 下会被 hub 或 SDK 在启动对账时拾取。
第 2 步:启用自动化
三种入口任选其一(README "Enable automation" 节):
- Hub 方式:
new HubWebSocketServer({ cronOptions: { workspaceRoot: "/absolute/workspace" } }); - SDK 方式:
ClineCore.create({ automation: true, ... }); - CLI 方式:
cline --enable-automation。
第 3 步:注入测试事件
README 给出的 Hub WebSocket 注入方式:
node -e "
const { HubWebSocketClient } = require('@cline/core');
const client = new HubWebSocketClient('ws://localhost:8000');
client.send('cron.event.ingest', {
eventType: 'local.manual_test',
envelope: { subject: 'test', topic: 'cron-feature-2', message: 'hello' }
});
"
cron.event.ingest 是 Hub 协议中的标准命令,定义在 sdk/packages/shared/src/hub.ts 的联合类型中(第 599 行附近)。事件来源除了这条 WebSocket 通道,README 还列出了:GitHub App / webhook 接收器、插件自发事件(参见 plugins/automation-events.ts)、Connector 适配器。
第 4 步:验收
- 事件到达后,Agent 会话以
act模式运行,最多 5 轮迭代、5 分钟超时; - 运行结束(或失败)后,报告写入
.cline/cron/reports/<run-id>.md,包含 YAML frontmatter(run ID、状态、耗时、token 用量)、工作摘要、工具调用与结果,事件触发型运行还会附带触发事件上下文; - 对照规范正文提示词验收:报告应能总结出注入事件的 id、subject(
test)、topic(cron-feature-2)与 payload 消息(hello)。
SDK 直接调用 ingestEvent 的方式在核心测试中有完整示例,见下文第五节。
四、引擎侧原理:一个事件如何变成一次运行
从源码结构看,事件处理由 CronEventIngress 承担,类注释明确其职责边界:"先持久化事件,再匹配规范并为匹配规范物化(materialize)queued runs;本层刻意不执行 Agent,执行归 runner 的认领循环所有"。ingestEvent 的完整管线为:
- 归一化(normalizeEvent):trim 各字段、校验
occurredAt的 ISO 格式(非法则回退为接收时刻)、补全dedupeKey、仅当payload/attributes是普通对象时才保留; - 持久化并判重:
store.insertEventLog落库。若该事件已存在,直接返回duplicate: true,抑制原因为duplicate_event,不再走匹配流程——这保证了事件日志的幂等; - 按事件类型取候选规范:
store.listEventSpecsForType(eventType)。对本规范而言,即取所有event: local.manual_test的规范; - 逐规范判定:
filters不匹配 → 抑制原因filter_mismatch;- 匹配后进入
materializeForSpec(L290-L356),依次执行三级节流:- debounce:
debounceSeconds > 0时查找该 spec + dedupe key 下已入队的 run,存在则把其scheduledFor推进到max(原值, receivedAt + debounce)并更新触发事件引用——即"拖尾合并",事件风暴收敛为一次延迟运行;本规范取 0,跳过此步,scheduledFor = receivedAt立即可认领; - dedupe window:
dedupeWindowSeconds: 60生效——若 60 秒内该 dedupe key 已有事件 run,抑制,原因dedupe_window; - cooldown:
cooldownSeconds: 0,不检查;
- debounce:
- 三级全过 →
store.enqueueRun({ triggerKind: "event", triggerEventId, ... })入队;
- 回写事件日志状态:
unmatched(无规范匹配)/queued(有 run 入队)/suppressed(全部被抑制)/failed(处理抛错),并记录匹配数、入队数、抑制数。
四个抑制原因枚举 duplicate_event | filter_mismatch | dedupe_window | cooldown(L23-L27)正是本地冒烟时最直接的观测点:连发重复事件应看到 dedupe_window,改错 topic 应看到 filter_mismatch,重复 eventId 应看到 duplicate_event——一次测试即可覆盖全部触发与抑制路径。
架构层面的完整描述可参考 sdk/ARCHITECTURE.md 的 automation 一节(其中指出事件经 cron.event.ingest 命令进入)。
五、测试佐证:ClineCore 如何验证该链路
sdk/packages/core/src/ClineCore.test.ts 中的用例 "exposes event automation through ClineCore instead of CronService" 与本文档规范一一对应:
// 写入与 local-manual-test 同构的规范(event: local.manual_test, filters.topic: cron-feature-2)
const core = await ClineCore.create({
automation: {
cronDir, // <root>/.cline/cron
reportsDir, // <root>/.cline/cron/reports
dbPath, // <root>/.cline/data/db/cron.db
autoStart: false,
pollIntervalMs: 10_000,
},
});
await core.automation.reconcileNow();
const result = core.automation.ingestEvent({
eventId: "evt_local_1",
eventType: "local.manual_test",
source: "local",
subject: "manual smoke test",
occurredAt: "2026-04-24T10:00:00.000Z",
attributes: { topic: "cron-feature-2" }, // 命中 filters.topic
});
expect(result.matchedSpecIds).toHaveLength(1);
expect(result.queuedRuns).toHaveLength(1);
断言要点:
ingestEvent同步返回matchedSpecIds与queuedRuns,且各为 1——filters 的topic从attributes解析命中;start()后,runner 认领 run 并真正发起会话,测试进一步断言host.runTurn的 prompt 包含"Trigger event:"(L784-L789),即触发事件上下文被规范化地注入了运行提示词——这正对应规范正文要求 Agent 总结的 "normalized trigger event context";- 目录布局约定:规范放
.cline/cron/events/,报告落.cline/cron/reports/,状态库在.cline/data/db/cron.db(SQLite),与 sqlite-cron-store 的持久化设计一致。
六、与相邻模板的对比与调参建议
| 维度 | local-manual-test | pr-review | local-plugin-event |
|---|---|---|---|
| 事件类型 | local.manual_test(手动注入) |
github.pull_request.opened(外部 webhook) |
local.plugin_event(插件自发) |
| 过滤条件 | topic: cron-feature-2 |
仓库/分支/labels 等 | topic: plugin-demo |
| 节流参数 | debounce 0 / dedupe 60 / cooldown 0 | debounce 30 / dedupe 600 / cooldown 120 | dedupe 5 / cooldown 5 |
| 并发 | maxParallel: 1 |
maxParallel: 2 |
maxParallel: 1 |
| 用途 | 本地冒烟,参数取最小/零值保证即时与可重复 | 生产 PR 评审,宽节流防打扰 | 验证插件事件管道 |
调参建议(基于源码语义):
- 调试阶段保持
debounceSeconds: 0;接入真实高频事件源(如 webhook 重试)时提高 debounce 让事件风暴收敛为一次延迟运行; dedupeWindowSeconds与cooldownSeconds的差别在于:前者按 dedupe key(事件级)去重,后者按 spec(规范级)冷却——同一规范下不同 subject 的事件也会受 cooldown 约束;- 验证多规范并发时可将
maxParallel提至 2 以上,对照pr-review的取法; - 冒烟场景保持
maxIterations: 5、timeoutSeconds: 300的小值,避免坏提示词消耗过多 token。
小结
local-manual-test.event.md 虽只有 28 行,却是一个自洽的事件驱动自动化验收用例:local.manual_test 事件类型 + topic 过滤保证零外部依赖且断言面清晰,debounce 0 / dedupe 60 / maxIterations 5 的组合兼顾即时性与快速收敛。配合 cron.event.ingest(Hub)或 core.automation.ingestEvent()(SDK)注入事件,再检查 .cline/cron/reports/<run-id>.md 中是否包含触发事件上下文,即完成一次覆盖"归一化 → 判重 → 过滤 → 节流 → 入队 → 执行 → 报告"全链路的本地验证。相关入口文档:sdk/examples/cron/README.md、sdk/ARCHITECTURE.md。
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 StartedRust0627
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