@zhin.js/core 1.0.26 → 1.0.28
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/CHANGELOG.md +18 -0
- package/lib/ai/agent.d.ts.map +1 -1
- package/lib/ai/agent.js +4 -0
- package/lib/ai/agent.js.map +1 -1
- package/lib/ai/bootstrap.d.ts +82 -0
- package/lib/ai/bootstrap.d.ts.map +1 -0
- package/lib/ai/bootstrap.js +199 -0
- package/lib/ai/bootstrap.js.map +1 -0
- package/lib/ai/builtin-tools.d.ts +36 -0
- package/lib/ai/builtin-tools.d.ts.map +1 -0
- package/lib/ai/builtin-tools.js +509 -0
- package/lib/ai/builtin-tools.js.map +1 -0
- package/lib/ai/compaction.d.ts +132 -0
- package/lib/ai/compaction.d.ts.map +1 -0
- package/lib/ai/compaction.js +370 -0
- package/lib/ai/compaction.js.map +1 -0
- package/lib/ai/hooks.d.ts +143 -0
- package/lib/ai/hooks.d.ts.map +1 -0
- package/lib/ai/hooks.js +108 -0
- package/lib/ai/hooks.js.map +1 -0
- package/lib/ai/index.d.ts +6 -0
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +6 -0
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/init.d.ts.map +1 -1
- package/lib/ai/init.js +120 -3
- package/lib/ai/init.js.map +1 -1
- package/lib/ai/types.d.ts +2 -0
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/ai/zhin-agent.d.ts +28 -1
- package/lib/ai/zhin-agent.d.ts.map +1 -1
- package/lib/ai/zhin-agent.js +196 -57
- package/lib/ai/zhin-agent.js.map +1 -1
- package/lib/built/config.d.ts +10 -0
- package/lib/built/config.d.ts.map +1 -1
- package/lib/built/config.js +54 -3
- package/lib/built/config.js.map +1 -1
- package/lib/built/tool.d.ts +6 -0
- package/lib/built/tool.d.ts.map +1 -1
- package/lib/built/tool.js +12 -0
- package/lib/built/tool.js.map +1 -1
- package/lib/cron.d.ts +0 -27
- package/lib/cron.d.ts.map +1 -1
- package/lib/cron.js +28 -27
- package/lib/cron.js.map +1 -1
- package/lib/types-generator.js +1 -1
- package/lib/types-generator.js.map +1 -1
- package/lib/types.d.ts +7 -0
- package/lib/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/ai/agent.ts +6 -0
- package/src/ai/bootstrap.ts +263 -0
- package/src/ai/builtin-tools.ts +569 -0
- package/src/ai/compaction.ts +529 -0
- package/src/ai/hooks.ts +223 -0
- package/src/ai/index.ts +58 -0
- package/src/ai/init.ts +127 -3
- package/src/ai/types.ts +2 -0
- package/src/ai/zhin-agent.ts +226 -54
- package/src/built/config.ts +53 -3
- package/src/built/tool.ts +12 -0
- package/src/cron.ts +28 -27
- package/src/types-generator.ts +1 -1
- package/src/types.ts +8 -0
- package/tests/adapter.test.ts +1 -1
- package/tests/config.test.ts +2 -2
- package/test/minimal-bot.ts +0 -31
- package/test/stress-test.ts +0 -123
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Bootstrap Files — 工作区引导文件管理
|
|
3
|
+
*
|
|
4
|
+
* 借鉴 OpenClaw 的 workspace bootstrap 设计,支持多种注入式提示文件:
|
|
5
|
+
*
|
|
6
|
+
* AGENTS.md — 持久化记忆/指令(AI 可读写)
|
|
7
|
+
* SOUL.md — 人格定义(只读)
|
|
8
|
+
* TOOLS.md — 工具使用指引(只读,用户自定义工具使用规则)
|
|
9
|
+
*
|
|
10
|
+
* 关键设计:
|
|
11
|
+
* 1. 基于 mtime 的文件缓存,避免冗余磁盘读取
|
|
12
|
+
* 2. 文件不存在不报错(可选)
|
|
13
|
+
* 3. 内容大小限制,防止注入超长文本
|
|
14
|
+
* 4. 统一的 ContextFile 格式,方便注入到 system prompt
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { Logger } from '@zhin.js/logger';
|
|
20
|
+
|
|
21
|
+
const logger = new Logger(null, 'Bootstrap');
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// 常量
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/** 支持的引导文件名 */
|
|
28
|
+
export const BOOTSTRAP_FILENAMES = [
|
|
29
|
+
'AGENTS.md',
|
|
30
|
+
'SOUL.md',
|
|
31
|
+
'TOOLS.md',
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
export type BootstrapFileName = typeof BOOTSTRAP_FILENAMES[number];
|
|
35
|
+
|
|
36
|
+
/** 单文件最大字符数(默认 16KB) */
|
|
37
|
+
const DEFAULT_MAX_CHARS = 16 * 1024;
|
|
38
|
+
|
|
39
|
+
/** 所有引导文件总最大字符数(默认 48KB) */
|
|
40
|
+
const DEFAULT_TOTAL_MAX_CHARS = 48 * 1024;
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// 类型
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
/** 引导文件信息 */
|
|
47
|
+
export interface BootstrapFile {
|
|
48
|
+
name: BootstrapFileName;
|
|
49
|
+
path: string;
|
|
50
|
+
content?: string;
|
|
51
|
+
missing: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 上下文文件(用于注入到 system prompt) */
|
|
55
|
+
export interface ContextFile {
|
|
56
|
+
path: string;
|
|
57
|
+
content: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// 文件缓存(基于 mtime)
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
const fileCache = new Map<string, { content: string; mtimeMs: number }>();
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 读文件,带 mtime 缓存
|
|
68
|
+
*/
|
|
69
|
+
async function readFileWithCache(filePath: string): Promise<string> {
|
|
70
|
+
try {
|
|
71
|
+
const stats = await fs.promises.stat(filePath);
|
|
72
|
+
const mtimeMs = stats.mtimeMs;
|
|
73
|
+
const cached = fileCache.get(filePath);
|
|
74
|
+
|
|
75
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
76
|
+
return cached.content;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
80
|
+
fileCache.set(filePath, { content, mtimeMs });
|
|
81
|
+
return content;
|
|
82
|
+
} catch {
|
|
83
|
+
fileCache.delete(filePath);
|
|
84
|
+
throw new Error(`文件读取失败: ${filePath}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 清除文件缓存(热重载时调用)
|
|
90
|
+
*/
|
|
91
|
+
export function clearBootstrapCache(): void {
|
|
92
|
+
fileCache.clear();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// 文件加载
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 获取数据目录
|
|
101
|
+
*/
|
|
102
|
+
function getDataDir(workspaceDir?: string): string {
|
|
103
|
+
const cwd = workspaceDir || process.cwd();
|
|
104
|
+
return path.join(cwd, 'data');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 加载工作区引导文件
|
|
109
|
+
*
|
|
110
|
+
* 搜索顺序:项目根目录 → data/ 目录
|
|
111
|
+
*/
|
|
112
|
+
export async function loadBootstrapFiles(
|
|
113
|
+
workspaceDir?: string,
|
|
114
|
+
): Promise<BootstrapFile[]> {
|
|
115
|
+
const cwd = workspaceDir || process.cwd();
|
|
116
|
+
const dataDir = getDataDir(cwd);
|
|
117
|
+
|
|
118
|
+
const result: BootstrapFile[] = [];
|
|
119
|
+
|
|
120
|
+
for (const name of BOOTSTRAP_FILENAMES) {
|
|
121
|
+
// 优先项目根目录
|
|
122
|
+
const rootPath = path.join(cwd, name);
|
|
123
|
+
const dataPath = path.join(dataDir, name);
|
|
124
|
+
|
|
125
|
+
let found = false;
|
|
126
|
+
for (const filePath of [rootPath, dataPath]) {
|
|
127
|
+
try {
|
|
128
|
+
const content = await readFileWithCache(filePath);
|
|
129
|
+
result.push({ name, path: filePath, content, missing: false });
|
|
130
|
+
found = true;
|
|
131
|
+
break; // 找到就不再搜索
|
|
132
|
+
} catch {
|
|
133
|
+
// 继续尝试下一个路径
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!found) {
|
|
138
|
+
result.push({ name, path: rootPath, missing: true });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 将引导文件转为上下文文件列表(用于注入到 system prompt)
|
|
147
|
+
*
|
|
148
|
+
* 自动裁剪超长内容,跳过缺失文件
|
|
149
|
+
*/
|
|
150
|
+
export function buildContextFiles(
|
|
151
|
+
bootstrapFiles: BootstrapFile[],
|
|
152
|
+
options?: {
|
|
153
|
+
maxChars?: number;
|
|
154
|
+
totalMaxChars?: number;
|
|
155
|
+
},
|
|
156
|
+
): ContextFile[] {
|
|
157
|
+
const maxChars = options?.maxChars ?? DEFAULT_MAX_CHARS;
|
|
158
|
+
const totalMaxChars = options?.totalMaxChars ?? DEFAULT_TOTAL_MAX_CHARS;
|
|
159
|
+
|
|
160
|
+
const contextFiles: ContextFile[] = [];
|
|
161
|
+
let totalChars = 0;
|
|
162
|
+
|
|
163
|
+
for (const file of bootstrapFiles) {
|
|
164
|
+
if (file.missing || !file.content) continue;
|
|
165
|
+
|
|
166
|
+
let content = file.content.trim();
|
|
167
|
+
if (!content) continue;
|
|
168
|
+
|
|
169
|
+
// 单文件裁剪
|
|
170
|
+
if (content.length > maxChars) {
|
|
171
|
+
content = content.slice(0, maxChars) + '\n...(内容过长已截断)';
|
|
172
|
+
logger.warn(`引导文件 ${file.name} 超过 ${maxChars} 字符,已截断`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 总量限制
|
|
176
|
+
if (totalChars + content.length > totalMaxChars) {
|
|
177
|
+
logger.warn(`引导文件总量超过 ${totalMaxChars} 字符,跳过 ${file.name}`);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
contextFiles.push({ path: file.name, content });
|
|
182
|
+
totalChars += content.length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return contextFiles;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 加载 SOUL.md 人格定义
|
|
190
|
+
*/
|
|
191
|
+
export async function loadSoulPersona(workspaceDir?: string): Promise<string | null> {
|
|
192
|
+
const files = await loadBootstrapFiles(workspaceDir);
|
|
193
|
+
const soulFile = files.find(f => f.name === 'SOUL.md' && !f.missing);
|
|
194
|
+
return soulFile?.content?.trim() || null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 加载 TOOLS.md 工具使用指引
|
|
199
|
+
*/
|
|
200
|
+
export async function loadToolsGuide(workspaceDir?: string): Promise<string | null> {
|
|
201
|
+
const files = await loadBootstrapFiles(workspaceDir);
|
|
202
|
+
const toolsFile = files.find(f => f.name === 'TOOLS.md' && !f.missing);
|
|
203
|
+
return toolsFile?.content?.trim() || null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 加载 AGENTS.md 持久化记忆
|
|
208
|
+
*/
|
|
209
|
+
export async function loadAgentsMemory(workspaceDir?: string): Promise<string | null> {
|
|
210
|
+
const files = await loadBootstrapFiles(workspaceDir);
|
|
211
|
+
const agentsFile = files.find(f => f.name === 'AGENTS.md' && !f.missing);
|
|
212
|
+
return agentsFile?.content?.trim() || null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// System Prompt 构建帮助函数
|
|
217
|
+
// ============================================================================
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 构建引导文件上下文段(注入到 system prompt 末尾)
|
|
221
|
+
*
|
|
222
|
+
* 格式与 OpenClaw 一致:
|
|
223
|
+
* ```
|
|
224
|
+
* # Project Context
|
|
225
|
+
*
|
|
226
|
+
* The following project context files have been loaded:
|
|
227
|
+
* If SOUL.md is present, embody its persona and tone.
|
|
228
|
+
*
|
|
229
|
+
* ## SOUL.md
|
|
230
|
+
*
|
|
231
|
+
* <content>
|
|
232
|
+
*
|
|
233
|
+
* ## TOOLS.md
|
|
234
|
+
*
|
|
235
|
+
* <content>
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export function buildBootstrapContextSection(contextFiles: ContextFile[]): string {
|
|
239
|
+
if (contextFiles.length === 0) return '';
|
|
240
|
+
|
|
241
|
+
const hasSoul = contextFiles.some(f =>
|
|
242
|
+
f.path.toLowerCase().endsWith('soul.md'),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const lines: string[] = [
|
|
246
|
+
'# 项目上下文',
|
|
247
|
+
'',
|
|
248
|
+
'以下项目上下文文件已加载:',
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
if (hasSoul) {
|
|
252
|
+
lines.push(
|
|
253
|
+
'如果存在 SOUL.md,请融入其人格和语气。避免生硬的通用回复,遵循其指引。',
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
lines.push('');
|
|
257
|
+
|
|
258
|
+
for (const file of contextFiles) {
|
|
259
|
+
lines.push(`## ${file.path}`, '', file.content, '');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return lines.join('\n');
|
|
263
|
+
}
|