@gird/ccli 2.15.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -6
- package/dist/agent/ai.js +10 -345
- package/dist/agent/ai.js.map +1 -1
- package/dist/agent/providers/anthropic.js +258 -0
- package/dist/agent/providers/anthropic.js.map +1 -0
- package/dist/agent/providers/gemini.js +14 -0
- package/dist/agent/providers/gemini.js.map +1 -0
- package/dist/agent/providers/openai.js +110 -0
- package/dist/agent/providers/openai.js.map +1 -0
- package/dist/agent/tool-projection.js +57 -0
- package/dist/agent/tool-projection.js.map +1 -0
- package/dist/cli/args/index.js +4 -0
- package/dist/cli/args/index.js.map +1 -1
- package/dist/cli/clipboard-image.js +51 -0
- package/dist/cli/clipboard-image.js.map +1 -0
- package/dist/cli/image-compress.js +62 -0
- package/dist/cli/image-compress.js.map +1 -0
- package/dist/cli/image-reference.js +190 -0
- package/dist/cli/image-reference.js.map +1 -0
- package/dist/cli/repl/index.js +110 -60
- package/dist/cli/repl/index.js.map +1 -1
- package/dist/cli/run/event-renderer.js +57 -0
- package/dist/cli/run/event-renderer.js.map +1 -0
- package/dist/cli/run/hooks.js +58 -19
- package/dist/cli/run/hooks.js.map +1 -1
- package/dist/cli/run/index.js +218 -9
- package/dist/cli/run/index.js.map +1 -1
- package/dist/cli/run/task.js +58 -1
- package/dist/cli/run/task.js.map +1 -1
- package/dist/cli/run/todo-panel.js +13 -0
- package/dist/cli/run/todo-panel.js.map +1 -1
- package/dist/cli/session/checkpoint.js +252 -0
- package/dist/cli/session/checkpoint.js.map +1 -0
- package/dist/cli/session/index.js +1 -1
- package/dist/cli/session/index.js.map +1 -1
- package/dist/cli/slash/index.js +26 -2
- package/dist/cli/slash/index.js.map +1 -1
- package/dist/cli/slash/session/load.js +3 -0
- package/dist/cli/slash/session/load.js.map +1 -1
- package/dist/cli/slash/session/replay.js +118 -0
- package/dist/cli/slash/session/replay.js.map +1 -0
- package/dist/cli/slash/session/save.js +1 -0
- package/dist/cli/slash/session/save.js.map +1 -1
- package/dist/cli/slash/system/agent.js +38 -104
- package/dist/cli/slash/system/agent.js.map +1 -1
- package/dist/cli/slash/system/compact.js +26 -5
- package/dist/cli/slash/system/compact.js.map +1 -1
- package/dist/cli/slash/system/goal.js +34 -34
- package/dist/cli/slash/system/goal.js.map +1 -1
- package/dist/cli/slash/system/help.js +3 -0
- package/dist/cli/slash/system/help.js.map +1 -1
- package/dist/cli/slash/system/plan.js +140 -0
- package/dist/cli/slash/system/plan.js.map +1 -0
- package/dist/cli/slash/system/revert.js +133 -0
- package/dist/cli/slash/system/revert.js.map +1 -0
- package/dist/config/config.js +57 -4
- package/dist/config/config.js.map +1 -1
- package/dist/config/plugins.js +378 -0
- package/dist/config/plugins.js.map +1 -0
- package/dist/config/skills.js +68 -6
- package/dist/config/skills.js.map +1 -1
- package/dist/harness/index.js +334 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/records.js +98 -0
- package/dist/harness/records.js.map +1 -0
- package/dist/index.js +87 -42
- package/dist/index.js.map +1 -1
- package/dist/tools/bash-background.js +2 -2
- package/dist/tools/bash-background.js.map +1 -1
- package/dist/tools/bash.js +3 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit-file.js +8 -2
- package/dist/tools/edit-file.js.map +1 -1
- package/dist/tools/glob.js +4 -3
- package/dist/tools/glob.js.map +1 -1
- package/dist/tools/grep.js +4 -3
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.js +20 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-dir.js +3 -2
- package/dist/tools/list-dir.js.map +1 -1
- package/dist/tools/read-file.js +3 -2
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/todo.js +17 -5
- package/dist/tools/todo.js.map +1 -1
- package/dist/tools/web-fetch.js +95 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/write-file.js +8 -2
- package/dist/tools/write-file.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CCLI Harness 边界。
|
|
3
|
+
*
|
|
4
|
+
* 这一层先做薄封装:把配置解析、AI client、MCP/skill 工具注册、记忆注入、
|
|
5
|
+
* hooks 与 runAgentTurn 入口收拢起来。CLI 仍负责 readline、确认弹窗和渲染,
|
|
6
|
+
* 后续 Web / Server / SDK 可从这里继续扩展,而不是直接耦合 cli/run。
|
|
7
|
+
*/
|
|
8
|
+
import { join, resolve } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { NAME, VERSION } from '../version.js';
|
|
11
|
+
import { createAIClient, } from '../agent/ai.js';
|
|
12
|
+
import { McpClientManager } from '../agent/mcp.js';
|
|
13
|
+
import { DEFAULT_SYSTEM_PROMPT, getRolePrompt, loadConfig, resolveMaxStepsPerTurn, } from '../config/config.js';
|
|
14
|
+
import { loadEnvFile } from '../config/env.js';
|
|
15
|
+
import { appendMemories, loadMemories } from '../config/memory.js';
|
|
16
|
+
import { loadSkills } from '../config/skills.js';
|
|
17
|
+
import { makeSkillTool } from '../tools/skill-tool.js';
|
|
18
|
+
import { registerMcpTools, registerTool, resetTools, } from '../tools/index.js';
|
|
19
|
+
import { runAgentTurn, runOnce as runOnceTurn, } from '../cli/run/index.js';
|
|
20
|
+
import { runHooks, runUserPromptHooks, } from '../cli/run/hooks.js';
|
|
21
|
+
import { defaultName } from '../cli/session/index.js';
|
|
22
|
+
import { defaultRecordsDir, pruneRecordsDir, RecordsWriter, } from './records.js';
|
|
23
|
+
export class CcliHarness {
|
|
24
|
+
/** Harness 绑定的工作目录;配置、记忆、技能、MCP 环境变量都以它为基准解析。 */
|
|
25
|
+
cwd;
|
|
26
|
+
/** 是否忽略配置文件里的 hooks;用于 `--no-hooks` 这类安全逃生场景。 */
|
|
27
|
+
noHooks;
|
|
28
|
+
/** 启动时扫描到的技能列表;REPL 用它做 `/skill` 补全与手动技能展开。 */
|
|
29
|
+
skills;
|
|
30
|
+
/** MCP server 启动时的连接错误;调用方负责决定如何展示给用户。 */
|
|
31
|
+
mcpErrors;
|
|
32
|
+
/** 当前生效配置;`reloadConfig()` 后会被替换为最新配置。 */
|
|
33
|
+
config;
|
|
34
|
+
/** 当前 AI client;切换/重载配置后会按新配置重新创建。 */
|
|
35
|
+
ai;
|
|
36
|
+
/** 当前注入 system prompt 的记忆快照;`refreshMemories()` 会重新读取。 */
|
|
37
|
+
memories;
|
|
38
|
+
/** 当前 MCP manager;没有 MCP 配置时为 null,退出时由 `close()` 统一清理。 */
|
|
39
|
+
mcpManager;
|
|
40
|
+
/** 当前会话 ID(用于快照备份与 records 文件命名)。 */
|
|
41
|
+
sessionId;
|
|
42
|
+
/** 会话事件记录目录。 */
|
|
43
|
+
recordsDir;
|
|
44
|
+
/** 解析并获取当前应生效的快照备份基准目录。 */
|
|
45
|
+
get checkpointsDir() {
|
|
46
|
+
const mode = this.config.checkpoint?.mode ?? 'local';
|
|
47
|
+
const customPath = this.config.checkpoint?.path;
|
|
48
|
+
if (customPath) {
|
|
49
|
+
return resolve(this.cwd, customPath);
|
|
50
|
+
}
|
|
51
|
+
if (mode === 'global') {
|
|
52
|
+
return join(homedir(), '.ccli', 'checkpoints');
|
|
53
|
+
}
|
|
54
|
+
return this.cwd
|
|
55
|
+
? join(this.cwd, '.ccli', 'checkpoints')
|
|
56
|
+
: join(homedir(), '.ccli', 'checkpoints');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 构造一个已初始化好的 Harness 实例。
|
|
60
|
+
*
|
|
61
|
+
* 一般业务代码应使用 `createCcliHarness()`,因为它会负责读取配置、连接 MCP、
|
|
62
|
+
* 扫描技能和加载记忆;构造函数主要给测试或未来更底层的组合场景使用。
|
|
63
|
+
*/
|
|
64
|
+
constructor(options) {
|
|
65
|
+
this.cwd = options.cwd;
|
|
66
|
+
this.noHooks = options.noHooks;
|
|
67
|
+
this.config = options.config;
|
|
68
|
+
this.ai = options.ai;
|
|
69
|
+
this.skills = options.skills;
|
|
70
|
+
this.memories = options.memories;
|
|
71
|
+
this.mcpManager = options.mcpManager;
|
|
72
|
+
this.mcpErrors = options.mcpErrors;
|
|
73
|
+
this.sessionId = options.sessionId ?? 'test-session';
|
|
74
|
+
this.recordsDir = options.recordsDir ?? defaultRecordsDir();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 记录一条会话事件到 records(~/.ccli/records/<sessionId>.jsonl)。
|
|
78
|
+
* 每次调用按当前 sessionId 现取 writer(sessionId 可能被 /session 换掉);
|
|
79
|
+
* 写入失败在 RecordsWriter 内部静默,绝不打断会话。
|
|
80
|
+
*/
|
|
81
|
+
recordEvent(event) {
|
|
82
|
+
new RecordsWriter(this.recordsDir, this.sessionId).write(event);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 返回当前应生效的 hooks。
|
|
86
|
+
*
|
|
87
|
+
* 如果启动时指定了 `--no-hooks`,这里固定返回 `undefined`,让调用方无需重复判断。
|
|
88
|
+
*/
|
|
89
|
+
get hooks() {
|
|
90
|
+
return this.noHooks ? undefined : this.config.hooks;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 根据角色名查找配置或内置角色的 system prompt。
|
|
94
|
+
*
|
|
95
|
+
* 只负责“查找”,找不到时返回 undefined;需要兜底默认提示词时用 `roleBasePrompt()`。
|
|
96
|
+
*/
|
|
97
|
+
getRolePrompt(role) {
|
|
98
|
+
return getRolePrompt(role, this.config.roles);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 获取某个角色最终用于对话的基础提示词。
|
|
102
|
+
*
|
|
103
|
+
* 若角色不存在,则回退到 `DEFAULT_SYSTEM_PROMPT`。这一步还不注入记忆。
|
|
104
|
+
*/
|
|
105
|
+
roleBasePrompt(role) {
|
|
106
|
+
return this.getRolePrompt(role) ?? DEFAULT_SYSTEM_PROMPT;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 构造最终 system prompt:基础提示词 + 当前记忆分层。
|
|
110
|
+
*
|
|
111
|
+
* REPL 切换角色或 `#` 写入记忆后,会用这个方法刷新 `history[0]`。
|
|
112
|
+
*/
|
|
113
|
+
buildSystemPrompt(basePrompt = DEFAULT_SYSTEM_PROMPT) {
|
|
114
|
+
return appendMemories(basePrompt, this.memories);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 创建一个新的会话历史数组。
|
|
118
|
+
*
|
|
119
|
+
* 返回值第一条固定是带记忆的 system 消息;后续 user / assistant / tool 消息由调用方追加。
|
|
120
|
+
*/
|
|
121
|
+
createHistory(role = 'default') {
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
role: 'system',
|
|
125
|
+
content: this.buildSystemPrompt(this.roleBasePrompt(role)),
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 重新读取全局 / 项目 / 子目录记忆,并更新 Harness 内部快照。
|
|
131
|
+
*
|
|
132
|
+
* 典型场景是用户在 REPL 里输入 `# xxx` 动态写入项目记忆后,立即让当前会话生效。
|
|
133
|
+
*/
|
|
134
|
+
refreshMemories() {
|
|
135
|
+
this.memories = loadMemories(this.cwd);
|
|
136
|
+
return this.memories;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 解析当前配置里的单回合工具调用上限。
|
|
140
|
+
*
|
|
141
|
+
* 封装这层后,CLI 不需要知道 `maxStepsPerTurn` 的默认值和 `0=不限` 规则。
|
|
142
|
+
*/
|
|
143
|
+
maxStepsPerTurn() {
|
|
144
|
+
return resolveMaxStepsPerTurn(this.config);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 执行一个交互式用户回合。
|
|
148
|
+
*
|
|
149
|
+
* 这是 `runAgentTurn()` 的 Harness 门面:自动注入当前 AI client、工具轮数上限和 hooks;
|
|
150
|
+
* CLI 仍传入 history、confirm、stats、permission 等交互态对象。
|
|
151
|
+
*/
|
|
152
|
+
async runTurn(options) {
|
|
153
|
+
await runAgentTurn({
|
|
154
|
+
...options,
|
|
155
|
+
sessionId: this.sessionId,
|
|
156
|
+
checkpointsDir: this.checkpointsDir,
|
|
157
|
+
ai: this.ai,
|
|
158
|
+
cwd: this.cwd,
|
|
159
|
+
configMaxSteps: options.configMaxSteps ?? this.maxStepsPerTurn(),
|
|
160
|
+
hooks: options.hooks ?? this.hooks,
|
|
161
|
+
record: options.record ?? ((e) => this.recordEvent(e)),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 执行一个交互式用户回合,并把输出转成结构化事件。
|
|
166
|
+
*
|
|
167
|
+
* 这是给未来 Web / Server / SDK 使用的旁路 API:文本输出走 `text_delta`,
|
|
168
|
+
* 工具调用走 `tool_call`,工具完成走 `tool_result`。终端 CLI 仍继续用 `runTurn()`。
|
|
169
|
+
*/
|
|
170
|
+
async runTurnWithEvents(options) {
|
|
171
|
+
try {
|
|
172
|
+
await this.runTurn({
|
|
173
|
+
...options,
|
|
174
|
+
print: (text) => options.emit({ type: 'text_delta', text }),
|
|
175
|
+
onReasoning: (text) => options.emit({ type: 'reasoning_delta', text }),
|
|
176
|
+
onHookOutput: (text) => options.emit({ type: 'hook_output', text }),
|
|
177
|
+
onPermissionRequest: (info) => options.emit({ type: 'permission_request', ...info }),
|
|
178
|
+
onToolCall: (toolCall) => options.emit({ type: 'tool_call', toolCall }),
|
|
179
|
+
onToolResult: (toolCall, result) => options.emit({ type: 'tool_result', toolCall, result }),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
// 回合异常也作为结构化事件抛出,便于前端渲染错误卡片;同时向上抛保留原有控制流
|
|
184
|
+
options.emit({ type: 'turn_error', error: e?.message ?? String(e) });
|
|
185
|
+
throw e;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 执行一次非交互式请求。
|
|
190
|
+
*
|
|
191
|
+
* 用于 `ccli "prompt"` / stdin 管道模式。Harness 负责注入 AI、system prompt、hooks 和轮数上限;
|
|
192
|
+
* 调用方只需要提供 prompt、确认策略和可选输出函数。
|
|
193
|
+
*/
|
|
194
|
+
async runOnce(options) {
|
|
195
|
+
return runOnceTurn({
|
|
196
|
+
sessionId: options.sessionId ?? this.sessionId,
|
|
197
|
+
checkpointsDir: options.checkpointsDir ?? this.checkpointsDir,
|
|
198
|
+
ai: this.ai,
|
|
199
|
+
prompt: options.prompt,
|
|
200
|
+
confirm: options.confirm,
|
|
201
|
+
print: options.print,
|
|
202
|
+
cwd: this.cwd,
|
|
203
|
+
systemPrompt: options.systemPrompt ?? this.buildSystemPrompt(),
|
|
204
|
+
configMaxSteps: this.maxStepsPerTurn(),
|
|
205
|
+
hooks: this.hooks,
|
|
206
|
+
record: (e) => this.recordEvent(e),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 执行一次非交互式请求,并把输出转成结构化事件。
|
|
211
|
+
*
|
|
212
|
+
* 与 `runOnce()` 的用户语义相同,但输出不直接绑定终端文本;CLI / Web / Server
|
|
213
|
+
* 可以各自传入不同的 `emit` 消费事件。
|
|
214
|
+
*/
|
|
215
|
+
async runOnceWithEvents(options) {
|
|
216
|
+
const history = [
|
|
217
|
+
{
|
|
218
|
+
role: 'system',
|
|
219
|
+
content: options.systemPrompt ?? this.buildSystemPrompt(),
|
|
220
|
+
},
|
|
221
|
+
{ role: 'user', content: options.prompt },
|
|
222
|
+
];
|
|
223
|
+
await this.runTurnWithEvents({
|
|
224
|
+
history,
|
|
225
|
+
confirm: options.confirm,
|
|
226
|
+
stats: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
227
|
+
emit: options.emit,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 触发 SessionStart hooks。
|
|
232
|
+
*
|
|
233
|
+
* `source` 用来区分 REPL 会话和非交互单次调用,便于用户 hook 脚本做不同处理。
|
|
234
|
+
*/
|
|
235
|
+
runSessionStart(source) {
|
|
236
|
+
return runHooks(this.hooks, 'SessionStart', {
|
|
237
|
+
cwd: this.cwd,
|
|
238
|
+
source,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 触发 SessionEnd hooks。
|
|
243
|
+
*
|
|
244
|
+
* 退出清理阶段也会尽量调用;如果 hooks 被禁用,则底层会直接返回空结果。
|
|
245
|
+
*/
|
|
246
|
+
runSessionEnd() {
|
|
247
|
+
return runHooks(this.hooks, 'SessionEnd', { cwd: this.cwd });
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 运行 UserPromptSubmit hooks,并返回可能被补充上下文后的 prompt。
|
|
251
|
+
*
|
|
252
|
+
* hook 可以拦截本次输入,也可以把 stdout 追加为“hook 补充上下文”。
|
|
253
|
+
*/
|
|
254
|
+
runUserPromptHooks(prompt) {
|
|
255
|
+
return runUserPromptHooks(this.hooks, this.cwd, prompt);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 重新读取配置并刷新 Harness 中的 config 与 AI client。
|
|
259
|
+
*
|
|
260
|
+
* 注意:这个方法只负责配置和模型客户端;MCP server 的重连仍由 `/reload` / `/mcp reload`
|
|
261
|
+
* 走既有的 `reloadMcpServers()`,避免本步改变用户可见行为。
|
|
262
|
+
*/
|
|
263
|
+
reloadConfig() {
|
|
264
|
+
const result = loadConfig(this.cwd);
|
|
265
|
+
if (!result.ok)
|
|
266
|
+
return result;
|
|
267
|
+
this.config = result.config;
|
|
268
|
+
this.ai = createAIClient(this.config);
|
|
269
|
+
return { ok: true, config: this.config };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* 关闭 Harness 持有的外部资源。
|
|
273
|
+
*
|
|
274
|
+
* 目前主要是关闭 MCP 连接;未来如果引入 server、records writer 或 telemetry flush,
|
|
275
|
+
* 也应统一收口到这里。
|
|
276
|
+
*/
|
|
277
|
+
async close() {
|
|
278
|
+
await this.mcpManager?.closeAll();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 创建并初始化 CCLI Harness。
|
|
283
|
+
*
|
|
284
|
+
* 这是 CLI / 未来 Server 的统一入口:读取配置、加载 `.env`、连接 MCP、注册 MCP/skill 工具、
|
|
285
|
+
* 加载记忆并创建 AI client。失败时返回 `{ ok:false }`,不直接退出进程,方便不同前端自行处理。
|
|
286
|
+
*/
|
|
287
|
+
export async function createCcliHarness(options = {}) {
|
|
288
|
+
const cwd = options.cwd ?? process.cwd();
|
|
289
|
+
const result = loadConfig(cwd);
|
|
290
|
+
if (!result.ok)
|
|
291
|
+
return result;
|
|
292
|
+
const config = result.config;
|
|
293
|
+
const dotenv = loadEnvFile(join(cwd, '.env'));
|
|
294
|
+
const envLookup = (name) => process.env[name] ?? dotenv[name];
|
|
295
|
+
const mcpErrors = [];
|
|
296
|
+
let mcpManager = null;
|
|
297
|
+
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
298
|
+
resetTools();
|
|
299
|
+
mcpManager = new McpClientManager(config.mcpServers, { name: NAME, version: VERSION }, envLookup);
|
|
300
|
+
const errors = await mcpManager.connectAll();
|
|
301
|
+
mcpErrors.push(...errors);
|
|
302
|
+
for (const error of errors) {
|
|
303
|
+
options.onMcpError?.(error);
|
|
304
|
+
}
|
|
305
|
+
await registerMcpTools(mcpManager);
|
|
306
|
+
}
|
|
307
|
+
const skills = loadSkills(cwd);
|
|
308
|
+
const skillTool = makeSkillTool(skills);
|
|
309
|
+
if (skillTool)
|
|
310
|
+
registerTool(skillTool);
|
|
311
|
+
// records 轮转清理:每次启动删一次过期/超量文件,失败静默(不挡启动)
|
|
312
|
+
try {
|
|
313
|
+
pruneRecordsDir(options.recordsDir ?? defaultRecordsDir());
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// 清理失败不影响启动
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
harness: new CcliHarness({
|
|
321
|
+
cwd,
|
|
322
|
+
noHooks: options.noHooks ?? false,
|
|
323
|
+
config,
|
|
324
|
+
ai: createAIClient(config),
|
|
325
|
+
skills,
|
|
326
|
+
memories: loadMemories(cwd),
|
|
327
|
+
mcpManager,
|
|
328
|
+
mcpErrors,
|
|
329
|
+
sessionId: defaultName(),
|
|
330
|
+
recordsDir: options.recordsDir,
|
|
331
|
+
}),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/harness/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话事件记录(records)。
|
|
3
|
+
*
|
|
4
|
+
* 把每个回合的结构化事件以「一行一个 JSON」追加到
|
|
5
|
+
* `~/.ccli/records/<sessionId>.jsonl`,面向复盘执行过程:
|
|
6
|
+
* 用户在什么时候说了什么、模型每轮回答了什么、调了哪些工具、
|
|
7
|
+
* 工具返回了什么、回合怎么结束的。
|
|
8
|
+
*
|
|
9
|
+
* 与 ~/.ccli/sessions/*.json(对话快照,供 /save、/load)互补:
|
|
10
|
+
* 快照用于恢复对话,records 用于还原「执行过程」(带时间戳与单轮边界)。
|
|
11
|
+
*
|
|
12
|
+
* 底线:记录是旁观者——任何写入失败都静默吞掉,绝不能打断会话。
|
|
13
|
+
*/
|
|
14
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, } from 'node:fs';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
/** 默认记录目录:~/.ccli/records。 */
|
|
18
|
+
export function defaultRecordsDir() {
|
|
19
|
+
return join(homedir(), '.ccli', 'records');
|
|
20
|
+
}
|
|
21
|
+
/** sessionId 用作文件名:非字母数字字符转成 _,避免路径问题。 */
|
|
22
|
+
function safeFileName(sessionId) {
|
|
23
|
+
return sessionId.replace(/[^a-zA-Z0-9._-]/g, '_') || 'session';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 某个会话的记录写入器。
|
|
27
|
+
* 每次 write 直接 appendFileSync(操作系统负责追加原子性),失败静默。
|
|
28
|
+
*/
|
|
29
|
+
export class RecordsWriter {
|
|
30
|
+
dir;
|
|
31
|
+
sessionId;
|
|
32
|
+
constructor(dir, sessionId) {
|
|
33
|
+
this.dir = dir;
|
|
34
|
+
this.sessionId = sessionId;
|
|
35
|
+
}
|
|
36
|
+
get filePath() {
|
|
37
|
+
return join(this.dir, `${safeFileName(this.sessionId)}.jsonl`);
|
|
38
|
+
}
|
|
39
|
+
/** 追加一条事件;任何失败(磁盘满、权限等)都不抛出。 */
|
|
40
|
+
write(event) {
|
|
41
|
+
try {
|
|
42
|
+
mkdirSync(this.dir, { recursive: true });
|
|
43
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...event }) +
|
|
44
|
+
'\n';
|
|
45
|
+
appendFileSync(this.filePath, line, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// 记录绝不能打断会话
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** records 保留上限:最近多少个会话文件。 */
|
|
53
|
+
export const MAX_RECORD_FILES = 50;
|
|
54
|
+
/** records 保留上限:多少天内没更新过的文件删除。 */
|
|
55
|
+
export const MAX_RECORD_AGE_DAYS = 30;
|
|
56
|
+
/**
|
|
57
|
+
* 轮转清理 records 目录(启动时调用一次):
|
|
58
|
+
* 先删超过 maxAgeDays 没更新的 jsonl,再按数量上限从最旧的开始删。
|
|
59
|
+
* 任何异常(目录不存在、权限问题)都静默,返回实际删除的文件数。
|
|
60
|
+
*/
|
|
61
|
+
export function pruneRecordsDir(dir, options = {}) {
|
|
62
|
+
const maxFiles = options.maxFiles ?? MAX_RECORD_FILES;
|
|
63
|
+
const maxAgeDays = options.maxAgeDays ?? MAX_RECORD_AGE_DAYS;
|
|
64
|
+
const now = options.now ?? Date.now();
|
|
65
|
+
try {
|
|
66
|
+
if (!existsSync(dir))
|
|
67
|
+
return 0;
|
|
68
|
+
const files = readdirSync(dir)
|
|
69
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
70
|
+
.map((f) => {
|
|
71
|
+
const path = join(dir, f);
|
|
72
|
+
return { path, mtime: statSync(path).mtime.getTime() };
|
|
73
|
+
});
|
|
74
|
+
const ageLimit = maxAgeDays * 24 * 60 * 60 * 1000;
|
|
75
|
+
const fresh = files.filter((f) => now - f.mtime <= ageLimit);
|
|
76
|
+
const doomed = new Set(files.filter((f) => now - f.mtime > ageLimit).map((f) => f.path));
|
|
77
|
+
// 数量仍超限 → 从最旧的继续删
|
|
78
|
+
const byAge = [...fresh].sort((a, b) => a.mtime - b.mtime);
|
|
79
|
+
for (let i = 0; i < Math.max(0, byAge.length - maxFiles); i++) {
|
|
80
|
+
doomed.add(byAge[i].path);
|
|
81
|
+
}
|
|
82
|
+
let deleted = 0;
|
|
83
|
+
for (const path of doomed) {
|
|
84
|
+
try {
|
|
85
|
+
unlinkSync(path);
|
|
86
|
+
deleted++;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// 单个删除失败不影响其它
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return deleted;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/harness/records.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -12,20 +12,15 @@
|
|
|
12
12
|
* ccli 启动交互式会话
|
|
13
13
|
*/
|
|
14
14
|
import { parseArgs } from './cli/args/index.js';
|
|
15
|
-
import { join } from 'node:path';
|
|
16
15
|
import { NAME, VERSION } from './version.js';
|
|
17
16
|
import { startRepl } from './cli/repl/index.js';
|
|
18
17
|
import { style } from './cli/style.js';
|
|
19
|
-
import { printConfig
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { loadSkills } from './config/skills.js';
|
|
23
|
-
import { registerTool } from './tools/index.js';
|
|
24
|
-
import { makeSkillTool } from './tools/skill-tool.js';
|
|
25
|
-
import { createAIClient } from './agent/ai.js';
|
|
26
|
-
import { McpClientManager } from './agent/mcp.js';
|
|
27
|
-
import { runOnce } from './cli/run/index.js';
|
|
18
|
+
import { printConfig } from './config/config.js';
|
|
19
|
+
import { printPlugins, installPlugin, uninstallPlugin, setPluginEnabled, } from './config/plugins.js';
|
|
20
|
+
import { createCcliHarness } from './harness/index.js';
|
|
28
21
|
import { injectFileReferences } from './cli/file-reference.js';
|
|
22
|
+
import { extractImageReferences, buildUserContent, } from './cli/image-reference.js';
|
|
23
|
+
import { createHarnessEventRenderer } from './cli/run/event-renderer.js';
|
|
29
24
|
const HELP = `${NAME} v${VERSION}
|
|
30
25
|
|
|
31
26
|
用法:
|
|
@@ -33,6 +28,7 @@ const HELP = `${NAME} v${VERSION}
|
|
|
33
28
|
|
|
34
29
|
命令:
|
|
35
30
|
config 打印当前解析出的配置(诊断用,密钥打码)
|
|
31
|
+
plugin 插件包管理:list / install <目录> [--project] / uninstall <名> / enable <名> / disable <名>
|
|
36
32
|
|
|
37
33
|
选项:
|
|
38
34
|
-v, --version 打印版本号
|
|
@@ -40,10 +36,16 @@ const HELP = `${NAME} v${VERSION}
|
|
|
40
36
|
-y, --yes 非交互式模式下自动确认破坏性工具调用
|
|
41
37
|
-r, --role <name> 非交互式模式下使用的系统提示词角色(默认 default)
|
|
42
38
|
--no-hooks 禁用所有生命周期钩子(忽略配置中的 hooks)
|
|
39
|
+
--project 插件安装到项目级 .ccli/plugins/(配合 plugin install)
|
|
43
40
|
|
|
44
41
|
示例:
|
|
45
42
|
ccli 启动交互式对话
|
|
46
43
|
ccli config 查看当前生效的后端 / 模型 / 密钥来源
|
|
44
|
+
ccli plugin 查看已安装的插件包
|
|
45
|
+
ccli plugin install ./my-plugin 安装一个插件包到 ~/.ccli/plugins/
|
|
46
|
+
ccli plugin install ./p --project 安装到项目级 .ccli/plugins/
|
|
47
|
+
ccli plugin uninstall my-plugin 卸载某个插件
|
|
48
|
+
ccli plugin disable my-plugin 停用某个插件
|
|
47
49
|
ccli --version 查看版本
|
|
48
50
|
ccli "解释这段报错" 非交互式单次调用
|
|
49
51
|
cat file.ts | ccli "给这段代码加注释" 从 stdin 读取上下文
|
|
@@ -89,65 +91,108 @@ async function main() {
|
|
|
89
91
|
if (command === 'config') {
|
|
90
92
|
process.exit(printConfig());
|
|
91
93
|
}
|
|
94
|
+
if (command === 'plugin' || command === 'plugins') {
|
|
95
|
+
const sub = args.positionals[1];
|
|
96
|
+
const arg = args.positionals[2];
|
|
97
|
+
if (!sub || sub === 'list') {
|
|
98
|
+
process.exit(printPlugins());
|
|
99
|
+
}
|
|
100
|
+
if (sub === 'install') {
|
|
101
|
+
if (!arg) {
|
|
102
|
+
console.error(style.error('用法:ccli plugin install <目录> [--project]'));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const r = installPlugin(arg, { project: args.project });
|
|
106
|
+
console.log(r.ok ? style.success(r.message) : style.error(r.message));
|
|
107
|
+
process.exit(r.ok ? 0 : 1);
|
|
108
|
+
}
|
|
109
|
+
if (sub === 'uninstall' || sub === 'remove') {
|
|
110
|
+
if (!arg) {
|
|
111
|
+
console.error(style.error(`用法:ccli plugin ${sub} <名称>`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
const r = uninstallPlugin(arg);
|
|
115
|
+
console.log(r.ok ? style.success(r.message) : style.error(r.message));
|
|
116
|
+
process.exit(r.ok ? 0 : 1);
|
|
117
|
+
}
|
|
118
|
+
if (sub === 'enable' || sub === 'disable') {
|
|
119
|
+
if (!arg) {
|
|
120
|
+
console.error(style.error(`用法:ccli plugin ${sub} <名称>`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const r = setPluginEnabled(arg, sub === 'enable');
|
|
124
|
+
console.log(r.ok ? style.success(r.message) : style.error(r.message));
|
|
125
|
+
process.exit(r.ok ? 0 : 1);
|
|
126
|
+
}
|
|
127
|
+
console.error(style.error(`未知子命令:plugin ${sub}`));
|
|
128
|
+
console.error(style.muted('可用:list(默认) / install <目录> [--project] / uninstall <名> / enable <名> / disable <名>'));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
92
131
|
const promptFromArgs = args.positionals.join(' ').trim();
|
|
93
132
|
const stdinText = (await readStdin()).trim();
|
|
94
|
-
|
|
133
|
+
// 先抽取 @图片(走多模态),再对剩余文本做 @文件 注入
|
|
134
|
+
const extracted = extractImageReferences(buildPrompt(promptFromArgs, stdinText), process.cwd());
|
|
135
|
+
for (const e of extracted.errors)
|
|
136
|
+
console.error(style.warn(e));
|
|
137
|
+
for (const n of extracted.notes)
|
|
138
|
+
console.error(style.muted(`(${n})`));
|
|
139
|
+
const prompt = injectFileReferences(extracted.text, process.cwd());
|
|
95
140
|
// 有 prompt 或 stdin 时,走非交互式单次调用
|
|
96
141
|
if (prompt) {
|
|
97
|
-
const result =
|
|
142
|
+
const result = await createCcliHarness({ noHooks: args.noHooks });
|
|
98
143
|
if (!result.ok) {
|
|
99
144
|
console.error(style.error(result.message));
|
|
100
145
|
process.exit(1);
|
|
101
146
|
}
|
|
102
|
-
const {
|
|
103
|
-
|
|
147
|
+
const { harness } = result;
|
|
148
|
+
if (harness.mcpErrors.length > 0) {
|
|
149
|
+
console.error(style.error('部分 MCP server 连接失败:'));
|
|
150
|
+
for (const e of harness.mcpErrors) {
|
|
151
|
+
console.error(style.muted(` ${e}`));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
104
154
|
const rolePrompt = args.role
|
|
105
|
-
? getRolePrompt(args.role
|
|
155
|
+
? harness.getRolePrompt(args.role)
|
|
106
156
|
: undefined;
|
|
107
|
-
const systemPrompt =
|
|
108
|
-
// 把可供模型调用的技能注册成 skill 工具(技能 v3)
|
|
109
|
-
const skillTool = makeSkillTool(loadSkills(process.cwd()));
|
|
110
|
-
if (skillTool)
|
|
111
|
-
registerTool(skillTool);
|
|
157
|
+
const systemPrompt = harness.buildSystemPrompt(rolePrompt ?? harness.roleBasePrompt('default'));
|
|
112
158
|
if (args.role &&
|
|
113
159
|
rolePrompt === undefined &&
|
|
114
160
|
args.role !== 'default') {
|
|
115
161
|
console.error(style.warn(`未知角色:${args.role},使用默认角色。`));
|
|
116
162
|
}
|
|
117
|
-
// 连接 MCP server
|
|
118
|
-
const dotenv = loadEnvFile(join(process.cwd(), '.env'));
|
|
119
|
-
const envLookup = (name) => process.env[name] ?? dotenv[name];
|
|
120
|
-
let mcp = null;
|
|
121
|
-
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
122
|
-
mcp = new McpClientManager(config.mcpServers, { name: NAME, version: VERSION }, envLookup);
|
|
123
|
-
const errors = await mcp.connectAll();
|
|
124
|
-
if (errors.length > 0) {
|
|
125
|
-
console.error(style.error('部分 MCP server 连接失败:'));
|
|
126
|
-
for (const e of errors)
|
|
127
|
-
console.error(style.muted(` ${e}`));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
163
|
const confirm = args.yes
|
|
131
164
|
? () => Promise.resolve('yes')
|
|
132
165
|
: async (preview) => {
|
|
133
166
|
console.error(style.warn(`\n[非交互式模式] 需要确认以下操作,请添加 --yes 自动批准:\n${preview}`));
|
|
134
167
|
return 'no';
|
|
135
168
|
};
|
|
169
|
+
// SessionStart hook:仅观察
|
|
170
|
+
const started = await harness.runSessionStart('once');
|
|
171
|
+
if (started.output)
|
|
172
|
+
console.log(started.output);
|
|
173
|
+
// UserPromptSubmit hook:可拦截输入;放行时 stdout 作为补充上下文追加
|
|
174
|
+
const submitted = await harness.runUserPromptHooks(prompt);
|
|
175
|
+
if (submitted.blocked) {
|
|
176
|
+
console.error(style.error(`输入被 UserPromptSubmit hook 拦截:${submitted.reason ?? ''}`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const content = buildUserContent(submitted.prompt, extracted.images, harness.ai.supportsVision(), harness.ai.getModel());
|
|
136
180
|
try {
|
|
137
|
-
await
|
|
138
|
-
|
|
139
|
-
prompt,
|
|
181
|
+
await harness.runOnceWithEvents({
|
|
182
|
+
prompt: content,
|
|
140
183
|
confirm,
|
|
141
|
-
mcpManager: mcp ?? undefined,
|
|
142
184
|
systemPrompt,
|
|
143
|
-
|
|
144
|
-
|
|
185
|
+
emit: createHarnessEventRenderer({
|
|
186
|
+
print: process.stdout.write.bind(process.stdout),
|
|
187
|
+
}),
|
|
145
188
|
});
|
|
146
189
|
}
|
|
147
190
|
finally {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
191
|
+
// SessionEnd hook:仅观察(出错也尽量触发)
|
|
192
|
+
const ended = await harness.runSessionEnd();
|
|
193
|
+
if (ended.output)
|
|
194
|
+
console.log(ended.output);
|
|
195
|
+
await harness.close();
|
|
151
196
|
}
|
|
152
197
|
return;
|
|
153
198
|
}
|