首页
/ Cline Core CLI Agent:基于 ClineCore 有状态会话构建交互式终端编码 Agent 的完整实战

Cline Core CLI Agent:基于 ClineCore 有状态会话构建交互式终端编码 Agent 的完整实战

2026-09-06 13:32:10作者:郦嵘贵Just

本文为 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 ClineCore runtime. This example is similar in spirit to cli-agent, but uses stateful ClineCore sessions and built-in runtime tools instead of the stateless Agent class, 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/sdkworkspace:*),因此必须在 Cline 仓库根目录下通过 workspace 安装;
  • build:sdk 脚本通过 --cwd ../../.. 委托到仓库根目录执行 SDK 构建,说明示例依赖本地源码构建产物而非已发布的 npm 包;
  • 运行时要求 Node.js >=22engines 字段),开发期使用 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 必须显式提供 providerIdmodelId。环境变量只是便捷入口,真正生效的是它们被透传进 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" 中 "CoreSessionEvent subscription via cline.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_filessearch_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_filessearch_codebaserun_commands 等,源码注释也印证了"Uses ClineCore's built-in tools instead of defining custom tools"——示例没有注册任何自定义工具,全部依赖 Cline 的 internal agent harness。

5. 事件流渲染:agent_event 的四种内容类型

流式输出的核心是 handleAgentEvent,它按 AgentEventtype 分派(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 "Streams agent_event text 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 编程模型的最小完整样本,它覆盖了:

  1. 运行时创建ClineCore.create()backendModecapabilities 注入方式;
  2. 有状态会话start() 显式模型配置、单一 sessionId 多轮复用、stop()/dispose() 资源清理;
  3. 工具审批双层模型toolPolicies 声明式自动审批 + requestToolApproval 宿主回调兜底;
  4. 事件驱动渲染subscribe() 订阅 CoreSessionEvent,按 agent_event 的 content 生命周期(start/update/end)流式输出文本与工具轨迹。

如果你需要一个能直接运行、带完整会话与内置工具能力的终端编码 Agent 起点,可以直接参考本示例的目录结构(package.json + tsconfig.json + src/index.ts)与依赖声明,在其基础上扩展工具策略、审批策略或事件消费逻辑。

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