@zhin.js/agent 0.0.15 → 0.0.17
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 +16 -0
- package/lib/builtin-tools.d.ts +93 -0
- package/lib/builtin-tools.d.ts.map +1 -1
- package/lib/builtin-tools.js +358 -3
- package/lib/builtin-tools.js.map +1 -1
- package/lib/init/register-builtin-tools.d.ts.map +1 -1
- package/lib/init/register-builtin-tools.js +138 -2
- package/lib/init/register-builtin-tools.js.map +1 -1
- package/lib/init/register-db-models.js +3 -3
- package/lib/init/register-db-models.js.map +1 -1
- package/lib/init/register-db-upgrade.js +2 -2
- package/lib/init/register-db-upgrade.js.map +1 -1
- package/lib/init/register-management-tools.js +1 -1
- package/lib/init/register-management-tools.js.map +1 -1
- package/lib/service.d.ts +4 -8
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +23 -112
- package/lib/service.js.map +1 -1
- package/lib/subagent.js +1 -1
- package/lib/subagent.js.map +1 -1
- package/lib/zhin-agent/builtin-tools.d.ts +1 -1
- package/lib/zhin-agent/builtin-tools.d.ts.map +1 -1
- package/lib/zhin-agent/config.d.ts +8 -1
- package/lib/zhin-agent/config.d.ts.map +1 -1
- package/lib/zhin-agent/config.js +8 -1
- package/lib/zhin-agent/config.js.map +1 -1
- package/lib/zhin-agent/index.d.ts +3 -3
- package/lib/zhin-agent/index.d.ts.map +1 -1
- package/lib/zhin-agent/index.js +52 -29
- package/lib/zhin-agent/index.js.map +1 -1
- package/lib/zhin-agent/tool-collector.js +1 -1
- package/package.json +3 -3
- package/src/builtin-tools.ts +443 -3
- package/src/init/register-ai-trigger.ts +1 -1
- package/src/init/register-builtin-tools.ts +135 -2
- package/src/init/register-db-models.ts +3 -3
- package/src/init/register-db-upgrade.ts +2 -2
- package/src/init/register-management-tools.ts +1 -1
- package/src/init/register-message-recorder.ts +1 -1
- package/src/service.ts +28 -132
- package/src/subagent.ts +1 -1
- package/src/zhin-agent/builtin-tools.ts +1 -1
- package/src/zhin-agent/config.ts +10 -2
- package/src/zhin-agent/index.ts +51 -29
- package/src/zhin-agent/tool-collector.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @zhin.js/agent
|
|
2
2
|
|
|
3
|
+
## 0.0.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 16c8f92: fix: 统一发一次版
|
|
8
|
+
- Updated dependencies [16c8f92]
|
|
9
|
+
- @zhin.js/ai@1.0.15
|
|
10
|
+
- @zhin.js/core@1.0.54
|
|
11
|
+
|
|
12
|
+
## 0.0.16
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- @zhin.js/core@1.0.53
|
|
17
|
+
- @zhin.js/ai@1.0.14
|
|
18
|
+
|
|
3
19
|
## 0.0.15
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/lib/builtin-tools.d.ts
CHANGED
|
@@ -31,6 +31,11 @@ export interface BuiltinToolsOptions {
|
|
|
31
31
|
* 返回额外技能根目录(每个根下为 `<skillName>/SKILL.md`),通常为已加载插件的 `.../skills`
|
|
32
32
|
*/
|
|
33
33
|
pluginSkillRootsResolver?: () => string[];
|
|
34
|
+
/**
|
|
35
|
+
* 按名称查找 SkillFeature 中已注册技能的 filePath
|
|
36
|
+
* 返回 SKILL.md 的绝对路径,或 undefined 表示未找到
|
|
37
|
+
*/
|
|
38
|
+
skillFileLookup?: (name: string) => string | undefined;
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* 创建所有内置系统工具
|
|
@@ -71,4 +76,92 @@ export declare function loadAlwaysSkillsContent(skills: SkillMeta[]): Promise<st
|
|
|
71
76
|
* 构建技能列表的 XML 摘要,供 model 区分可用/不可用及缺失依赖
|
|
72
77
|
*/
|
|
73
78
|
export declare function buildSkillsSummaryXML(skills: SkillMeta[]): string;
|
|
79
|
+
/**
|
|
80
|
+
* Agent 预设元数据(从 *.agent.md frontmatter 解析)
|
|
81
|
+
*/
|
|
82
|
+
export interface AgentMeta {
|
|
83
|
+
name: string;
|
|
84
|
+
description: string;
|
|
85
|
+
keywords?: string[];
|
|
86
|
+
tags?: string[];
|
|
87
|
+
/** frontmatter 中声明的工具名列表 */
|
|
88
|
+
toolNames?: string[];
|
|
89
|
+
/** *.agent.md 文件的绝对路径 */
|
|
90
|
+
filePath: string;
|
|
91
|
+
/** 首选模型名 */
|
|
92
|
+
model?: string;
|
|
93
|
+
/** 首选 Provider 名 */
|
|
94
|
+
provider?: string;
|
|
95
|
+
/** 最大工具调用迭代次数 */
|
|
96
|
+
maxIterations?: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 扫描 agents/ 目录,发现 *.agent.md 文件
|
|
100
|
+
* 加载顺序与 skills 一致:Workspace > ~/.zhin > data > 插件包
|
|
101
|
+
* 同名先发现者优先
|
|
102
|
+
*/
|
|
103
|
+
export declare function discoverWorkspaceAgents(root?: Plugin | null): Promise<AgentMeta[]>;
|
|
104
|
+
/**
|
|
105
|
+
* 简写参数定义(*.tool.md frontmatter 中使用)
|
|
106
|
+
*/
|
|
107
|
+
export interface ToolParamShorthand {
|
|
108
|
+
type: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
required?: boolean;
|
|
111
|
+
enum?: string[];
|
|
112
|
+
default?: any;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Tool 元数据(从 *.tool.md frontmatter 解析)
|
|
116
|
+
*/
|
|
117
|
+
export interface ToolMeta {
|
|
118
|
+
name: string;
|
|
119
|
+
description: string;
|
|
120
|
+
/** 简写参数定义(frontmatter 格式) */
|
|
121
|
+
parameters?: Record<string, ToolParamShorthand>;
|
|
122
|
+
/** 命令配置 */
|
|
123
|
+
command?: {
|
|
124
|
+
pattern?: string;
|
|
125
|
+
alias?: string[];
|
|
126
|
+
examples?: string[];
|
|
127
|
+
};
|
|
128
|
+
/** 支持的平台列表 */
|
|
129
|
+
platforms?: string[];
|
|
130
|
+
/** 支持的场景列表 */
|
|
131
|
+
scopes?: string[];
|
|
132
|
+
/** 权限级别 */
|
|
133
|
+
permissionLevel?: string;
|
|
134
|
+
/** 标签 */
|
|
135
|
+
tags?: string[];
|
|
136
|
+
/** 触发关键词 */
|
|
137
|
+
keywords?: string[];
|
|
138
|
+
/** 工具分类 */
|
|
139
|
+
kind?: string;
|
|
140
|
+
/** 是否隐藏 */
|
|
141
|
+
hidden?: boolean;
|
|
142
|
+
/** handler 文件路径(相对于 .tool.md) */
|
|
143
|
+
handler?: string;
|
|
144
|
+
/** *.tool.md 文件的绝对路径 */
|
|
145
|
+
filePath: string;
|
|
146
|
+
/** body 内容(无 handler 时作为 prompt 模板) */
|
|
147
|
+
templateBody?: string;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 从根插件树收集:根插件与直接子插件包目录下的 `tools/`
|
|
151
|
+
*/
|
|
152
|
+
export declare function collectPluginToolSearchRoots(root: Plugin | null | undefined): string[];
|
|
153
|
+
/**
|
|
154
|
+
* 获取所有 tool 搜索目录(标准目录 + 插件包 tools/)
|
|
155
|
+
*/
|
|
156
|
+
export declare function getToolSearchDirectories(root?: Plugin | null): string[];
|
|
157
|
+
/**
|
|
158
|
+
* 扫描 tools/ 目录,发现 *.tool.md 文件
|
|
159
|
+
* 加载顺序与 skills/agents 一致:Workspace > ~/.zhin > data > 插件包
|
|
160
|
+
* 同名先发现者优先
|
|
161
|
+
*/
|
|
162
|
+
export declare function discoverWorkspaceTools(root?: Plugin | null): Promise<ToolMeta[]>;
|
|
163
|
+
/**
|
|
164
|
+
* 将 ToolMeta 转换为 Tool 对象(包含 execute 函数)
|
|
165
|
+
*/
|
|
166
|
+
export declare function buildToolFromMeta(meta: ToolMeta): Promise<import('@zhin.js/core').Tool | null>;
|
|
74
167
|
//# sourceMappingURL=builtin-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtin-tools.d.ts","sourceRoot":"","sources":["../src/builtin-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA2BnF;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"builtin-tools.d.ts","sourceRoot":"","sources":["../src/builtin-tools.ts"],"names":[],"mappings":"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"}
|
package/lib/builtin-tools.js
CHANGED
|
@@ -58,7 +58,13 @@ export function collectPluginSkillSearchRoots(root) {
|
|
|
58
58
|
const fromPlugin = (p) => {
|
|
59
59
|
if (!p?.filePath)
|
|
60
60
|
return;
|
|
61
|
-
|
|
61
|
+
const dir = path.dirname(p.filePath);
|
|
62
|
+
push(path.join(dir, 'skills'));
|
|
63
|
+
// Also check package root when filePath is under src/ or lib/
|
|
64
|
+
const dirName = path.basename(dir);
|
|
65
|
+
if (dirName === 'src' || dirName === 'lib') {
|
|
66
|
+
push(path.join(path.dirname(dir), 'skills'));
|
|
67
|
+
}
|
|
62
68
|
};
|
|
63
69
|
fromPlugin(root);
|
|
64
70
|
for (const child of root.children || []) {
|
|
@@ -113,6 +119,7 @@ export function createBuiltinTools(options) {
|
|
|
113
119
|
const DATA_DIR = getDataDir();
|
|
114
120
|
const skillMaxChars = options?.skillInstructionMaxChars ?? 4000;
|
|
115
121
|
const skillDirList = () => mergeSkillDirsWithResolver(options?.pluginSkillRootsResolver);
|
|
122
|
+
const skillFileLookup = options?.skillFileLookup;
|
|
116
123
|
const tools = [];
|
|
117
124
|
// ── read_file(清晰描述 + 强关键词) ──
|
|
118
125
|
tools.push(new ZhinTool('read_file')
|
|
@@ -487,12 +494,19 @@ export function createBuiltinTools(options) {
|
|
|
487
494
|
.param('name', { type: 'string', description: '技能名称' }, true)
|
|
488
495
|
.execute(async (args) => {
|
|
489
496
|
try {
|
|
490
|
-
//
|
|
497
|
+
// 优先查找 SkillFeature 中已注册技能的 filePath
|
|
498
|
+
const registeredPath = skillFileLookup?.(args.name);
|
|
499
|
+
if (registeredPath && fs.existsSync(registeredPath)) {
|
|
500
|
+
const fullContent = await fs.promises.readFile(registeredPath, 'utf-8');
|
|
501
|
+
const depWarning = await checkSkillDeps(fullContent);
|
|
502
|
+
const instructions = extractSkillInstructions(args.name, fullContent, skillMaxChars);
|
|
503
|
+
return depWarning ? `${depWarning}\n\n${instructions}` : instructions;
|
|
504
|
+
}
|
|
505
|
+
// 回退到目录扫描(与 discoverWorkspaceSkills 顺序一致)
|
|
491
506
|
for (const dir of skillDirList()) {
|
|
492
507
|
const skillPath = path.join(dir, args.name, 'SKILL.md');
|
|
493
508
|
if (fs.existsSync(skillPath)) {
|
|
494
509
|
const fullContent = await fs.promises.readFile(skillPath, 'utf-8');
|
|
495
|
-
// 5.3 可执行环境检查:若 SKILL 声明了 deps,再次检查;缺失则在返回内容中提示
|
|
496
510
|
const depWarning = await checkSkillDeps(fullContent);
|
|
497
511
|
const instructions = extractSkillInstructions(args.name, fullContent, skillMaxChars);
|
|
498
512
|
return depWarning ? `${depWarning}\n\n${instructions}` : instructions;
|
|
@@ -835,4 +849,345 @@ export function buildSkillsSummaryXML(skills) {
|
|
|
835
849
|
lines.push('</skills>');
|
|
836
850
|
return lines.join('\n');
|
|
837
851
|
}
|
|
852
|
+
/**
|
|
853
|
+
* 扫描 agents/ 目录,发现 *.agent.md 文件
|
|
854
|
+
* 加载顺序与 skills 一致:Workspace > ~/.zhin > data > 插件包
|
|
855
|
+
* 同名先发现者优先
|
|
856
|
+
*/
|
|
857
|
+
export async function discoverWorkspaceAgents(root) {
|
|
858
|
+
const agents = [];
|
|
859
|
+
const seenNames = new Set();
|
|
860
|
+
// 构建扫描目录:标准目录的 agents/ + 插件包的 agents/
|
|
861
|
+
const agentDirs = [
|
|
862
|
+
path.join(process.cwd(), 'agents'),
|
|
863
|
+
path.join(os.homedir(), '.zhin', 'agents'),
|
|
864
|
+
path.join(getDataDir(), 'agents'),
|
|
865
|
+
];
|
|
866
|
+
if (root) {
|
|
867
|
+
const addPluginDir = (p) => {
|
|
868
|
+
if (!p?.filePath)
|
|
869
|
+
return;
|
|
870
|
+
const dir = path.dirname(p.filePath);
|
|
871
|
+
const d = path.join(dir, 'agents');
|
|
872
|
+
if (!agentDirs.includes(d))
|
|
873
|
+
agentDirs.push(d);
|
|
874
|
+
// Also check package root when filePath is under src/ or lib/
|
|
875
|
+
const dirName = path.basename(dir);
|
|
876
|
+
if (dirName === 'src' || dirName === 'lib') {
|
|
877
|
+
const d2 = path.join(path.dirname(dir), 'agents');
|
|
878
|
+
if (!agentDirs.includes(d2))
|
|
879
|
+
agentDirs.push(d2);
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
addPluginDir(root);
|
|
883
|
+
for (const child of root.children || []) {
|
|
884
|
+
addPluginDir(child);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
for (const agentsDir of agentDirs) {
|
|
888
|
+
if (!fs.existsSync(agentsDir))
|
|
889
|
+
continue;
|
|
890
|
+
let entries;
|
|
891
|
+
try {
|
|
892
|
+
entries = await fs.promises.readdir(agentsDir, { withFileTypes: true });
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
for (const entry of entries) {
|
|
898
|
+
// 支持两种结构:
|
|
899
|
+
// 1. agents/<name>.agent.md(扁平文件)
|
|
900
|
+
// 2. agents/<name>/<name>.agent.md(目录结构)
|
|
901
|
+
let agentMdPath;
|
|
902
|
+
if (entry.isFile() && entry.name.endsWith('.agent.md')) {
|
|
903
|
+
agentMdPath = path.join(agentsDir, entry.name);
|
|
904
|
+
}
|
|
905
|
+
else if (entry.isDirectory()) {
|
|
906
|
+
const nested = path.join(agentsDir, entry.name, `${entry.name}.agent.md`);
|
|
907
|
+
if (fs.existsSync(nested)) {
|
|
908
|
+
agentMdPath = nested;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (!agentMdPath)
|
|
912
|
+
continue;
|
|
913
|
+
try {
|
|
914
|
+
const content = await fs.promises.readFile(agentMdPath, 'utf-8');
|
|
915
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/);
|
|
916
|
+
if (!match) {
|
|
917
|
+
logger.debug(`Agent文件 ${agentMdPath} 没有有效的frontmatter格式`);
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
let jsYaml;
|
|
921
|
+
try {
|
|
922
|
+
jsYaml = await import('js-yaml');
|
|
923
|
+
if (jsYaml.default)
|
|
924
|
+
jsYaml = jsYaml.default;
|
|
925
|
+
}
|
|
926
|
+
catch (e) {
|
|
927
|
+
logger.warn(`Unable to import js-yaml module: ${e}`);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const metadata = jsYaml.load(match[1]);
|
|
931
|
+
if (!metadata || !metadata.name || !metadata.description) {
|
|
932
|
+
logger.debug(`Agent文件 ${agentMdPath} 缺少必需的 name/description 字段`);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (seenNames.has(metadata.name)) {
|
|
936
|
+
logger.debug(`Agent '${metadata.name}' 已由先序目录加载,跳过: ${agentMdPath}`);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
seenNames.add(metadata.name);
|
|
940
|
+
agents.push({
|
|
941
|
+
name: metadata.name,
|
|
942
|
+
description: metadata.description,
|
|
943
|
+
keywords: metadata.keywords || [],
|
|
944
|
+
tags: metadata.tags || [],
|
|
945
|
+
toolNames: Array.isArray(metadata.tools) ? metadata.tools : [],
|
|
946
|
+
filePath: agentMdPath,
|
|
947
|
+
model: metadata.model,
|
|
948
|
+
provider: metadata.provider,
|
|
949
|
+
maxIterations: typeof metadata.maxIterations === 'number' ? metadata.maxIterations : undefined,
|
|
950
|
+
});
|
|
951
|
+
logger.debug(`Agent发现成功: ${metadata.name}`);
|
|
952
|
+
}
|
|
953
|
+
catch (e) {
|
|
954
|
+
logger.warn(`Failed to parse agent.md in ${agentMdPath}:`, e);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (agents.length > 0) {
|
|
959
|
+
logger.info(`发现 ${agents.length} 个工作区 Agent 预设: ${agents.map(a => a.name).join(', ')}`);
|
|
960
|
+
}
|
|
961
|
+
return agents;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* 从根插件树收集:根插件与直接子插件包目录下的 `tools/`
|
|
965
|
+
*/
|
|
966
|
+
export function collectPluginToolSearchRoots(root) {
|
|
967
|
+
if (!root)
|
|
968
|
+
return [];
|
|
969
|
+
const dirs = [];
|
|
970
|
+
const push = (d) => {
|
|
971
|
+
if (d && !dirs.includes(d))
|
|
972
|
+
dirs.push(d);
|
|
973
|
+
};
|
|
974
|
+
const fromPlugin = (p) => {
|
|
975
|
+
if (!p?.filePath)
|
|
976
|
+
return;
|
|
977
|
+
const dir = path.dirname(p.filePath);
|
|
978
|
+
push(path.join(dir, 'tools'));
|
|
979
|
+
// Also check package root when filePath is under src/ or lib/
|
|
980
|
+
const dirName = path.basename(dir);
|
|
981
|
+
if (dirName === 'src' || dirName === 'lib') {
|
|
982
|
+
push(path.join(path.dirname(dir), 'tools'));
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
fromPlugin(root);
|
|
986
|
+
for (const child of root.children || []) {
|
|
987
|
+
fromPlugin(child);
|
|
988
|
+
}
|
|
989
|
+
return dirs;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* 获取所有 tool 搜索目录(标准目录 + 插件包 tools/)
|
|
993
|
+
*/
|
|
994
|
+
export function getToolSearchDirectories(root) {
|
|
995
|
+
const list = [
|
|
996
|
+
path.join(process.cwd(), 'tools'),
|
|
997
|
+
path.join(os.homedir(), '.zhin', 'tools'),
|
|
998
|
+
path.join(getDataDir(), 'tools'),
|
|
999
|
+
];
|
|
1000
|
+
for (const d of collectPluginToolSearchRoots(root ?? undefined)) {
|
|
1001
|
+
if (!list.includes(d))
|
|
1002
|
+
list.push(d);
|
|
1003
|
+
}
|
|
1004
|
+
return list;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* 将简写参数定义转换为 ToolParametersSchema
|
|
1008
|
+
*/
|
|
1009
|
+
function shorthandToSchema(params) {
|
|
1010
|
+
const properties = {};
|
|
1011
|
+
const required = [];
|
|
1012
|
+
for (const [key, param] of Object.entries(params)) {
|
|
1013
|
+
properties[key] = {
|
|
1014
|
+
type: param.type || 'string',
|
|
1015
|
+
description: param.description || key,
|
|
1016
|
+
};
|
|
1017
|
+
if (param.enum)
|
|
1018
|
+
properties[key].enum = param.enum;
|
|
1019
|
+
if (param.default !== undefined)
|
|
1020
|
+
properties[key].default = param.default;
|
|
1021
|
+
if (param.required)
|
|
1022
|
+
required.push(key);
|
|
1023
|
+
}
|
|
1024
|
+
return { type: 'object', properties, required: required.length > 0 ? required : undefined };
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* 加载 handler 文件(动态 import)
|
|
1028
|
+
* @returns execute 函数, 或 undefined(加载失败)
|
|
1029
|
+
*/
|
|
1030
|
+
async function loadToolHandler(handlerPath, toolMdPath) {
|
|
1031
|
+
const resolved = path.resolve(path.dirname(toolMdPath), handlerPath);
|
|
1032
|
+
if (!fs.existsSync(resolved)) {
|
|
1033
|
+
logger.warn(`Tool handler 文件不存在: ${resolved}`);
|
|
1034
|
+
return undefined;
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
const fileUrl = `file://${resolved}?t=${Date.now()}`;
|
|
1038
|
+
const mod = await import(fileUrl);
|
|
1039
|
+
const fn = mod.default || mod;
|
|
1040
|
+
if (typeof fn !== 'function') {
|
|
1041
|
+
logger.warn(`Tool handler 未导出函数: ${resolved}`);
|
|
1042
|
+
return undefined;
|
|
1043
|
+
}
|
|
1044
|
+
return fn;
|
|
1045
|
+
}
|
|
1046
|
+
catch (e) {
|
|
1047
|
+
logger.warn(`Tool handler 加载失败 (${resolved}): ${errMsg(e)}`);
|
|
1048
|
+
return undefined;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* 从 body 构建 prompt 模板执行函数
|
|
1053
|
+
*/
|
|
1054
|
+
function buildTemplateExecute(body) {
|
|
1055
|
+
return (args) => body.replace(/\{\{(\w+)\}\}/g, (_, k) => {
|
|
1056
|
+
const val = args[k];
|
|
1057
|
+
return val !== undefined && val !== null ? String(val) : '';
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* 扫描 tools/ 目录,发现 *.tool.md 文件
|
|
1062
|
+
* 加载顺序与 skills/agents 一致:Workspace > ~/.zhin > data > 插件包
|
|
1063
|
+
* 同名先发现者优先
|
|
1064
|
+
*/
|
|
1065
|
+
export async function discoverWorkspaceTools(root) {
|
|
1066
|
+
const tools = [];
|
|
1067
|
+
const seenNames = new Set();
|
|
1068
|
+
const toolDirs = getToolSearchDirectories(root);
|
|
1069
|
+
for (const toolsDir of toolDirs) {
|
|
1070
|
+
if (!fs.existsSync(toolsDir))
|
|
1071
|
+
continue;
|
|
1072
|
+
let entries;
|
|
1073
|
+
try {
|
|
1074
|
+
entries = await fs.promises.readdir(toolsDir, { withFileTypes: true });
|
|
1075
|
+
}
|
|
1076
|
+
catch {
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
for (const entry of entries) {
|
|
1080
|
+
// 支持两种结构:
|
|
1081
|
+
// 1. tools/<name>.tool.md(扁平文件)
|
|
1082
|
+
// 2. tools/<name>/<name>.tool.md(目录结构,允许放 handler.ts)
|
|
1083
|
+
let toolMdPath;
|
|
1084
|
+
if (entry.isFile() && entry.name.endsWith('.tool.md')) {
|
|
1085
|
+
toolMdPath = path.join(toolsDir, entry.name);
|
|
1086
|
+
}
|
|
1087
|
+
else if (entry.isDirectory()) {
|
|
1088
|
+
const nested = path.join(toolsDir, entry.name, `${entry.name}.tool.md`);
|
|
1089
|
+
if (fs.existsSync(nested)) {
|
|
1090
|
+
toolMdPath = nested;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (!toolMdPath)
|
|
1094
|
+
continue;
|
|
1095
|
+
try {
|
|
1096
|
+
const content = await fs.promises.readFile(toolMdPath, 'utf-8');
|
|
1097
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/);
|
|
1098
|
+
if (!match) {
|
|
1099
|
+
logger.debug(`Tool文件 ${toolMdPath} 没有有效的frontmatter格式`);
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
let jsYaml;
|
|
1103
|
+
try {
|
|
1104
|
+
jsYaml = await import('js-yaml');
|
|
1105
|
+
if (jsYaml.default)
|
|
1106
|
+
jsYaml = jsYaml.default;
|
|
1107
|
+
}
|
|
1108
|
+
catch (e) {
|
|
1109
|
+
logger.warn(`Unable to import js-yaml module: ${e}`);
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
const metadata = jsYaml.load(match[1]);
|
|
1113
|
+
if (!metadata || !metadata.name || !metadata.description) {
|
|
1114
|
+
logger.debug(`Tool文件 ${toolMdPath} 缺少必需的 name/description 字段`);
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (seenNames.has(metadata.name)) {
|
|
1118
|
+
logger.debug(`Tool '${metadata.name}' 已由先序目录加载,跳过: ${toolMdPath}`);
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
seenNames.add(metadata.name);
|
|
1122
|
+
// 提取 body(frontmatter 之后的内容)
|
|
1123
|
+
const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*(?:\n|$)/, '').trim();
|
|
1124
|
+
tools.push({
|
|
1125
|
+
name: metadata.name,
|
|
1126
|
+
description: metadata.description,
|
|
1127
|
+
parameters: metadata.parameters || undefined,
|
|
1128
|
+
command: metadata.command || undefined,
|
|
1129
|
+
platforms: metadata.platforms,
|
|
1130
|
+
scopes: metadata.scopes,
|
|
1131
|
+
permissionLevel: metadata.permissionLevel,
|
|
1132
|
+
tags: metadata.tags || [],
|
|
1133
|
+
keywords: metadata.keywords || [],
|
|
1134
|
+
kind: metadata.kind,
|
|
1135
|
+
hidden: metadata.hidden,
|
|
1136
|
+
handler: metadata.handler,
|
|
1137
|
+
filePath: toolMdPath,
|
|
1138
|
+
templateBody: !metadata.handler && body ? body : undefined,
|
|
1139
|
+
});
|
|
1140
|
+
logger.debug(`Tool发现成功: ${metadata.name}`);
|
|
1141
|
+
}
|
|
1142
|
+
catch (e) {
|
|
1143
|
+
logger.warn(`Failed to parse tool.md in ${toolMdPath}:`, e);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (tools.length > 0) {
|
|
1148
|
+
logger.info(`发现 ${tools.length} 个工作区 Tool: ${tools.map(t => t.name).join(', ')}`);
|
|
1149
|
+
}
|
|
1150
|
+
return tools;
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* 将 ToolMeta 转换为 Tool 对象(包含 execute 函数)
|
|
1154
|
+
*/
|
|
1155
|
+
export async function buildToolFromMeta(meta) {
|
|
1156
|
+
// 构建 execute 函数
|
|
1157
|
+
let execute;
|
|
1158
|
+
if (meta.handler) {
|
|
1159
|
+
execute = await loadToolHandler(meta.handler, meta.filePath);
|
|
1160
|
+
if (!execute)
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
else if (meta.templateBody) {
|
|
1164
|
+
execute = buildTemplateExecute(meta.templateBody);
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
logger.warn(`Tool '${meta.name}' 既没有 handler 也没有模板 body,跳过`);
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
// 构建参数 schema
|
|
1171
|
+
const parameters = meta.parameters
|
|
1172
|
+
? shorthandToSchema(meta.parameters)
|
|
1173
|
+
: { type: 'object', properties: {} };
|
|
1174
|
+
return {
|
|
1175
|
+
name: meta.name,
|
|
1176
|
+
description: meta.description,
|
|
1177
|
+
parameters,
|
|
1178
|
+
execute,
|
|
1179
|
+
tags: meta.tags,
|
|
1180
|
+
keywords: meta.keywords,
|
|
1181
|
+
platforms: meta.platforms,
|
|
1182
|
+
scopes: meta.scopes,
|
|
1183
|
+
permissionLevel: meta.permissionLevel,
|
|
1184
|
+
hidden: meta.hidden,
|
|
1185
|
+
kind: meta.kind,
|
|
1186
|
+
command: meta.command ? {
|
|
1187
|
+
pattern: meta.command.pattern,
|
|
1188
|
+
alias: meta.command.alias,
|
|
1189
|
+
examples: meta.command.examples,
|
|
1190
|
+
} : undefined,
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
838
1193
|
//# sourceMappingURL=builtin-tools.js.map
|