@hflin/cclin 0.1.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 +124 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +32 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +280 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/runtime/compaction.d.ts +49 -0
- package/dist/runtime/compaction.d.ts.map +1 -0
- package/dist/runtime/compaction.js +118 -0
- package/dist/runtime/compaction.js.map +1 -0
- package/dist/runtime/compaction.test.d.ts +7 -0
- package/dist/runtime/compaction.test.d.ts.map +1 -0
- package/dist/runtime/compaction.test.js +70 -0
- package/dist/runtime/compaction.test.js.map +1 -0
- package/dist/runtime/history.d.ts +34 -0
- package/dist/runtime/history.d.ts.map +1 -0
- package/dist/runtime/history.js +63 -0
- package/dist/runtime/history.js.map +1 -0
- package/dist/runtime/hooks.d.ts +54 -0
- package/dist/runtime/hooks.d.ts.map +1 -0
- package/dist/runtime/hooks.js +113 -0
- package/dist/runtime/hooks.js.map +1 -0
- package/dist/runtime/hooks.test.d.ts +7 -0
- package/dist/runtime/hooks.test.d.ts.map +1 -0
- package/dist/runtime/hooks.test.js +73 -0
- package/dist/runtime/hooks.test.js.map +1 -0
- package/dist/runtime/model-profile.d.ts +42 -0
- package/dist/runtime/model-profile.d.ts.map +1 -0
- package/dist/runtime/model-profile.js +84 -0
- package/dist/runtime/model-profile.js.map +1 -0
- package/dist/runtime/prompt.d.ts +38 -0
- package/dist/runtime/prompt.d.ts.map +1 -0
- package/dist/runtime/prompt.js +152 -0
- package/dist/runtime/prompt.js.map +1 -0
- package/dist/runtime/prompt.md +64 -0
- package/dist/runtime/prompt.test.d.ts +7 -0
- package/dist/runtime/prompt.test.d.ts.map +1 -0
- package/dist/runtime/prompt.test.js +38 -0
- package/dist/runtime/prompt.test.js.map +1 -0
- package/dist/runtime/react-loop.d.ts +82 -0
- package/dist/runtime/react-loop.d.ts.map +1 -0
- package/dist/runtime/react-loop.js +311 -0
- package/dist/runtime/react-loop.js.map +1 -0
- package/dist/runtime/react-loop.test.d.ts +7 -0
- package/dist/runtime/react-loop.test.d.ts.map +1 -0
- package/dist/runtime/react-loop.test.js +78 -0
- package/dist/runtime/react-loop.test.js.map +1 -0
- package/dist/runtime/session.d.ts +109 -0
- package/dist/runtime/session.d.ts.map +1 -0
- package/dist/runtime/session.js +252 -0
- package/dist/runtime/session.js.map +1 -0
- package/dist/runtime/skills.d.ts +36 -0
- package/dist/runtime/skills.d.ts.map +1 -0
- package/dist/runtime/skills.js +187 -0
- package/dist/runtime/skills.js.map +1 -0
- package/dist/runtime/skills.test.d.ts +7 -0
- package/dist/runtime/skills.test.d.ts.map +1 -0
- package/dist/runtime/skills.test.js +92 -0
- package/dist/runtime/skills.test.js.map +1 -0
- package/dist/tools/approval.d.ts +61 -0
- package/dist/tools/approval.d.ts.map +1 -0
- package/dist/tools/approval.js +119 -0
- package/dist/tools/approval.js.map +1 -0
- package/dist/tools/approval.test.d.ts +9 -0
- package/dist/tools/approval.test.d.ts.map +1 -0
- package/dist/tools/approval.test.js +112 -0
- package/dist/tools/approval.test.js.map +1 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +58 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit-file.d.ts +6 -0
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +58 -0
- package/dist/tools/edit-file.js.map +1 -0
- package/dist/tools/get-memory.d.ts +9 -0
- package/dist/tools/get-memory.d.ts.map +1 -0
- package/dist/tools/get-memory.js +56 -0
- package/dist/tools/get-memory.js.map +1 -0
- package/dist/tools/list-directory.d.ts +6 -0
- package/dist/tools/list-directory.d.ts.map +1 -0
- package/dist/tools/list-directory.js +68 -0
- package/dist/tools/list-directory.js.map +1 -0
- package/dist/tools/mcp-client.d.ts +74 -0
- package/dist/tools/mcp-client.d.ts.map +1 -0
- package/dist/tools/mcp-client.js +129 -0
- package/dist/tools/mcp-client.js.map +1 -0
- package/dist/tools/mcp-config.d.ts +31 -0
- package/dist/tools/mcp-config.d.ts.map +1 -0
- package/dist/tools/mcp-config.js +55 -0
- package/dist/tools/mcp-config.js.map +1 -0
- package/dist/tools/mcp-registry.d.ts +39 -0
- package/dist/tools/mcp-registry.d.ts.map +1 -0
- package/dist/tools/mcp-registry.js +88 -0
- package/dist/tools/mcp-registry.js.map +1 -0
- package/dist/tools/orchestrator.d.ts +52 -0
- package/dist/tools/orchestrator.d.ts.map +1 -0
- package/dist/tools/orchestrator.js +190 -0
- package/dist/tools/orchestrator.js.map +1 -0
- package/dist/tools/orchestrator.test.d.ts +8 -0
- package/dist/tools/orchestrator.test.d.ts.map +1 -0
- package/dist/tools/orchestrator.test.js +122 -0
- package/dist/tools/orchestrator.test.js.map +1 -0
- package/dist/tools/read-file.d.ts +6 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +50 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/registry.d.ts +55 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +75 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/registry.test.d.ts +8 -0
- package/dist/tools/registry.test.d.ts.map +1 -0
- package/dist/tools/registry.test.js +100 -0
- package/dist/tools/registry.test.js.map +1 -0
- package/dist/tools/router.d.ts +62 -0
- package/dist/tools/router.d.ts.map +1 -0
- package/dist/tools/router.js +119 -0
- package/dist/tools/router.js.map +1 -0
- package/dist/tools/router.test.d.ts +7 -0
- package/dist/tools/router.test.d.ts.map +1 -0
- package/dist/tools/router.test.js +102 -0
- package/dist/tools/router.test.js.map +1 -0
- package/dist/tools/safety.d.ts +16 -0
- package/dist/tools/safety.d.ts.map +1 -0
- package/dist/tools/safety.js +81 -0
- package/dist/tools/safety.js.map +1 -0
- package/dist/tools/safety.test.d.ts +7 -0
- package/dist/tools/safety.test.d.ts.map +1 -0
- package/dist/tools/safety.test.js +104 -0
- package/dist/tools/safety.test.js.map +1 -0
- package/dist/tools/search-files.d.ts +9 -0
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +114 -0
- package/dist/tools/search-files.js.map +1 -0
- package/dist/tools/update-plan.d.ts +9 -0
- package/dist/tools/update-plan.d.ts.map +1 -0
- package/dist/tools/update-plan.js +99 -0
- package/dist/tools/update-plan.js.map +1 -0
- package/dist/tools/write-file.d.ts +6 -0
- package/dist/tools/write-file.d.ts.map +1 -0
- package/dist/tools/write-file.js +41 -0
- package/dist/tools/write-file.js.map +1 -0
- package/dist/tui/app.d.ts +31 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +121 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/chatwidget/markdown_renderer.d.ts +20 -0
- package/dist/tui/chatwidget/markdown_renderer.d.ts.map +1 -0
- package/dist/tui/chatwidget/markdown_renderer.js +188 -0
- package/dist/tui/chatwidget/markdown_renderer.js.map +1 -0
- package/dist/tui/cjk_text.d.ts +25 -0
- package/dist/tui/cjk_text.d.ts.map +1 -0
- package/dist/tui/cjk_text.js +84 -0
- package/dist/tui/cjk_text.js.map +1 -0
- package/dist/tui/cjk_text.test.d.ts +2 -0
- package/dist/tui/cjk_text.test.d.ts.map +1 -0
- package/dist/tui/cjk_text.test.js +62 -0
- package/dist/tui/cjk_text.test.js.map +1 -0
- package/dist/tui/composer_input.d.ts +31 -0
- package/dist/tui/composer_input.d.ts.map +1 -0
- package/dist/tui/composer_input.js +184 -0
- package/dist/tui/composer_input.js.map +1 -0
- package/dist/tui/composer_input.test.d.ts +2 -0
- package/dist/tui/composer_input.test.d.ts.map +1 -0
- package/dist/tui/composer_input.test.js +87 -0
- package/dist/tui/composer_input.test.js.map +1 -0
- package/dist/tui/input.d.ts +21 -0
- package/dist/tui/input.d.ts.map +1 -0
- package/dist/tui/input.js +166 -0
- package/dist/tui/input.js.map +1 -0
- package/dist/tui/output.d.ts +17 -0
- package/dist/tui/output.d.ts.map +1 -0
- package/dist/tui/output.js +104 -0
- package/dist/tui/output.js.map +1 -0
- package/dist/tui/state/chat_timeline.d.ts +50 -0
- package/dist/tui/state/chat_timeline.d.ts.map +1 -0
- package/dist/tui/state/chat_timeline.js +129 -0
- package/dist/tui/state/chat_timeline.js.map +1 -0
- package/dist/tui/types.d.ts +45 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +14 -0
- package/dist/tui/types.js.map +1 -0
- package/dist/types.d.ts +435 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/tokenizer.d.ts +21 -0
- package/dist/utils/tokenizer.d.ts.map +1 -0
- package/dist/utils/tokenizer.js +71 -0
- package/dist/utils/tokenizer.js.map +1 -0
- package/dist/utils/tokenizer.test.d.ts +7 -0
- package/dist/utils/tokenizer.test.d.ts.map +1 -0
- package/dist/utils/tokenizer.test.js +51 -0
- package/dist/utils/tokenizer.test.js.map +1 -0
- package/package.json +41 -0
- package/src/runtime/prompt.md +64 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Session 类 — 管理多轮对话状态。
|
|
3
|
+
*
|
|
4
|
+
* Phase 2:持有对话历史和 LLM 调用函数,
|
|
5
|
+
* 委托 `runTurn()` 执行 ReAct 循环。
|
|
6
|
+
*
|
|
7
|
+
* Phase 6:新增上下文压缩能力:
|
|
8
|
+
* - 接受 contextWindow / compactThreshold / tokenCounter 配置
|
|
9
|
+
* - 暴露 compactHistory() 公开方法
|
|
10
|
+
* - 将压缩相关依赖传递给 runTurn()
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
import { runTurn } from './react-loop.js';
|
|
14
|
+
import { CONTEXT_COMPACTION_SYSTEM_PROMPT, buildCompactionUserPrompt, buildCompactedHistory, } from './compaction.js';
|
|
15
|
+
import { buildHookRunners, runHook } from './hooks.js';
|
|
16
|
+
import { createHistoryEvent } from './history.js';
|
|
17
|
+
// ─── Session 类 ──────────────────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Agent Session 类。
|
|
20
|
+
*
|
|
21
|
+
* 用法:
|
|
22
|
+
* ```ts
|
|
23
|
+
* const session = new Session({ callLLM, systemPrompt: '...' })
|
|
24
|
+
* const result = await session.runTurn('你好')
|
|
25
|
+
* console.log(result.finalText)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class Session {
|
|
29
|
+
/** Session 唯一标识。 */
|
|
30
|
+
id;
|
|
31
|
+
/** 对话历史。 */
|
|
32
|
+
history = [];
|
|
33
|
+
/** 轮次计数器。 */
|
|
34
|
+
turnIndex = 0;
|
|
35
|
+
/** 获取当前轮次。 */
|
|
36
|
+
get currentTurn() {
|
|
37
|
+
return this.turnIndex;
|
|
38
|
+
}
|
|
39
|
+
/** LLM 调用函数。 */
|
|
40
|
+
callLLM;
|
|
41
|
+
/** 工具执行函数。 */
|
|
42
|
+
executeTool;
|
|
43
|
+
/** Token 计数器。 */
|
|
44
|
+
tokenCounter;
|
|
45
|
+
/** 上下文窗口大小。 */
|
|
46
|
+
contextWindow;
|
|
47
|
+
/** 自动压缩阈值百分比。 */
|
|
48
|
+
compactThreshold;
|
|
49
|
+
/** Hook 注册表。 */
|
|
50
|
+
hookRunners;
|
|
51
|
+
/** 清除 once 授权回调。 */
|
|
52
|
+
clearApprovalsFn;
|
|
53
|
+
/** 历史事件写入器。 */
|
|
54
|
+
historySink;
|
|
55
|
+
/** 流式 chunk 回调。 */
|
|
56
|
+
onAssistantChunk;
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.id = options.sessionId ?? randomUUID();
|
|
59
|
+
this.callLLM = options.callLLM;
|
|
60
|
+
this.executeTool = options.executeTool;
|
|
61
|
+
this.tokenCounter = options.tokenCounter;
|
|
62
|
+
this.contextWindow = options.contextWindow ?? 128_000;
|
|
63
|
+
this.compactThreshold = options.compactThreshold ?? 80;
|
|
64
|
+
this.hookRunners = buildHookRunners(options.hooks, options.middlewares);
|
|
65
|
+
this.clearApprovalsFn = options.clearApprovalsFn;
|
|
66
|
+
this.historySink = options.historySink;
|
|
67
|
+
this.onAssistantChunk = options.onAssistantChunk;
|
|
68
|
+
// 如果提供了系统提示词,作为历史的第一条消息
|
|
69
|
+
if (options.systemPrompt) {
|
|
70
|
+
this.history.push({
|
|
71
|
+
role: 'system',
|
|
72
|
+
content: options.systemPrompt,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 执行一轮对话。
|
|
78
|
+
*
|
|
79
|
+
* 委托给 react-loop.ts 的 runTurn(),传入当前 history。
|
|
80
|
+
* history 会被 runTurn() 就地修改(追加用户/助手/工具消息)。
|
|
81
|
+
*/
|
|
82
|
+
async runTurn(input) {
|
|
83
|
+
this.turnIndex += 1;
|
|
84
|
+
// 写入 turn_start 事件
|
|
85
|
+
if (this.historySink) {
|
|
86
|
+
await this.historySink.append(createHistoryEvent({
|
|
87
|
+
sessionId: this.id,
|
|
88
|
+
type: 'turn_start',
|
|
89
|
+
turn: this.turnIndex,
|
|
90
|
+
content: input,
|
|
91
|
+
role: 'user',
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
const result = await runTurn(input, {
|
|
95
|
+
history: this.history,
|
|
96
|
+
callLLM: this.callLLM,
|
|
97
|
+
executeTool: this.executeTool,
|
|
98
|
+
tokenCounter: this.tokenCounter,
|
|
99
|
+
contextWindow: this.contextWindow,
|
|
100
|
+
compactThreshold: this.compactThreshold,
|
|
101
|
+
compactFn: () => this.compactHistory('auto'),
|
|
102
|
+
hookRunners: this.hookRunners,
|
|
103
|
+
sessionId: this.id,
|
|
104
|
+
turnIndex: this.turnIndex,
|
|
105
|
+
clearApprovalsFn: this.clearApprovalsFn,
|
|
106
|
+
onAssistantChunk: this.onAssistantChunk,
|
|
107
|
+
});
|
|
108
|
+
// 写入 final 事件
|
|
109
|
+
if (this.historySink) {
|
|
110
|
+
await this.historySink.append(createHistoryEvent({
|
|
111
|
+
sessionId: this.id,
|
|
112
|
+
type: 'final',
|
|
113
|
+
turn: this.turnIndex,
|
|
114
|
+
content: result.finalText,
|
|
115
|
+
role: 'assistant',
|
|
116
|
+
meta: {
|
|
117
|
+
status: result.status,
|
|
118
|
+
steps: result.steps.length,
|
|
119
|
+
tokenUsage: result.tokenUsage,
|
|
120
|
+
},
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
/** 返回当前对话历史的副本。 */
|
|
126
|
+
getHistory() {
|
|
127
|
+
return [...this.history];
|
|
128
|
+
}
|
|
129
|
+
/** 获取当前轮次数。 */
|
|
130
|
+
getTurnIndex() {
|
|
131
|
+
return this.turnIndex;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 手动或自动压缩对话历史。
|
|
135
|
+
*
|
|
136
|
+
* 流程:
|
|
137
|
+
* 1. 用 tokenCounter 计算当前 token 数
|
|
138
|
+
* 2. 提取 system 消息外的历史
|
|
139
|
+
* 3. 调用 LLM 生成摘要
|
|
140
|
+
* 4. 用摘要重建历史
|
|
141
|
+
*/
|
|
142
|
+
async compactHistory(reason = 'manual') {
|
|
143
|
+
const thresholdTokens = Math.floor(this.contextWindow * (this.compactThreshold / 100));
|
|
144
|
+
// 无 tokenCounter 时跳过
|
|
145
|
+
if (!this.tokenCounter) {
|
|
146
|
+
return {
|
|
147
|
+
reason,
|
|
148
|
+
status: 'skipped',
|
|
149
|
+
beforeTokens: 0,
|
|
150
|
+
afterTokens: 0,
|
|
151
|
+
thresholdTokens,
|
|
152
|
+
reductionPercent: 0,
|
|
153
|
+
errorMessage: 'No tokenCounter configured',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const beforeTokens = this.tokenCounter.countMessages(this.history);
|
|
157
|
+
const systemMessage = this.history[0]?.role === 'system' ? this.history[0] : undefined;
|
|
158
|
+
const historyWithoutSystem = systemMessage
|
|
159
|
+
? this.history.slice(1)
|
|
160
|
+
: this.history.slice();
|
|
161
|
+
// 无可压缩内容时跳过
|
|
162
|
+
if (!historyWithoutSystem.length) {
|
|
163
|
+
return {
|
|
164
|
+
reason,
|
|
165
|
+
status: 'skipped',
|
|
166
|
+
beforeTokens,
|
|
167
|
+
afterTokens: beforeTokens,
|
|
168
|
+
thresholdTokens,
|
|
169
|
+
reductionPercent: 0,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
// 调用 LLM 生成摘要
|
|
174
|
+
const response = await this.callLLM([
|
|
175
|
+
{ role: 'system', content: CONTEXT_COMPACTION_SYSTEM_PROMPT },
|
|
176
|
+
{ role: 'user', content: buildCompactionUserPrompt(historyWithoutSystem) },
|
|
177
|
+
]);
|
|
178
|
+
// 提取摘要文本
|
|
179
|
+
const summaryText = response.content
|
|
180
|
+
.filter((b) => b.type === 'text')
|
|
181
|
+
.map((b) => b.text)
|
|
182
|
+
.join('')
|
|
183
|
+
.trim();
|
|
184
|
+
if (!summaryText) {
|
|
185
|
+
throw new Error('Compaction model returned an empty summary.');
|
|
186
|
+
}
|
|
187
|
+
// 重建历史
|
|
188
|
+
const compactedHistory = buildCompactedHistory(systemMessage, summaryText);
|
|
189
|
+
const afterTokens = this.tokenCounter.countMessages(compactedHistory);
|
|
190
|
+
this.history.splice(0, this.history.length, ...compactedHistory);
|
|
191
|
+
const reductionPercent = beforeTokens > 0
|
|
192
|
+
? Math.max(0, Math.round(((beforeTokens - afterTokens) / beforeTokens) *
|
|
193
|
+
10_000) / 100)
|
|
194
|
+
: 0;
|
|
195
|
+
// Phase 7:发射 onContextCompacted Hook
|
|
196
|
+
await runHook(this.hookRunners, 'onContextCompacted', {
|
|
197
|
+
sessionId: this.id,
|
|
198
|
+
turn: this.turnIndex,
|
|
199
|
+
reason,
|
|
200
|
+
status: 'success',
|
|
201
|
+
beforeTokens,
|
|
202
|
+
afterTokens,
|
|
203
|
+
thresholdTokens,
|
|
204
|
+
reductionPercent,
|
|
205
|
+
summary: summaryText,
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
reason,
|
|
209
|
+
status: 'success',
|
|
210
|
+
beforeTokens,
|
|
211
|
+
afterTokens,
|
|
212
|
+
thresholdTokens,
|
|
213
|
+
reductionPercent,
|
|
214
|
+
summary: summaryText,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
return {
|
|
219
|
+
reason,
|
|
220
|
+
status: 'failed',
|
|
221
|
+
beforeTokens,
|
|
222
|
+
afterTokens: beforeTokens,
|
|
223
|
+
thresholdTokens,
|
|
224
|
+
reductionPercent: 0,
|
|
225
|
+
errorMessage: err.message,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 为当前会话生成标题(Phase 7)。
|
|
231
|
+
*/
|
|
232
|
+
async generateTitle() {
|
|
233
|
+
// TODO: 实际调用 LLM 生成标题
|
|
234
|
+
const title = `Session ${this.id.split('-')[0]}`;
|
|
235
|
+
// 发射 onTitleGenerated Hook
|
|
236
|
+
await runHook(this.hookRunners, 'onTitleGenerated', {
|
|
237
|
+
sessionId: this.id,
|
|
238
|
+
turn: this.turnIndex,
|
|
239
|
+
title,
|
|
240
|
+
originalPrompt: this.history[1]?.content || '',
|
|
241
|
+
});
|
|
242
|
+
return title;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 允许外部(如 index.ts 的回调)触发内部 Hook。
|
|
246
|
+
* @internal
|
|
247
|
+
*/
|
|
248
|
+
async _runHook(event, payload) {
|
|
249
|
+
await runHook(this.hookRunners, event, payload);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/runtime/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Skills 系统 — 技能发现、YAML frontmatter 解析、prompt 注入。
|
|
3
|
+
*
|
|
4
|
+
* Phase 10:让 Agent 能发现和使用项目/用户级 SKILL.md 文件。
|
|
5
|
+
* 1. 扫描搜索根(项目 .agents/skills/ + 用户 ~/.cclin/skills/)
|
|
6
|
+
* 2. 解析 SKILL.md 的 YAML frontmatter(name + description)
|
|
7
|
+
* 3. 渲染 skills section 注入系统提示词
|
|
8
|
+
*/
|
|
9
|
+
export type SkillMetadata = {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
path: string;
|
|
13
|
+
};
|
|
14
|
+
type LoadSkillsOptions = {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
homeDir?: string;
|
|
17
|
+
cclinHome?: string;
|
|
18
|
+
maxSkills?: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 解析 SKILL.md 文件内容,提取 name 和 description。
|
|
22
|
+
* 返回 SkillMetadata 或 null(解析失败时)。
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseSkillFile(content: string, path: string): SkillMetadata | null;
|
|
25
|
+
/**
|
|
26
|
+
* 加载所有可用的 Skills。
|
|
27
|
+
* 扫描项目级和用户级目录,发现并解析 SKILL.md 文件。
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadSkills(options?: LoadSkillsOptions): Promise<SkillMetadata[]>;
|
|
30
|
+
/**
|
|
31
|
+
* 将 skills 列表渲染为可注入 system prompt 的文本。
|
|
32
|
+
* 如果没有 skills 则返回 null。
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderSkillsSection(skills: SkillMetadata[]): string | null;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/runtime/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,MAAM,aAAa,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACf,CAAA;AAED,KAAK,iBAAiB,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AA0DD;;;GAGG;AACH,wBAAgB,cAAc,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACb,aAAa,GAAG,IAAI,CAYtB;AAwED;;;GAGG;AACH,wBAAsB,UAAU,CAC5B,OAAO,GAAE,iBAAsB,GAChC,OAAO,CAAC,aAAa,EAAE,CAAC,CAmB1B;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,CAC/B,MAAM,EAAE,aAAa,EAAE,GACxB,MAAM,GAAG,IAAI,CAiBf"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Skills 系统 — 技能发现、YAML frontmatter 解析、prompt 注入。
|
|
3
|
+
*
|
|
4
|
+
* Phase 10:让 Agent 能发现和使用项目/用户级 SKILL.md 文件。
|
|
5
|
+
* 1. 扫描搜索根(项目 .agents/skills/ + 用户 ~/.cclin/skills/)
|
|
6
|
+
* 2. 解析 SKILL.md 的 YAML frontmatter(name + description)
|
|
7
|
+
* 3. 渲染 skills section 注入系统提示词
|
|
8
|
+
*/
|
|
9
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join, resolve } from 'node:path';
|
|
12
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
13
|
+
const SKILL_FILENAME = 'SKILL.md';
|
|
14
|
+
const MAX_SCAN_DEPTH = 4;
|
|
15
|
+
const DEFAULT_MAX_SKILLS = 100;
|
|
16
|
+
const MAX_NAME_LEN = 64;
|
|
17
|
+
const MAX_DESCRIPTION_LEN = 1024;
|
|
18
|
+
const SKILLS_USAGE_RULES = `### How to use skills
|
|
19
|
+
- If the user names a skill or the task clearly matches a skill's description, use that skill.
|
|
20
|
+
- To use a skill: open its \`SKILL.md\` with read_file, follow the instructions inside.
|
|
21
|
+
- If \`SKILL.md\` references relative paths, resolve them relative to the skill directory.
|
|
22
|
+
- Keep context small: only load files directly needed, don't bulk-load everything.
|
|
23
|
+
- When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
|
|
24
|
+
- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.`;
|
|
25
|
+
// ─── Frontmatter Parsing ─────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* 从 Markdown 内容中提取 YAML frontmatter 块。
|
|
28
|
+
* frontmatter 必须以 `---` 开头和结尾。
|
|
29
|
+
*/
|
|
30
|
+
function extractFrontmatter(content) {
|
|
31
|
+
const lines = content.split(/\r?\n/);
|
|
32
|
+
if (lines[0]?.trim() !== '---')
|
|
33
|
+
return null;
|
|
34
|
+
const fmLines = [];
|
|
35
|
+
let foundClosing = false;
|
|
36
|
+
for (const line of lines.slice(1)) {
|
|
37
|
+
if (line.trim() === '---') {
|
|
38
|
+
foundClosing = true;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
fmLines.push(line);
|
|
42
|
+
}
|
|
43
|
+
if (!foundClosing || fmLines.length === 0)
|
|
44
|
+
return null;
|
|
45
|
+
return fmLines.join('\n');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 从 frontmatter 中解析指定 key 的单行值。
|
|
49
|
+
* 支持带/不带引号的值:`name: "foo"` 或 `name: foo`
|
|
50
|
+
*/
|
|
51
|
+
function parseFrontmatterValue(fm, key) {
|
|
52
|
+
const pattern = new RegExp(`^${key}\\s*:\\s*(.+?)\\s*$`, 'm');
|
|
53
|
+
const match = fm.match(pattern);
|
|
54
|
+
if (!match?.[1])
|
|
55
|
+
return null;
|
|
56
|
+
let val = match[1].trim();
|
|
57
|
+
// Strip quotes
|
|
58
|
+
if (val.length >= 2 &&
|
|
59
|
+
((val.startsWith('"') && val.endsWith('"')) ||
|
|
60
|
+
(val.startsWith("'") && val.endsWith("'")))) {
|
|
61
|
+
val = val.slice(1, -1);
|
|
62
|
+
}
|
|
63
|
+
return val.split(/\s+/).join(' '); // normalize whitespace
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 解析 SKILL.md 文件内容,提取 name 和 description。
|
|
67
|
+
* 返回 SkillMetadata 或 null(解析失败时)。
|
|
68
|
+
*/
|
|
69
|
+
export function parseSkillFile(content, path) {
|
|
70
|
+
const fm = extractFrontmatter(content);
|
|
71
|
+
if (!fm)
|
|
72
|
+
return null;
|
|
73
|
+
const name = parseFrontmatterValue(fm, 'name');
|
|
74
|
+
const description = parseFrontmatterValue(fm, 'description');
|
|
75
|
+
if (!name || !description)
|
|
76
|
+
return null;
|
|
77
|
+
if (name.length > MAX_NAME_LEN || description.length > MAX_DESCRIPTION_LEN) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return { name, description, path };
|
|
81
|
+
}
|
|
82
|
+
// ─── Skill Discovery ─────────────────────────────────────────────────────────
|
|
83
|
+
/** Directories to skip during recursive scanning. */
|
|
84
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '__pycache__']);
|
|
85
|
+
/**
|
|
86
|
+
* Recursively scan a directory for SKILL.md files.
|
|
87
|
+
* Depth-limited to avoid scanning deeply nested directories.
|
|
88
|
+
*/
|
|
89
|
+
async function scanDirectory(dir, depth, results, maxSkills, seenPaths) {
|
|
90
|
+
if (depth > MAX_SCAN_DEPTH || results.length >= maxSkills)
|
|
91
|
+
return;
|
|
92
|
+
let entries;
|
|
93
|
+
try {
|
|
94
|
+
entries = await readdir(dir, {
|
|
95
|
+
withFileTypes: true,
|
|
96
|
+
encoding: 'utf-8',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return; // directory not readable
|
|
101
|
+
}
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
if (results.length >= maxSkills)
|
|
104
|
+
return;
|
|
105
|
+
const fullPath = join(dir, entry.name);
|
|
106
|
+
if (entry.isFile() && entry.name === SKILL_FILENAME) {
|
|
107
|
+
const normalizedPath = resolve(fullPath);
|
|
108
|
+
if (seenPaths.has(normalizedPath))
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
const content = await readFile(normalizedPath, 'utf-8');
|
|
112
|
+
const parsed = parseSkillFile(content, normalizedPath);
|
|
113
|
+
if (parsed) {
|
|
114
|
+
results.push(parsed);
|
|
115
|
+
seenPaths.add(normalizedPath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue; // file not readable
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
|
|
123
|
+
await scanDirectory(fullPath, depth + 1, results, maxSkills, seenPaths);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the skill root directories to scan.
|
|
129
|
+
* 1. Project-level: <cwd>/.agents/skills/
|
|
130
|
+
* 2. User-level: ~/.cclin/skills/
|
|
131
|
+
*/
|
|
132
|
+
function resolveSkillRoots(options) {
|
|
133
|
+
const cwd = options.cwd ?? process.cwd();
|
|
134
|
+
const home = options.homeDir ?? homedir();
|
|
135
|
+
const cclinHome = options.cclinHome
|
|
136
|
+
?? process.env.CCLIN_HOME
|
|
137
|
+
?? join(home, '.cclin');
|
|
138
|
+
return [
|
|
139
|
+
join(cwd, '.agents', 'skills'),
|
|
140
|
+
join(cclinHome, 'skills'),
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 加载所有可用的 Skills。
|
|
145
|
+
* 扫描项目级和用户级目录,发现并解析 SKILL.md 文件。
|
|
146
|
+
*/
|
|
147
|
+
export async function loadSkills(options = {}) {
|
|
148
|
+
const roots = resolveSkillRoots(options);
|
|
149
|
+
const maxSkills = Math.max(1, options.maxSkills ?? DEFAULT_MAX_SKILLS);
|
|
150
|
+
const skills = [];
|
|
151
|
+
const seenPaths = new Set();
|
|
152
|
+
for (const root of roots) {
|
|
153
|
+
// Check if root directory exists before scanning
|
|
154
|
+
try {
|
|
155
|
+
const info = await stat(root);
|
|
156
|
+
if (!info.isDirectory())
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
continue; // root doesn't exist
|
|
161
|
+
}
|
|
162
|
+
await scanDirectory(root, 0, skills, maxSkills, seenPaths);
|
|
163
|
+
}
|
|
164
|
+
return skills;
|
|
165
|
+
}
|
|
166
|
+
// ─── Prompt Rendering ────────────────────────────────────────────────────────
|
|
167
|
+
/**
|
|
168
|
+
* 将 skills 列表渲染为可注入 system prompt 的文本。
|
|
169
|
+
* 如果没有 skills 则返回 null。
|
|
170
|
+
*/
|
|
171
|
+
export function renderSkillsSection(skills) {
|
|
172
|
+
if (skills.length === 0)
|
|
173
|
+
return null;
|
|
174
|
+
const lines = [];
|
|
175
|
+
lines.push('## Skills');
|
|
176
|
+
lines.push('A skill is a set of local instructions stored in a `SKILL.md` file. '
|
|
177
|
+
+ 'Below is the list of available skills.');
|
|
178
|
+
lines.push('');
|
|
179
|
+
lines.push('### Available skills');
|
|
180
|
+
for (const skill of skills) {
|
|
181
|
+
lines.push(`- **${skill.name}**: ${skill.description} (file: ${skill.path})`);
|
|
182
|
+
}
|
|
183
|
+
lines.push('');
|
|
184
|
+
lines.push(SKILLS_USAGE_RULES);
|
|
185
|
+
return lines.join('\n');
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/runtime/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.test.d.ts","sourceRoot":"","sources":["../../src/runtime/skills.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unit tests for Skills system (Phase 10).
|
|
3
|
+
*
|
|
4
|
+
* Tests: parseSkillFile, renderSkillsSection
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { parseSkillFile, renderSkillsSection } from './skills.js';
|
|
8
|
+
// ─── parseSkillFile ──────────────────────────────────────────────────────────
|
|
9
|
+
describe('parseSkillFile', () => {
|
|
10
|
+
it('should parse valid frontmatter with name and description', () => {
|
|
11
|
+
const content = `---
|
|
12
|
+
name: git-push
|
|
13
|
+
description: Push all changes to remote repository
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Git Push Skill
|
|
17
|
+
|
|
18
|
+
Instructions here...`;
|
|
19
|
+
const result = parseSkillFile(content, '/skills/git-push/SKILL.md');
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
name: 'git-push',
|
|
22
|
+
description: 'Push all changes to remote repository',
|
|
23
|
+
path: '/skills/git-push/SKILL.md',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it('should handle quoted values', () => {
|
|
27
|
+
const content = `---
|
|
28
|
+
name: "my skill"
|
|
29
|
+
description: 'Does cool things'
|
|
30
|
+
---
|
|
31
|
+
Body`;
|
|
32
|
+
const result = parseSkillFile(content, '/test/SKILL.md');
|
|
33
|
+
expect(result).not.toBeNull();
|
|
34
|
+
expect(result.name).toBe('my skill');
|
|
35
|
+
expect(result.description).toBe('Does cool things');
|
|
36
|
+
});
|
|
37
|
+
it('should return null when no frontmatter', () => {
|
|
38
|
+
const content = '# Just a header\nSome content';
|
|
39
|
+
expect(parseSkillFile(content, '/test')).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
it('should return null when missing name', () => {
|
|
42
|
+
const content = `---
|
|
43
|
+
description: Only description
|
|
44
|
+
---
|
|
45
|
+
Body`;
|
|
46
|
+
expect(parseSkillFile(content, '/test')).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
it('should return null when missing description', () => {
|
|
49
|
+
const content = `---
|
|
50
|
+
name: only-name
|
|
51
|
+
---
|
|
52
|
+
Body`;
|
|
53
|
+
expect(parseSkillFile(content, '/test')).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
it('should return null when frontmatter is not closed', () => {
|
|
56
|
+
const content = `---
|
|
57
|
+
name: broken
|
|
58
|
+
description: no closing fence
|
|
59
|
+
Body here`;
|
|
60
|
+
expect(parseSkillFile(content, '/test')).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
it('should normalize whitespace in values', () => {
|
|
63
|
+
const content = `---
|
|
64
|
+
name: multi word name
|
|
65
|
+
description: has extra spaces
|
|
66
|
+
---
|
|
67
|
+
Body`;
|
|
68
|
+
const result = parseSkillFile(content, '/test');
|
|
69
|
+
expect(result.name).toBe('multi word name');
|
|
70
|
+
expect(result.description).toBe('has extra spaces');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
// ─── renderSkillsSection ─────────────────────────────────────────────────────
|
|
74
|
+
describe('renderSkillsSection', () => {
|
|
75
|
+
it('should return null for empty skills array', () => {
|
|
76
|
+
expect(renderSkillsSection([])).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
it('should render all skills with name and description', () => {
|
|
79
|
+
const skills = [
|
|
80
|
+
{ name: 'git-push', description: 'Push changes', path: '/a' },
|
|
81
|
+
{ name: 'blog-writer', description: 'Write blogs', path: '/b' },
|
|
82
|
+
];
|
|
83
|
+
const result = renderSkillsSection(skills);
|
|
84
|
+
expect(result).toContain('## Skills');
|
|
85
|
+
expect(result).toContain('**git-push**');
|
|
86
|
+
expect(result).toContain('Push changes');
|
|
87
|
+
expect(result).toContain('**blog-writer**');
|
|
88
|
+
expect(result).toContain('Write blogs');
|
|
89
|
+
expect(result).toContain('### How to use skills');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=skills.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.test.js","sourceRoot":"","sources":["../../src/runtime/skills.test.ts"],"names":[],"mappings":"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"}
|