Cline Core CLI Agent:基于 ClineCore 有状态会话构建交互式终端编码 Agent 的完整实战
本文为 Cline 仓库中 cline-core-cli-agent 示例的配套技术指南。它演示了如何脱离无状态 Agent 类、直接使用 ClineCore 运行时构建一个带会话持久化、内置工具与工具审批能力的交互式终端聊天 Agent,读者读完后可掌握 ClineCore.create() / start() / send() / stop() / dispose() 的完整生命周期、toolPolicies 审批策略配置,以及 agent_event 事件流的订阅与流式渲染方法。
1. 示例定位:为什么选择 ClineCore 而不是轻量运行时
该示例的 README 开篇即说明了它在示例体系中的定位:
An interactive terminal chat agent powered by the
ClineCoreruntime. This example is similar in spirit tocli-agent, but uses stateful ClineCore sessions and built-in runtime tools instead of the statelessAgentclass, to leverage Cline's internal agent harness.
即:它与 cli-agent 示例 目标相似(都是交互式终端聊天 Agent),但两者在运行时选型上完全不同:
| 维度 | cli-agent / quickstart | cline-core-cli-agent(本示例) |
|---|---|---|
| 运行时 | 无状态 Agent 类 |
有状态 ClineCore 运行时 |
| 会话 | 单轮、无状态 | 单 sessionId 承载多轮对话 |
| 工具 | 需自行定义 | 直接使用 Cline 内置运行时工具 |
| 持久化 | 无 | 具备 sessions 持久化能力 |
| 适用场景 | 最小 SDK 示例 | 需要完整 ClineCore 运行时(会话、持久化、内置工具)的场景 |
README 的 Notes 一节给出了明确的选型建议:当你想要"full ClineCore runtime with sessions, persistence, and built-in tools"时使用本示例;追求最小 SDK 示例时看 quickstart;追求轻量无状态运行时时看 cli-agent。
2. 环境准备与快速上手
2.1 前置依赖
从 package.json 可以看到该示例的技术栈约束:
{
"name": "@cline/example-cline-core-cli-agent",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"build:sdk": "bun run --cwd ../../.. build:sdk",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@cline/sdk": "workspace:*"
},
"engines": {
"node": ">=22"
}
}
关键事实:
- 依赖的是 monorepo 内部工作区包
@cline/sdk(workspace:*),因此必须在 Cline 仓库根目录下通过 workspace 安装; build:sdk脚本通过--cwd ../../..委托到仓库根目录执行 SDK 构建,说明示例依赖本地源码构建产物而非已发布的 npm 包;- 运行时要求 Node.js
>=22(engines字段),开发期使用 Bun 直接执行 TypeScript。
2.2 安装与配置
按照 README 的步骤:
# 1. 安装依赖并构建本地 SDK
bun install
bun run build:sdk
# 2. 设置 API Key(Cline 网关密钥)
export CLINE_API_KEY="sk_..."
# 3. 启动交互式 Agent
bun dev
启动后终端会打印运行元信息(对应 src/index.ts 第 159–162 行):
ClineCore CLI Agent (type 'exit' to quit)
Provider: cline
Model: anthropic/claude-sonnet-4.6
CWD: /path/to/current/dir
在 you: 提示符输入任意消息即可看到流式响应,输入 exit 退出。
2.3 可选的模型配置
README 指出示例默认使用 Cline 网关提供商与 Claude Sonnet,可通过环境变量覆盖:
export CLINE_PROVIDER_ID="cline"
export CLINE_MODEL_ID="anthropic/claude-sonnet-4.6"
源码中这三个环境变量的解析逻辑位于 src/index.ts 第 8–13 行,并附带一条重要的设计注释:
// ClineCore does not choose a model automatically; each session config must provide one.
// These example defaults use the Cline gateway with Claude Sonnet, and can be overridden with env vars.
const providerId = process.env.CLINE_PROVIDER_ID ?? "cline";
const modelId = process.env.CLINE_MODEL_ID ?? "anthropic/claude-sonnet-4.6";
const apiKey = process.env.CLINE_API_KEY;
const cwd = process.cwd();
这里传递了一个关键约束:ClineCore 不会自动选择模型,每个 session 的 config 必须显式提供 providerId 和 modelId。环境变量只是便捷入口,真正生效的是它们被透传进 start() 的会话配置(见第 4 节参数表)。
3. 运行时生命周期:从 create 到 dispose
README "What it does" 一节概括了六项核心行为,源码逐条印证了它们。整个生命周期可归纳为五步:
3.1 惰性创建 ClineCore 实例
async function ensureCline(): Promise<ClineCore> {
if (cline) {
return cline;
}
cline = await ClineCore.create({
clientName: "cline-core-cli-agent",
backendMode: "local",
capabilities: {
requestToolApproval,
},
});
unsubscribe = cline.subscribe((event) => {
if (event.type === "agent_event") {
handleAgentEvent(event.payload.event);
}
});
return cline;
}
三个要点:
ClineCore.create()是ClineCore类(定义于 sdk/packages/core/src/ClineCore.ts)的静态工厂方法,该类是 "The primary entry point for the Cline Core SDK";backendMode: "local"表明运行时进程本地启动(而非连接远端 hub);capabilities.requestToolApproval把"工具执行审批"这一宿主能力注入运行时——这是ClineCoreOptions能力体系的一部分,ToolApprovalRequest类型定义在 sdk/packages/shared/src/agents/types.ts 中;- 通过
cline.subscribe()订阅CoreSessionEvent,并只过滤type === "agent_event"的事件进入本地渲染器(对应 README "Concepts demonstrated" 中 "CoreSessionEventsubscription viacline.subscribe()")。
3.2 启动会话:start() 与完整会话配置
async function startSession(): Promise<string> {
const runtime = await ensureCline();
const result = await runtime.start({
source: "cli",
interactive: true,
config: {
providerId,
modelId,
apiKey,
cwd,
workspaceRoot: cwd,
mode: "act",
systemPrompt,
maxIterations: 10,
enableTools: true,
enableSpawnAgent: false,
enableAgentTeams: false,
disableMcpSettingsTools: true,
},
toolPolicies: {
"*": { autoApprove: false },
read_files: { autoApprove: true },
search_codebase: { autoApprove: true },
},
});
return result.sessionId;
}
start() 返回 result.sessionId,整个多轮对话就围绕这一个 sessionId 展开(README 所称 "Multi-turn conversation using a single sessionId")。各会话配置参数含义如下:
| 参数 | 示例取值 | 说明 |
|---|---|---|
providerId |
cline |
模型提供方,默认走 Cline 网关,可经 CLINE_PROVIDER_ID 覆盖 |
modelId |
anthropic/claude-sonnet-4.6 |
具体模型 ID,可经 CLINE_MODEL_ID 覆盖 |
apiKey |
$CLINE_API_KEY |
网关鉴权密钥 |
cwd / workspaceRoot |
process.cwd() |
Agent 工作目录与工作区根,内置工具的文件操作以此为基准 |
mode |
"act" |
运行模式为执行态(非纯规划态) |
systemPrompt |
见下文 | 系统提示词,约束行为风格 |
maxIterations |
10 |
单轮内 Agent 循环迭代上限,防止工具调用无限循环 |
enableTools |
true |
启用 Cline 内置运行时工具 |
enableSpawnAgent / enableAgentTeams |
false |
禁用子 Agent 派生与多 Agent 团队,保持单 Agent 行为 |
disableMcpSettingsTools |
true |
禁用 MCP 设置类工具,收窄 CLI 场景的能力面 |
source: "cli" 与 interactive: true 用于标识会话来源与交互属性。系统提示词同样刻意保持克制:
const systemPrompt = `You are a helpful assistant in an interactive terminal chat.
Be concise. You can use built-in tools to inspect files, search the workspace, and run shell commands when helpful.`;
3.3 逐轮发送:send()
async function runTurn(input: string): Promise<void> {
activeSessionId ??= await startSession();
hasPrintedAssistantPrefix = false;
const runtime = await ensureCline();
await runtime.send({
sessionId: activeSessionId,
prompt: input,
});
console.log();
}
注意 activeSessionId ??= await startSession() 的惰性初始化:首轮输入才创建会话,而非进程启动即建会话;后续轮次复用同一 sessionId,由 ClineCore 维护上下文状态。
3.4 优雅停机:stop() 与 dispose()
} finally {
rl.close();
unsubscribe?.();
if (activeSessionId && cline) {
await cline.stop(activeSessionId).catch(() => undefined);
}
await cline?.dispose();
console.log("Goodbye!");
}
finally 块保证无论正常 exit 还是异常退出都完成清理:关闭 readline 接口、取消事件订阅、调用 cline.stop(sessionId) 结束会话(用 .catch(() => undefined) 容忍会话尚未建立或已失效的情况)、最后 cline.dispose() 释放运行时资源。这正是 README 所列 "Calls cline.stop() and cline.dispose() during shutdown" 的实现。
4. 工具审批:toolPolicies 静态策略 + requestToolApproval 动态兜底
README "Concepts demonstrated" 中有一条值得展开:"Basic tool policies: file reads/search are auto-approved, other tools request approval"。该示例实际上演示了两层审批机制:
第一层:start() 中的 toolPolicies 声明式策略
toolPolicies: {
"*": { autoApprove: false }, // 默认所有工具都需审批
read_files: { autoApprove: true }, // 文件读取自动放行
search_codebase: { autoApprove: true }, // 代码库检索自动放行
},
通配符 * 作为默认策略兜底,具体工具名做白名单式放行——只读类操作(read_files、search_codebase)低风险自动执行,写文件、执行命令等高风险工具落入第二层。
第二层:宿主回调 requestToolApproval
async function requestToolApproval(request: ToolApprovalRequest) {
console.log(`\n[approval] ${request.toolName} wants to run:`);
console.log(formatToolValue(request.input));
const answer = await ask("Approve? [y/N] ");
const approved = answer.trim().toLowerCase() === "y";
return {
approved,
...(approved ? {} : { reason: "User denied tool execution" }),
};
}
未被自动审批的工具会触发该回调:打印工具名与输入参数、等待用户 y/N 应答,拒绝时附带 reason 说明。这个回调即 ClineCore.create() 时通过 capabilities 注入的宿主能力,体现了 Cline SDK 中"运行时发起、宿主决策"的权限模型。
内置工具方面,README 列举了 read_files、search_codebase、run_commands 等,源码注释也印证了"Uses ClineCore's built-in tools instead of defining custom tools"——示例没有注册任何自定义工具,全部依赖 Cline 的 internal agent harness。
5. 事件流渲染:agent_event 的四种内容类型
流式输出的核心是 handleAgentEvent,它按 AgentEvent 的 type 分派(src/index.ts 第 36–72 行):
function handleAgentEvent(event: AgentEvent): void {
switch (event.type) {
case "content_start":
if (event.contentType === "text" && event.text) {
printAssistantPrefix();
process.stdout.write(event.text); // 文本增量直接写 stdout
}
if (event.contentType === "tool" && event.toolName) {
console.log(`\n[tool] ${event.toolName}(${JSON.stringify(event.input ?? {})})`);
}
break;
case "content_update":
if (event.contentType === "tool" && event.toolName) {
console.log(`[update] ${event.toolName}: ${formatToolValue(event.update)}`);
}
break;
case "content_end":
if (event.contentType === "tool" && event.toolName) {
if (event.error) {
console.log(`[error] ${event.toolName}: ${event.error}`);
} else {
console.log(`[result] ${formatToolValue(event.output)}`);
}
}
break;
case "notice":
console.log(`\n[notice] ${event.message}`);
break;
case "error":
console.error(`\n[error] ${event.error.message}`);
break;
}
}
事件语义可以归纳为一张表:
| 事件 type | contentType | 渲染行为 |
|---|---|---|
content_start |
text |
打印一次 agent: 前缀,随后流式写出文本增量 |
content_start |
tool |
打印 [tool] 工具名(输入参数),标记工具调用开始 |
content_update |
tool |
打印 [update] 工具执行中的增量信息 |
content_end |
tool |
成功打印 [result],失败打印 [error] |
notice |
- | 打印运行时提示信息 |
error |
- | 以 stderr 打印错误信息 |
两个工程细节值得一提:
- 前缀去重:
printAssistantPrefix()用hasPrintedAssistantPrefix标志保证每个回答只打印一次agent:前缀;runTurn每轮开始时将其重置,对应 README "Streamsagent_eventtext to stdout" 的行为; - 长输出截断:
formatToolValue()对非字符串值做JSON.stringify后限制 200 字符(超出部分以...截断),避免工具结果把终端刷屏。
6. 交互式输入循环
外层是一个基于 node:readline 的极简 REPL(第 94–103、116–176 行):
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
function ask(question: string): Promise<string> {
return new Promise((resolve) => { rl.question(question, resolve); });
}
主循环处理三种输入:exit(忽略大小写)触发退出流程进入 finally 清理;空输入直接 continue 等待下一轮;其余内容 trim 后交给 runTurn 发送。整个循环把"用户输入 → ClineCore 会话 → 事件流 → 终端渲染"串成一条清晰的单向数据流,没有任何多余的状态管理。
7. 小结:本示例的实战价值
这个约 185 行的 index.ts 是理解 ClineCore 编程模型的最小完整样本,它覆盖了:
- 运行时创建:
ClineCore.create()的backendMode与capabilities注入方式; - 有状态会话:
start()显式模型配置、单一sessionId多轮复用、stop()/dispose()资源清理; - 工具审批双层模型:
toolPolicies声明式自动审批 +requestToolApproval宿主回调兜底; - 事件驱动渲染:
subscribe()订阅CoreSessionEvent,按agent_event的 content 生命周期(start/update/end)流式输出文本与工具轨迹。
如果你需要一个能直接运行、带完整会话与内置工具能力的终端编码 Agent 起点,可以直接参考本示例的目录结构(package.json + tsconfig.json + src/index.ts)与依赖声明,在其基础上扩展工具策略、审批策略或事件消费逻辑。
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 StartedRust0624
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