@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,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 上下文压缩模块 — LLM 驱动的对话历史摘要。
|
|
3
|
+
*
|
|
4
|
+
* Phase 6:当对话历史的 token 数接近上下文窗口限制时,
|
|
5
|
+
* 调用 LLM 对历史生成结构化摘要,替换原始历史以释放空间。
|
|
6
|
+
*
|
|
7
|
+
* 设计参考 memo-code 的 compact_prompt.ts,但做了精简:
|
|
8
|
+
* - 去掉了"保留最近 N 条 user 消息"的逻辑(简化)
|
|
9
|
+
* - 保留了消息转文本、摘要检测、历史重建的核心流程
|
|
10
|
+
*/
|
|
11
|
+
import type { ChatMessage } from '../types.js';
|
|
12
|
+
/**
|
|
13
|
+
* 给 LLM 的压缩指令。
|
|
14
|
+
*
|
|
15
|
+
* 这个 prompt 告诉 LLM:"你在做一次检查点压缩,
|
|
16
|
+
* 帮下一个 LLM 接手工作"。关键点:
|
|
17
|
+
* - 保留进展和关键决策
|
|
18
|
+
* - 保留约束和用户偏好
|
|
19
|
+
* - 清晰列出后续步骤
|
|
20
|
+
*/
|
|
21
|
+
export declare const CONTEXT_COMPACTION_SYSTEM_PROMPT = "You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.\n\nInclude:\n- Current progress and key decisions made\n- Important context, constraints, or user preferences\n- What remains to be done (clear next steps)\n- Any critical data, examples, or references needed to continue\n\nBe concise, structured, and focused on helping the next LLM seamlessly continue the work.";
|
|
22
|
+
/**
|
|
23
|
+
* 摘要消息的识别前缀。
|
|
24
|
+
*
|
|
25
|
+
* 压缩后的摘要以这段文字开头,
|
|
26
|
+
* 让后续的压缩轮次能识别"这是之前的摘要,不是原始对话"。
|
|
27
|
+
*/
|
|
28
|
+
export declare const CONTEXT_SUMMARY_PREFIX = "Another language model started to solve this problem and produced a summary of its thinking process. Use this summary to continue the task without redoing completed work.";
|
|
29
|
+
/**
|
|
30
|
+
* 判断消息是否为之前压缩生成的摘要。
|
|
31
|
+
*
|
|
32
|
+
* 通过检查 user 消息是否以摘要前缀开头来识别。
|
|
33
|
+
* 压缩时需要跳过已有的摘要,避免"摘要套摘要"。
|
|
34
|
+
*/
|
|
35
|
+
export declare function isContextSummaryMessage(message: ChatMessage): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* 构建发给 LLM 的压缩请求(user prompt 部分)。
|
|
38
|
+
*
|
|
39
|
+
* 将整个对话历史转为文本格式,让 LLM 阅读后生成摘要。
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildCompactionUserPrompt(messages: ChatMessage[]): string;
|
|
42
|
+
/**
|
|
43
|
+
* 用压缩摘要重建历史数组。
|
|
44
|
+
*
|
|
45
|
+
* 新历史结构:[system (如有)] + [摘要消息]
|
|
46
|
+
* 这样历史从完整对话变为一条摘要,大幅减少 token 数。
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildCompactedHistory(systemMessage: ChatMessage | undefined, summary: string): ChatMessage[];
|
|
49
|
+
//# sourceMappingURL=compaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/runtime/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAO9C;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,sbAQ6C,CAAA;AAE1F;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,+KAC6I,CAAA;AA2ChL;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAGrE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACrC,QAAQ,EAAE,WAAW,EAAE,GACxB,MAAM,CAaR;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,aAAa,EAAE,WAAW,GAAG,SAAS,EACtC,OAAO,EAAE,MAAM,GAChB,WAAW,EAAE,CAUf"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 上下文压缩模块 — LLM 驱动的对话历史摘要。
|
|
3
|
+
*
|
|
4
|
+
* Phase 6:当对话历史的 token 数接近上下文窗口限制时,
|
|
5
|
+
* 调用 LLM 对历史生成结构化摘要,替换原始历史以释放空间。
|
|
6
|
+
*
|
|
7
|
+
* 设计参考 memo-code 的 compact_prompt.ts,但做了精简:
|
|
8
|
+
* - 去掉了"保留最近 N 条 user 消息"的逻辑(简化)
|
|
9
|
+
* - 保留了消息转文本、摘要检测、历史重建的核心流程
|
|
10
|
+
*/
|
|
11
|
+
/** 单条消息内容的最大字符数(超出时截断,避免压缩请求本身过大)。 */
|
|
12
|
+
const MAX_MESSAGE_CONTENT_CHARS = 4_000;
|
|
13
|
+
// ─── 压缩提示词 ─────────────────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* 给 LLM 的压缩指令。
|
|
16
|
+
*
|
|
17
|
+
* 这个 prompt 告诉 LLM:"你在做一次检查点压缩,
|
|
18
|
+
* 帮下一个 LLM 接手工作"。关键点:
|
|
19
|
+
* - 保留进展和关键决策
|
|
20
|
+
* - 保留约束和用户偏好
|
|
21
|
+
* - 清晰列出后续步骤
|
|
22
|
+
*/
|
|
23
|
+
export const CONTEXT_COMPACTION_SYSTEM_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
|
|
24
|
+
|
|
25
|
+
Include:
|
|
26
|
+
- Current progress and key decisions made
|
|
27
|
+
- Important context, constraints, or user preferences
|
|
28
|
+
- What remains to be done (clear next steps)
|
|
29
|
+
- Any critical data, examples, or references needed to continue
|
|
30
|
+
|
|
31
|
+
Be concise, structured, and focused on helping the next LLM seamlessly continue the work.`;
|
|
32
|
+
/**
|
|
33
|
+
* 摘要消息的识别前缀。
|
|
34
|
+
*
|
|
35
|
+
* 压缩后的摘要以这段文字开头,
|
|
36
|
+
* 让后续的压缩轮次能识别"这是之前的摘要,不是原始对话"。
|
|
37
|
+
*/
|
|
38
|
+
export const CONTEXT_SUMMARY_PREFIX = 'Another language model started to solve this problem and produced a summary of its thinking process. Use this summary to continue the task without redoing completed work.';
|
|
39
|
+
// ─── 工具函数 ────────────────────────────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* 截断过长的消息内容。
|
|
42
|
+
*
|
|
43
|
+
* 防止压缩请求本身太大(比如一个工具返回了 10 万字的文件内容)。
|
|
44
|
+
*/
|
|
45
|
+
function normalizeContent(content) {
|
|
46
|
+
const compact = content.replace(/\r\n/g, '\n').trim();
|
|
47
|
+
if (compact.length <= MAX_MESSAGE_CONTENT_CHARS) {
|
|
48
|
+
return compact;
|
|
49
|
+
}
|
|
50
|
+
return `${compact.slice(0, MAX_MESSAGE_CONTENT_CHARS)}...`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 将单条消息转换为人类可读的文本行。
|
|
54
|
+
*
|
|
55
|
+
* 格式:[index] ROLE (附加信息)\n内容
|
|
56
|
+
* 这个格式让 LLM 能清晰理解对话的结构和顺序。
|
|
57
|
+
*/
|
|
58
|
+
function messageToTranscriptLine(message, index) {
|
|
59
|
+
const role = message.role.toUpperCase();
|
|
60
|
+
if (message.role === 'assistant' && message.tool_calls?.length) {
|
|
61
|
+
const toolNames = message.tool_calls
|
|
62
|
+
.map((tc) => tc.function.name)
|
|
63
|
+
.join(', ');
|
|
64
|
+
return `[${index}] ${role} (tool_calls: ${toolNames})\n${normalizeContent(message.content)}`;
|
|
65
|
+
}
|
|
66
|
+
if (message.role === 'tool') {
|
|
67
|
+
const toolName = message.name ? ` (${message.name})` : '';
|
|
68
|
+
return `[${index}] ${role}${toolName}\n${normalizeContent(message.content)}`;
|
|
69
|
+
}
|
|
70
|
+
return `[${index}] ${role}\n${normalizeContent(message.content)}`;
|
|
71
|
+
}
|
|
72
|
+
// ─── 公开 API ────────────────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* 判断消息是否为之前压缩生成的摘要。
|
|
75
|
+
*
|
|
76
|
+
* 通过检查 user 消息是否以摘要前缀开头来识别。
|
|
77
|
+
* 压缩时需要跳过已有的摘要,避免"摘要套摘要"。
|
|
78
|
+
*/
|
|
79
|
+
export function isContextSummaryMessage(message) {
|
|
80
|
+
if (message.role !== 'user')
|
|
81
|
+
return false;
|
|
82
|
+
return message.content.startsWith(`${CONTEXT_SUMMARY_PREFIX}\n`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 构建发给 LLM 的压缩请求(user prompt 部分)。
|
|
86
|
+
*
|
|
87
|
+
* 将整个对话历史转为文本格式,让 LLM 阅读后生成摘要。
|
|
88
|
+
*/
|
|
89
|
+
export function buildCompactionUserPrompt(messages) {
|
|
90
|
+
const transcript = messages.length
|
|
91
|
+
? messages
|
|
92
|
+
.map((msg, i) => messageToTranscriptLine(msg, i))
|
|
93
|
+
.join('\n\n')
|
|
94
|
+
: '(empty)';
|
|
95
|
+
return [
|
|
96
|
+
'Conversation history to summarize:',
|
|
97
|
+
transcript,
|
|
98
|
+
'',
|
|
99
|
+
'Return only the summary body in plain text. Do not add markdown fences.',
|
|
100
|
+
].join('\n');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 用压缩摘要重建历史数组。
|
|
104
|
+
*
|
|
105
|
+
* 新历史结构:[system (如有)] + [摘要消息]
|
|
106
|
+
* 这样历史从完整对话变为一条摘要,大幅减少 token 数。
|
|
107
|
+
*/
|
|
108
|
+
export function buildCompactedHistory(systemMessage, summary) {
|
|
109
|
+
const summaryMessage = {
|
|
110
|
+
role: 'user',
|
|
111
|
+
content: `${CONTEXT_SUMMARY_PREFIX}\n${summary}`,
|
|
112
|
+
};
|
|
113
|
+
if (systemMessage) {
|
|
114
|
+
return [systemMessage, summaryMessage];
|
|
115
|
+
}
|
|
116
|
+
return [summaryMessage];
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=compaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../src/runtime/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,sCAAsC;AACtC,MAAM,yBAAyB,GAAG,KAAK,CAAA;AAEvC,0EAA0E;AAE1E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG;;;;;;;;0FAQ0C,CAAA;AAE1F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAC/B,4KAA4K,CAAA;AAEhL,4EAA4E;AAE5E;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,OAAe;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;IACrD,IAAI,OAAO,CAAC,MAAM,IAAI,yBAAyB,EAAE,CAAC;QAC9C,OAAO,OAAO,CAAA;IAClB,CAAC;IACD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,yBAAyB,CAAC,KAAK,CAAA;AAC9D,CAAC;AAED;;;;;GAKG;AACH,SAAS,uBAAuB,CAC5B,OAAoB,EACpB,KAAa;IAEb,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAA;IACvC,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU;aAC/B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;aAC7B,IAAI,CAAC,IAAI,CAAC,CAAA;QACf,OAAO,IAAI,KAAK,KAAK,IAAI,iBAAiB,SAAS,MAAM,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAA;IAChG,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;QACzD,OAAO,IAAI,KAAK,KAAK,IAAI,GAAG,QAAQ,KAAK,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAA;IAChF,CAAC;IACD,OAAO,IAAI,KAAK,KAAK,IAAI,KAAK,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAA;AACrE,CAAC;AAED,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAoB;IACxD,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAA;IACzC,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,sBAAsB,IAAI,CAAC,CAAA;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CACrC,QAAuB;IAEvB,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM;QAC9B,CAAC,CAAC,QAAQ;aACH,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;aAChD,IAAI,CAAC,MAAM,CAAC;QACnB,CAAC,CAAC,SAAS,CAAA;IAEf,OAAO;QACH,oCAAoC;QACpC,UAAU;QACV,EAAE;QACF,yEAAyE;KAC5E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACjC,aAAsC,EACtC,OAAe;IAEf,MAAM,cAAc,GAAgB;QAChC,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,GAAG,sBAAsB,KAAK,OAAO,EAAE;KACnD,CAAA;IAED,IAAI,aAAa,EAAE,CAAC;QAChB,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;IAC1C,CAAC;IACD,OAAO,CAAC,cAAc,CAAC,CAAA;AAC3B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.test.d.ts","sourceRoot":"","sources":["../../src/runtime/compaction.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unit tests for context compaction module (Phase 6).
|
|
3
|
+
*
|
|
4
|
+
* Tests: isContextSummaryMessage, buildCompactionUserPrompt, buildCompactedHistory
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { isContextSummaryMessage, buildCompactionUserPrompt, buildCompactedHistory, } from './compaction.js';
|
|
8
|
+
// ─── Test Fixtures ────────────────────────────────────────────────────────────
|
|
9
|
+
function msg(role, content) {
|
|
10
|
+
return { role, content };
|
|
11
|
+
}
|
|
12
|
+
function summaryMsg(text) {
|
|
13
|
+
return {
|
|
14
|
+
role: 'user',
|
|
15
|
+
content: `Another language model started to solve this problem and produced a summary of its thinking process. Use this summary to continue the task without redoing completed work.\n${text}`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
19
|
+
describe('isContextSummaryMessage', () => {
|
|
20
|
+
it('should identify summary messages', () => {
|
|
21
|
+
expect(isContextSummaryMessage(summaryMsg('stuff'))).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it('should reject non-summary user messages', () => {
|
|
24
|
+
expect(isContextSummaryMessage(msg('user', 'hello'))).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it('should reject assistant messages', () => {
|
|
27
|
+
expect(isContextSummaryMessage(msg('assistant', 'sup'))).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('buildCompactionUserPrompt', () => {
|
|
31
|
+
it('should format message transcript correctly', () => {
|
|
32
|
+
const history = [
|
|
33
|
+
msg('user', 'What is 1+1?'),
|
|
34
|
+
msg('assistant', '2'),
|
|
35
|
+
msg('user', 'wrong'),
|
|
36
|
+
];
|
|
37
|
+
const prompt = buildCompactionUserPrompt(history);
|
|
38
|
+
expect(prompt).toContain('[0] USER\nWhat is 1+1?');
|
|
39
|
+
expect(prompt).toContain('[1] ASSISTANT\n2');
|
|
40
|
+
expect(prompt).toContain('[2] USER\nwrong');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('buildCompactedHistory', () => {
|
|
44
|
+
it('should merge summary and retain system message', () => {
|
|
45
|
+
const history = [
|
|
46
|
+
msg('system', 'sys prompt here'),
|
|
47
|
+
msg('user', 'hi'),
|
|
48
|
+
msg('assistant', 'hello'),
|
|
49
|
+
msg('user', 'do it'),
|
|
50
|
+
];
|
|
51
|
+
const compacted = buildCompactedHistory(history[0], 'New Summary');
|
|
52
|
+
expect(compacted).toHaveLength(2);
|
|
53
|
+
expect(compacted[0].role).toBe('system');
|
|
54
|
+
expect(compacted[0].content).toBe('sys prompt here');
|
|
55
|
+
expect(compacted[1].role).toBe('user');
|
|
56
|
+
expect(compacted[1].content).toContain('Another language model started');
|
|
57
|
+
expect(compacted[1].content).toContain('New Summary');
|
|
58
|
+
});
|
|
59
|
+
it('should work without system message', () => {
|
|
60
|
+
const history = [
|
|
61
|
+
msg('user', 'hi'),
|
|
62
|
+
msg('assistant', 'hello'),
|
|
63
|
+
];
|
|
64
|
+
const compacted = buildCompactedHistory(undefined, 'Summary Only');
|
|
65
|
+
expect(compacted).toHaveLength(1);
|
|
66
|
+
expect(compacted[0].role).toBe('user');
|
|
67
|
+
expect(compacted[0].content).toContain('Summary Only');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=compaction.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.test.js","sourceRoot":"","sources":["../../src/runtime/compaction.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 会话历史持久化 — JSONL 格式写入。
|
|
3
|
+
*
|
|
4
|
+
* 参考 memo-code 的 history.ts,
|
|
5
|
+
* 每个事件写为一行 JSON,支持串行写入队列。
|
|
6
|
+
*/
|
|
7
|
+
import type { HistoryEvent, HistoryEventType, HistorySink, Role } from '../types.js';
|
|
8
|
+
/** JSONL 历史写入器:每条事件一行 JSON。 */
|
|
9
|
+
export declare class JsonlHistorySink implements HistorySink {
|
|
10
|
+
private filePath;
|
|
11
|
+
private ensureDirPromise;
|
|
12
|
+
private writeQueue;
|
|
13
|
+
private closed;
|
|
14
|
+
constructor(filePath: string);
|
|
15
|
+
/** 确保目标目录存在。 */
|
|
16
|
+
private ensureDirectory;
|
|
17
|
+
/** 追加一条事件到 JSONL 文件。 */
|
|
18
|
+
append(event: HistoryEvent): Promise<void>;
|
|
19
|
+
/** 等待所有挂起的写入完成。 */
|
|
20
|
+
flush(): Promise<void>;
|
|
21
|
+
/** 关闭写入器。 */
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** 创建一条结构化历史事件。 */
|
|
25
|
+
export declare function createHistoryEvent(params: {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
type: HistoryEventType;
|
|
28
|
+
turn?: number;
|
|
29
|
+
step?: number;
|
|
30
|
+
content?: string;
|
|
31
|
+
role?: Role;
|
|
32
|
+
meta?: Record<string, unknown>;
|
|
33
|
+
}): HistoryEvent;
|
|
34
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/runtime/history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACR,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,IAAI,EACP,MAAM,aAAa,CAAA;AAIpB,+BAA+B;AAC/B,qBAAa,gBAAiB,YAAW,WAAW;IAKpC,OAAO,CAAC,QAAQ;IAJ5B,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,MAAM,CAAQ;gBAEF,QAAQ,EAAE,MAAM;IAEpC,gBAAgB;IAChB,OAAO,CAAC,eAAe;IAUvB,wBAAwB;IAClB,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAehD,mBAAmB;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,aAAa;IACP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B;AAID,mBAAmB;AACnB,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,gBAAgB,CAAA;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC,GAAG,YAAY,CAWf"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 会话历史持久化 — JSONL 格式写入。
|
|
3
|
+
*
|
|
4
|
+
* 参考 memo-code 的 history.ts,
|
|
5
|
+
* 每个事件写为一行 JSON,支持串行写入队列。
|
|
6
|
+
*/
|
|
7
|
+
import { appendFile, mkdir } from 'node:fs/promises';
|
|
8
|
+
import { dirname } from 'node:path';
|
|
9
|
+
// ─── JsonlHistorySink ────────────────────────────────────────────────────────
|
|
10
|
+
/** JSONL 历史写入器:每条事件一行 JSON。 */
|
|
11
|
+
export class JsonlHistorySink {
|
|
12
|
+
filePath;
|
|
13
|
+
ensureDirPromise = null;
|
|
14
|
+
writeQueue = Promise.resolve();
|
|
15
|
+
closed = false;
|
|
16
|
+
constructor(filePath) {
|
|
17
|
+
this.filePath = filePath;
|
|
18
|
+
}
|
|
19
|
+
/** 确保目标目录存在。 */
|
|
20
|
+
ensureDirectory() {
|
|
21
|
+
if (!this.ensureDirPromise) {
|
|
22
|
+
this.ensureDirPromise = mkdir(dirname(this.filePath), { recursive: true }).then(() => { });
|
|
23
|
+
}
|
|
24
|
+
return this.ensureDirPromise;
|
|
25
|
+
}
|
|
26
|
+
/** 追加一条事件到 JSONL 文件。 */
|
|
27
|
+
async append(event) {
|
|
28
|
+
if (this.closed) {
|
|
29
|
+
throw new Error('History sink is closed');
|
|
30
|
+
}
|
|
31
|
+
this.writeQueue = this.writeQueue.then(async () => {
|
|
32
|
+
await this.ensureDirectory();
|
|
33
|
+
await appendFile(this.filePath, `${JSON.stringify(event)}\n`, 'utf8');
|
|
34
|
+
});
|
|
35
|
+
return this.writeQueue;
|
|
36
|
+
}
|
|
37
|
+
/** 等待所有挂起的写入完成。 */
|
|
38
|
+
async flush() {
|
|
39
|
+
await this.writeQueue;
|
|
40
|
+
}
|
|
41
|
+
/** 关闭写入器。 */
|
|
42
|
+
async close() {
|
|
43
|
+
if (this.closed)
|
|
44
|
+
return;
|
|
45
|
+
this.closed = true;
|
|
46
|
+
await this.flush();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// ─── 辅助函数 ─────────────────────────────────────────────────────────────────
|
|
50
|
+
/** 创建一条结构化历史事件。 */
|
|
51
|
+
export function createHistoryEvent(params) {
|
|
52
|
+
return {
|
|
53
|
+
ts: new Date().toISOString(),
|
|
54
|
+
sessionId: params.sessionId,
|
|
55
|
+
type: params.type,
|
|
56
|
+
turn: params.turn,
|
|
57
|
+
step: params.step,
|
|
58
|
+
content: params.content,
|
|
59
|
+
role: params.role,
|
|
60
|
+
meta: params.meta,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/runtime/history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQnC,gFAAgF;AAEhF,+BAA+B;AAC/B,MAAM,OAAO,gBAAgB;IAKL;IAJZ,gBAAgB,GAAyB,IAAI,CAAA;IAC7C,UAAU,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAA;IAC7C,MAAM,GAAG,KAAK,CAAA;IAEtB,YAAoB,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAExC,gBAAgB;IACR,eAAe;QACnB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACzB,IAAI,CAAC,gBAAgB,GAAG,KAAK,CACzB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EACtB,EAAE,SAAS,EAAE,IAAI,EAAE,CACtB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACpB,CAAC;QACD,OAAO,IAAI,CAAC,gBAAgB,CAAA;IAChC,CAAC;IAED,wBAAwB;IACxB,KAAK,CAAC,MAAM,CAAC,KAAmB;QAC5B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;QAC7C,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YAC9C,MAAM,IAAI,CAAC,eAAe,EAAE,CAAA;YAC5B,MAAM,UAAU,CACZ,IAAI,CAAC,QAAQ,EACb,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAC5B,MAAM,CACT,CAAA;QACL,CAAC,CAAC,CAAA;QACF,OAAO,IAAI,CAAC,UAAU,CAAA;IAC1B,CAAC;IAED,mBAAmB;IACnB,KAAK,CAAC,KAAK;QACP,MAAM,IAAI,CAAC,UAAU,CAAA;IACzB,CAAC;IAED,aAAa;IACb,KAAK,CAAC,KAAK;QACP,IAAI,IAAI,CAAC,MAAM;YAAE,OAAM;QACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QAClB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;IACtB,CAAC;CACJ;AAED,6EAA6E;AAE7E,mBAAmB;AACnB,MAAM,UAAU,kBAAkB,CAAC,MAQlC;IACG,OAAO;QACH,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;KACpB,CAAA;AACL,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Hook 系统 — 生命周期钩子的注册与执行引擎。
|
|
3
|
+
*
|
|
4
|
+
* Phase 7:提供 HookRunnerMap 注册表和 runHook() 安全执行器,
|
|
5
|
+
* 让 ReAct 循环在关键节点发射事件,由外部中间件处理 UI/日志等。
|
|
6
|
+
*
|
|
7
|
+
* 设计要点:
|
|
8
|
+
* - runHook() 内部 try/catch,单个 handler 失败不影响主流程
|
|
9
|
+
* - snapshotHistory() 深拷贝历史,防止 hook 修改共享状态
|
|
10
|
+
* - buildHookRunners() 合并 hooks + middlewares 到统一注册表
|
|
11
|
+
*/
|
|
12
|
+
import type { ChatMessage, AgentHooks, AgentMiddleware, AgentHookHandler, TurnStartHookPayload, ActionHookPayload, ObservationHookPayload, FinalHookPayload, ContextUsageHookPayload, ContextCompactedHookPayload, ApprovalHookPayload, ApprovalResponseHookPayload, TitleGeneratedHookPayload } from '../types.js';
|
|
13
|
+
/** 9 种 Hook 名称。 */
|
|
14
|
+
export type HookName = 'onTurnStart' | 'onAction' | 'onObservation' | 'onFinal' | 'onContextUsage' | 'onContextCompacted' | 'onApprovalRequest' | 'onApprovalResponse' | 'onTitleGenerated';
|
|
15
|
+
/** Hook 名称 → Payload 类型映射。 */
|
|
16
|
+
export type HookPayloadMap = {
|
|
17
|
+
onTurnStart: TurnStartHookPayload;
|
|
18
|
+
onAction: ActionHookPayload;
|
|
19
|
+
onObservation: ObservationHookPayload;
|
|
20
|
+
onFinal: FinalHookPayload;
|
|
21
|
+
onContextUsage: ContextUsageHookPayload;
|
|
22
|
+
onContextCompacted: ContextCompactedHookPayload;
|
|
23
|
+
onApprovalRequest: ApprovalHookPayload;
|
|
24
|
+
onApprovalResponse: ApprovalResponseHookPayload;
|
|
25
|
+
onTitleGenerated: TitleGeneratedHookPayload;
|
|
26
|
+
};
|
|
27
|
+
/** Hook 注册表:每个 hook 名称对应一个 handler 数组。 */
|
|
28
|
+
export type HookRunnerMap = {
|
|
29
|
+
[K in HookName]: AgentHookHandler<HookPayloadMap[K]>[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* 从 hooks + middlewares 配置构建统一的 HookRunnerMap。
|
|
33
|
+
*
|
|
34
|
+
* hooks 先注册,然后按顺序注册每个 middleware。
|
|
35
|
+
* 这样 hooks 里的 handler 总是最先执行。
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildHookRunners(hooks?: AgentHooks, middlewares?: AgentMiddleware[]): HookRunnerMap;
|
|
38
|
+
/**
|
|
39
|
+
* 安全执行指定 Hook 的所有 handler。
|
|
40
|
+
*
|
|
41
|
+
* 关键设计:每个 handler 独立 try/catch,
|
|
42
|
+
* 一个 handler 抛错不会阻止后续 handler 执行,
|
|
43
|
+
* 也不会影响主流程(ReAct 循环继续)。
|
|
44
|
+
*/
|
|
45
|
+
export declare function runHook<K extends HookName>(map: HookRunnerMap, name: K, payload: HookPayloadMap[K]): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* 深拷贝对话历史(传给 hook 的快照)。
|
|
48
|
+
*
|
|
49
|
+
* 防止 hook 中的代码意外修改共享的 history 数组。
|
|
50
|
+
* 对 assistant 消息的 tool_calls 做三层拷贝,
|
|
51
|
+
* 确保 function 对象也是独立副本。
|
|
52
|
+
*/
|
|
53
|
+
export declare function snapshotHistory(history: ChatMessage[]): ChatMessage[];
|
|
54
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/runtime/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACR,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,2BAA2B,EAC3B,mBAAmB,EACnB,2BAA2B,EAC3B,yBAAyB,EAC5B,MAAM,aAAa,CAAA;AAIpB,mBAAmB;AACnB,MAAM,MAAM,QAAQ,GACd,aAAa,GACb,UAAU,GACV,eAAe,GACf,SAAS,GACT,gBAAgB,GAChB,oBAAoB,GACpB,mBAAmB,GACnB,oBAAoB,GACpB,kBAAkB,CAAA;AAExB,8BAA8B;AAC9B,MAAM,MAAM,cAAc,GAAG;IACzB,WAAW,EAAE,oBAAoB,CAAA;IACjC,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,aAAa,EAAE,sBAAsB,CAAA;IACrC,OAAO,EAAE,gBAAgB,CAAA;IACzB,cAAc,EAAE,uBAAuB,CAAA;IACvC,kBAAkB,EAAE,2BAA2B,CAAA;IAC/C,iBAAiB,EAAE,mBAAmB,CAAA;IACtC,kBAAkB,EAAE,2BAA2B,CAAA;IAC/C,gBAAgB,EAAE,yBAAyB,CAAA;CAC9C,CAAA;AAED,0CAA0C;AAC1C,MAAM,MAAM,aAAa,GAAG;KACvB,CAAC,IAAI,QAAQ,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE;CACzD,CAAA;AA2CD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,CAAC,EAAE,UAAU,EAClB,WAAW,CAAC,EAAE,eAAe,EAAE,GAChC,aAAa,CASf;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,CAAC,SAAS,QAAQ,EAC5C,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAarE"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Hook 系统 — 生命周期钩子的注册与执行引擎。
|
|
3
|
+
*
|
|
4
|
+
* Phase 7:提供 HookRunnerMap 注册表和 runHook() 安全执行器,
|
|
5
|
+
* 让 ReAct 循环在关键节点发射事件,由外部中间件处理 UI/日志等。
|
|
6
|
+
*
|
|
7
|
+
* 设计要点:
|
|
8
|
+
* - runHook() 内部 try/catch,单个 handler 失败不影响主流程
|
|
9
|
+
* - snapshotHistory() 深拷贝历史,防止 hook 修改共享状态
|
|
10
|
+
* - buildHookRunners() 合并 hooks + middlewares 到统一注册表
|
|
11
|
+
*/
|
|
12
|
+
// ─── 内部工具函数 ─────────────────────────────────────────────────────────────
|
|
13
|
+
/** 创建空的 Hook 注册表。 */
|
|
14
|
+
function emptyHookMap() {
|
|
15
|
+
return {
|
|
16
|
+
onTurnStart: [],
|
|
17
|
+
onAction: [],
|
|
18
|
+
onObservation: [],
|
|
19
|
+
onFinal: [],
|
|
20
|
+
onContextUsage: [],
|
|
21
|
+
onContextCompacted: [],
|
|
22
|
+
onApprovalRequest: [],
|
|
23
|
+
onApprovalResponse: [],
|
|
24
|
+
onTitleGenerated: [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 将单个中间件的 handler 注册到注册表中。
|
|
29
|
+
*
|
|
30
|
+
* 遍历中间件上所有可选的 hook 属性,
|
|
31
|
+
* 如果存在则 push 到对应数组。
|
|
32
|
+
*/
|
|
33
|
+
function registerMiddleware(target, middleware) {
|
|
34
|
+
if (!middleware)
|
|
35
|
+
return;
|
|
36
|
+
if (middleware.onTurnStart)
|
|
37
|
+
target.onTurnStart.push(middleware.onTurnStart);
|
|
38
|
+
if (middleware.onAction)
|
|
39
|
+
target.onAction.push(middleware.onAction);
|
|
40
|
+
if (middleware.onObservation)
|
|
41
|
+
target.onObservation.push(middleware.onObservation);
|
|
42
|
+
if (middleware.onFinal)
|
|
43
|
+
target.onFinal.push(middleware.onFinal);
|
|
44
|
+
if (middleware.onContextUsage)
|
|
45
|
+
target.onContextUsage.push(middleware.onContextUsage);
|
|
46
|
+
if (middleware.onContextCompacted)
|
|
47
|
+
target.onContextCompacted.push(middleware.onContextCompacted);
|
|
48
|
+
if (middleware.onApprovalRequest)
|
|
49
|
+
target.onApprovalRequest.push(middleware.onApprovalRequest);
|
|
50
|
+
if (middleware.onApprovalResponse)
|
|
51
|
+
target.onApprovalResponse.push(middleware.onApprovalResponse);
|
|
52
|
+
if (middleware.onTitleGenerated)
|
|
53
|
+
target.onTitleGenerated.push(middleware.onTitleGenerated);
|
|
54
|
+
}
|
|
55
|
+
// ─── 公开 API ────────────────────────────────────────────────────────────────
|
|
56
|
+
/**
|
|
57
|
+
* 从 hooks + middlewares 配置构建统一的 HookRunnerMap。
|
|
58
|
+
*
|
|
59
|
+
* hooks 先注册,然后按顺序注册每个 middleware。
|
|
60
|
+
* 这样 hooks 里的 handler 总是最先执行。
|
|
61
|
+
*/
|
|
62
|
+
export function buildHookRunners(hooks, middlewares) {
|
|
63
|
+
const map = emptyHookMap();
|
|
64
|
+
registerMiddleware(map, hooks);
|
|
65
|
+
if (Array.isArray(middlewares)) {
|
|
66
|
+
for (const mw of middlewares) {
|
|
67
|
+
registerMiddleware(map, mw);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return map;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 安全执行指定 Hook 的所有 handler。
|
|
74
|
+
*
|
|
75
|
+
* 关键设计:每个 handler 独立 try/catch,
|
|
76
|
+
* 一个 handler 抛错不会阻止后续 handler 执行,
|
|
77
|
+
* 也不会影响主流程(ReAct 循环继续)。
|
|
78
|
+
*/
|
|
79
|
+
export async function runHook(map, name, payload) {
|
|
80
|
+
const handlers = map[name];
|
|
81
|
+
if (!handlers.length)
|
|
82
|
+
return;
|
|
83
|
+
for (const handler of handlers) {
|
|
84
|
+
try {
|
|
85
|
+
await handler(payload);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.warn(`Hook ${name} failed: ${err.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 深拷贝对话历史(传给 hook 的快照)。
|
|
94
|
+
*
|
|
95
|
+
* 防止 hook 中的代码意外修改共享的 history 数组。
|
|
96
|
+
* 对 assistant 消息的 tool_calls 做三层拷贝,
|
|
97
|
+
* 确保 function 对象也是独立副本。
|
|
98
|
+
*/
|
|
99
|
+
export function snapshotHistory(history) {
|
|
100
|
+
return history.map((msg) => {
|
|
101
|
+
if (msg.role === 'assistant' && msg.tool_calls?.length) {
|
|
102
|
+
return {
|
|
103
|
+
...msg,
|
|
104
|
+
tool_calls: msg.tool_calls.map((tc) => ({
|
|
105
|
+
...tc,
|
|
106
|
+
function: { ...tc.function },
|
|
107
|
+
})),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return { ...msg };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/runtime/hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.test.d.ts","sourceRoot":"","sources":["../../src/runtime/hooks.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unit tests for Hooks system (Phase 7).
|
|
3
|
+
*
|
|
4
|
+
* Tests: buildHookRunners, runHook, snapshotHistory
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
7
|
+
import { buildHookRunners, runHook, snapshotHistory, } from './hooks.js';
|
|
8
|
+
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
9
|
+
describe('runHook', () => {
|
|
10
|
+
it('should execute handlers successfully', async () => {
|
|
11
|
+
const h1 = vi.fn(async () => undefined);
|
|
12
|
+
const h2 = vi.fn(async () => undefined);
|
|
13
|
+
await runHook({ onTurnStart: [h1, h2] }, 'onTurnStart', { history: [] });
|
|
14
|
+
expect(h1).toHaveBeenCalledOnce();
|
|
15
|
+
expect(h2).toHaveBeenCalledOnce();
|
|
16
|
+
});
|
|
17
|
+
it('should isolate errors and continue sequence', async () => {
|
|
18
|
+
const h1 = vi.fn(async () => { throw new Error('First failed'); });
|
|
19
|
+
const h2 = vi.fn(async () => undefined);
|
|
20
|
+
// Console.error will be called, hide it to avoid noisy test output
|
|
21
|
+
const spy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
22
|
+
await runHook({ onTurnStart: [h1, h2] }, 'onTurnStart', { history: [] });
|
|
23
|
+
expect(h1).toHaveBeenCalledOnce();
|
|
24
|
+
expect(h2).toHaveBeenCalledOnce(); // h2 still runs despite h1 error
|
|
25
|
+
spy.mockRestore();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('buildHookRunners', () => {
|
|
29
|
+
it('should merge app hooks and middleware correctly', () => {
|
|
30
|
+
const globalHook = {
|
|
31
|
+
onTurnStart: vi.fn(),
|
|
32
|
+
};
|
|
33
|
+
const middleware = {
|
|
34
|
+
name: 'test-mid',
|
|
35
|
+
onTurnStart: vi.fn(),
|
|
36
|
+
};
|
|
37
|
+
const runners = buildHookRunners(globalHook, [middleware]);
|
|
38
|
+
expect(runners).toBeDefined();
|
|
39
|
+
expect(runners.onTurnStart).toHaveLength(2);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('snapshotHistory', () => {
|
|
43
|
+
it('should perform deep copy of message history', () => {
|
|
44
|
+
const original = [
|
|
45
|
+
{ role: 'user', content: 'hello' },
|
|
46
|
+
{
|
|
47
|
+
role: 'assistant',
|
|
48
|
+
content: 'hi',
|
|
49
|
+
tool_calls: [{
|
|
50
|
+
id: '1', type: 'function', function: { name: 'f', arguments: '{}' }
|
|
51
|
+
}]
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
const snapshot = snapshotHistory(original);
|
|
55
|
+
// It should match structure
|
|
56
|
+
expect(snapshot).toEqual(original);
|
|
57
|
+
// But be a different reference at the top level
|
|
58
|
+
expect(snapshot).not.toBe(original);
|
|
59
|
+
// And different reference at the tool calls level
|
|
60
|
+
const originalAsst = original[1];
|
|
61
|
+
const snapshotAsst = snapshot[1];
|
|
62
|
+
expect(snapshotAsst.tool_calls).not.toBe(originalAsst.tool_calls);
|
|
63
|
+
// Modifying original shouldn't affect snapshot
|
|
64
|
+
original.push({ role: 'user', content: 'mutated' });
|
|
65
|
+
expect(snapshot).toHaveLength(2);
|
|
66
|
+
// Modifying deep object doesn't affect snapshot
|
|
67
|
+
if (originalAsst.tool_calls) {
|
|
68
|
+
originalAsst.tool_calls[0].function.arguments = '{"mutated":true}';
|
|
69
|
+
}
|
|
70
|
+
expect(snapshotAsst.tool_calls?.[0].function.arguments).toBe('{}');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=hooks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.test.js","sourceRoot":"","sources":["../../src/runtime/hooks.test.ts"],"names":[],"mappings":"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"}
|