kode-sdk 2.7.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/LICENSE +201 -0
- package/README.md +74 -0
- package/dist/core/agent/breakpoint-manager.d.ts +16 -0
- package/dist/core/agent/breakpoint-manager.js +36 -0
- package/dist/core/agent/message-queue.d.ts +26 -0
- package/dist/core/agent/message-queue.js +47 -0
- package/dist/core/agent/permission-manager.d.ts +9 -0
- package/dist/core/agent/permission-manager.js +32 -0
- package/dist/core/agent/todo-manager.d.ts +26 -0
- package/dist/core/agent/todo-manager.js +91 -0
- package/dist/core/agent/tool-runner.d.ts +9 -0
- package/dist/core/agent/tool-runner.js +45 -0
- package/dist/core/agent.d.ts +271 -0
- package/dist/core/agent.js +2334 -0
- package/dist/core/checkpointer.d.ts +96 -0
- package/dist/core/checkpointer.js +57 -0
- package/dist/core/checkpointers/file.d.ts +20 -0
- package/dist/core/checkpointers/file.js +153 -0
- package/dist/core/checkpointers/index.d.ts +3 -0
- package/dist/core/checkpointers/index.js +9 -0
- package/dist/core/checkpointers/redis.d.ts +35 -0
- package/dist/core/checkpointers/redis.js +113 -0
- package/dist/core/compression/ai-strategy.d.ts +53 -0
- package/dist/core/compression/ai-strategy.js +298 -0
- package/dist/core/compression/index.d.ts +12 -0
- package/dist/core/compression/index.js +27 -0
- package/dist/core/compression/prompts.d.ts +35 -0
- package/dist/core/compression/prompts.js +114 -0
- package/dist/core/compression/simple-strategy.d.ts +44 -0
- package/dist/core/compression/simple-strategy.js +240 -0
- package/dist/core/compression/token-estimator.d.ts +42 -0
- package/dist/core/compression/token-estimator.js +121 -0
- package/dist/core/compression/types.d.ts +140 -0
- package/dist/core/compression/types.js +9 -0
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.js +2 -0
- package/dist/core/context-manager.d.ts +115 -0
- package/dist/core/context-manager.js +107 -0
- package/dist/core/errors.d.ts +6 -0
- package/dist/core/errors.js +17 -0
- package/dist/core/events.d.ts +49 -0
- package/dist/core/events.js +312 -0
- package/dist/core/file-pool.d.ts +43 -0
- package/dist/core/file-pool.js +120 -0
- package/dist/core/hooks.d.ts +23 -0
- package/dist/core/hooks.js +71 -0
- package/dist/core/permission-modes.d.ts +31 -0
- package/dist/core/permission-modes.js +61 -0
- package/dist/core/pool.d.ts +31 -0
- package/dist/core/pool.js +87 -0
- package/dist/core/room.d.ts +15 -0
- package/dist/core/room.js +57 -0
- package/dist/core/scheduler.d.ts +33 -0
- package/dist/core/scheduler.js +58 -0
- package/dist/core/template.d.ts +69 -0
- package/dist/core/template.js +35 -0
- package/dist/core/time-bridge.d.ts +18 -0
- package/dist/core/time-bridge.js +100 -0
- package/dist/core/todo.d.ts +34 -0
- package/dist/core/todo.js +89 -0
- package/dist/core/types.d.ts +380 -0
- package/dist/core/types.js +3 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +147 -0
- package/dist/infra/provider.d.ts +144 -0
- package/dist/infra/provider.js +294 -0
- package/dist/infra/sandbox-factory.d.ts +10 -0
- package/dist/infra/sandbox-factory.js +21 -0
- package/dist/infra/sandbox.d.ts +87 -0
- package/dist/infra/sandbox.js +255 -0
- package/dist/infra/store.d.ts +154 -0
- package/dist/infra/store.js +584 -0
- package/dist/skills/index.d.ts +12 -0
- package/dist/skills/index.js +36 -0
- package/dist/skills/injector.d.ts +29 -0
- package/dist/skills/injector.js +96 -0
- package/dist/skills/loader.d.ts +59 -0
- package/dist/skills/loader.js +215 -0
- package/dist/skills/manager.d.ts +85 -0
- package/dist/skills/manager.js +221 -0
- package/dist/skills/parser.d.ts +40 -0
- package/dist/skills/parser.js +107 -0
- package/dist/skills/types.d.ts +107 -0
- package/dist/skills/types.js +7 -0
- package/dist/skills/validator.d.ts +30 -0
- package/dist/skills/validator.js +121 -0
- package/dist/store.d.ts +1 -0
- package/dist/store.js +5 -0
- package/dist/tools/bash_kill/index.d.ts +1 -0
- package/dist/tools/bash_kill/index.js +35 -0
- package/dist/tools/bash_kill/prompt.d.ts +2 -0
- package/dist/tools/bash_kill/prompt.js +14 -0
- package/dist/tools/bash_logs/index.d.ts +1 -0
- package/dist/tools/bash_logs/index.js +40 -0
- package/dist/tools/bash_logs/prompt.d.ts +2 -0
- package/dist/tools/bash_logs/prompt.js +14 -0
- package/dist/tools/bash_run/index.d.ts +16 -0
- package/dist/tools/bash_run/index.js +61 -0
- package/dist/tools/bash_run/prompt.d.ts +2 -0
- package/dist/tools/bash_run/prompt.js +18 -0
- package/dist/tools/builtin.d.ts +9 -0
- package/dist/tools/builtin.js +27 -0
- package/dist/tools/define.d.ts +101 -0
- package/dist/tools/define.js +214 -0
- package/dist/tools/fs_edit/index.d.ts +1 -0
- package/dist/tools/fs_edit/index.js +62 -0
- package/dist/tools/fs_edit/prompt.d.ts +2 -0
- package/dist/tools/fs_edit/prompt.js +15 -0
- package/dist/tools/fs_glob/index.d.ts +1 -0
- package/dist/tools/fs_glob/index.js +60 -0
- package/dist/tools/fs_glob/prompt.d.ts +2 -0
- package/dist/tools/fs_glob/prompt.js +18 -0
- package/dist/tools/fs_grep/index.d.ts +1 -0
- package/dist/tools/fs_grep/index.js +66 -0
- package/dist/tools/fs_grep/prompt.d.ts +2 -0
- package/dist/tools/fs_grep/prompt.js +16 -0
- package/dist/tools/fs_multi_edit/index.d.ts +1 -0
- package/dist/tools/fs_multi_edit/index.js +106 -0
- package/dist/tools/fs_multi_edit/prompt.d.ts +2 -0
- package/dist/tools/fs_multi_edit/prompt.js +16 -0
- package/dist/tools/fs_read/index.d.ts +1 -0
- package/dist/tools/fs_read/index.js +40 -0
- package/dist/tools/fs_read/prompt.d.ts +2 -0
- package/dist/tools/fs_read/prompt.js +16 -0
- package/dist/tools/fs_rm/index.d.ts +1 -0
- package/dist/tools/fs_rm/index.js +41 -0
- package/dist/tools/fs_rm/prompt.d.ts +2 -0
- package/dist/tools/fs_rm/prompt.js +14 -0
- package/dist/tools/fs_write/index.d.ts +1 -0
- package/dist/tools/fs_write/index.js +40 -0
- package/dist/tools/fs_write/prompt.d.ts +2 -0
- package/dist/tools/fs_write/prompt.js +15 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +56 -0
- package/dist/tools/mcp.d.ts +73 -0
- package/dist/tools/mcp.js +198 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +26 -0
- package/dist/tools/skill_activate/index.d.ts +5 -0
- package/dist/tools/skill_activate/index.js +63 -0
- package/dist/tools/skill_list/index.d.ts +5 -0
- package/dist/tools/skill_list/index.js +48 -0
- package/dist/tools/skill_resource/index.d.ts +5 -0
- package/dist/tools/skill_resource/index.js +82 -0
- package/dist/tools/task_run/index.d.ts +7 -0
- package/dist/tools/task_run/index.js +60 -0
- package/dist/tools/task_run/prompt.d.ts +5 -0
- package/dist/tools/task_run/prompt.js +29 -0
- package/dist/tools/todo_read/index.d.ts +1 -0
- package/dist/tools/todo_read/index.js +29 -0
- package/dist/tools/todo_read/prompt.d.ts +2 -0
- package/dist/tools/todo_read/prompt.js +18 -0
- package/dist/tools/todo_write/index.d.ts +1 -0
- package/dist/tools/todo_write/index.js +42 -0
- package/dist/tools/todo_write/prompt.d.ts +2 -0
- package/dist/tools/todo_write/prompt.js +23 -0
- package/dist/tools/tool.d.ts +43 -0
- package/dist/tools/tool.js +104 -0
- package/dist/tools/toolkit.d.ts +69 -0
- package/dist/tools/toolkit.js +98 -0
- package/dist/tools/type-inference.d.ts +127 -0
- package/dist/tools/type-inference.js +207 -0
- package/dist/utils/agent-id.d.ts +1 -0
- package/dist/utils/agent-id.js +28 -0
- package/dist/utils/session-id.d.ts +21 -0
- package/dist/utils/session-id.js +64 -0
- package/dist/utils/unicode.d.ts +17 -0
- package/dist/utils/unicode.js +62 -0
- package/package.json +117 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SkillsInjector = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Skills 系统提示注入器
|
|
6
|
+
*
|
|
7
|
+
* 将 Skills 元数据转换为 XML 格式并注入系统提示
|
|
8
|
+
*/
|
|
9
|
+
class SkillsInjector {
|
|
10
|
+
/**
|
|
11
|
+
* 生成 available_skills XML
|
|
12
|
+
*
|
|
13
|
+
* 遵循 Agent Skills 规范的推荐格式
|
|
14
|
+
*
|
|
15
|
+
* @param skills - Skills 列表(仅需元数据)
|
|
16
|
+
* @returns XML 格式的系统提示片段
|
|
17
|
+
*/
|
|
18
|
+
static toPromptXML(skills) {
|
|
19
|
+
if (skills.length === 0)
|
|
20
|
+
return '';
|
|
21
|
+
const skillsXml = skills
|
|
22
|
+
.map((skill) => {
|
|
23
|
+
const location = 'path' in skill ? `${skill.path}/SKILL.md` : '';
|
|
24
|
+
return ` <skill>
|
|
25
|
+
<name>${SkillsInjector.escapeXml(skill.name)}</name>
|
|
26
|
+
<description>${SkillsInjector.escapeXml(skill.description)}</description>${location ? `\n <location>${SkillsInjector.escapeXml(location)}</location>` : ''}
|
|
27
|
+
</skill>`;
|
|
28
|
+
})
|
|
29
|
+
.join('\n');
|
|
30
|
+
return `
|
|
31
|
+
<available_skills>
|
|
32
|
+
${skillsXml}
|
|
33
|
+
</available_skills>
|
|
34
|
+
|
|
35
|
+
When a task matches a skill's description, use skill_activate to load its full instructions.
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 生成激活后的 skill_instructions XML
|
|
40
|
+
*/
|
|
41
|
+
static toActivatedXML(skill) {
|
|
42
|
+
if (!skill.body) {
|
|
43
|
+
return `<skill_instructions name="${SkillsInjector.escapeXml(skill.name)}">
|
|
44
|
+
Skill body not loaded. Use skill_activate to load full content.
|
|
45
|
+
</skill_instructions>`;
|
|
46
|
+
}
|
|
47
|
+
let resourcesInfo = '';
|
|
48
|
+
if (skill.resources) {
|
|
49
|
+
const parts = [];
|
|
50
|
+
if (skill.resources.scripts?.length) {
|
|
51
|
+
parts.push(`Scripts: ${skill.resources.scripts.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
if (skill.resources.references?.length) {
|
|
54
|
+
parts.push(`References: ${skill.resources.references.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
if (skill.resources.assets?.length) {
|
|
57
|
+
parts.push(`Assets: ${skill.resources.assets.join(', ')}`);
|
|
58
|
+
}
|
|
59
|
+
if (parts.length > 0) {
|
|
60
|
+
resourcesInfo = `\n\n[Available Resources]\n${parts.join('\n')}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return `<skill_instructions name="${SkillsInjector.escapeXml(skill.name)}">
|
|
64
|
+
${skill.body}${resourcesInfo}
|
|
65
|
+
</skill_instructions>`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 转义 XML 特殊字符
|
|
69
|
+
*/
|
|
70
|
+
static escapeXml(text) {
|
|
71
|
+
return text
|
|
72
|
+
.replace(/&/g, '&')
|
|
73
|
+
.replace(/</g, '<')
|
|
74
|
+
.replace(/>/g, '>')
|
|
75
|
+
.replace(/"/g, '"')
|
|
76
|
+
.replace(/'/g, ''');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 生成 Skills 总结(用于上下文压缩)
|
|
80
|
+
*/
|
|
81
|
+
static toSummary(skills) {
|
|
82
|
+
if (skills.length === 0)
|
|
83
|
+
return '';
|
|
84
|
+
const activated = skills.filter((s) => s.activatedAt);
|
|
85
|
+
const available = skills.filter((s) => !s.activatedAt);
|
|
86
|
+
let summary = '[Skills Status]\n';
|
|
87
|
+
if (activated.length > 0) {
|
|
88
|
+
summary += `Activated: ${activated.map((s) => s.name).join(', ')}\n`;
|
|
89
|
+
}
|
|
90
|
+
if (available.length > 0) {
|
|
91
|
+
summary += `Available: ${available.map((s) => s.name).join(', ')}\n`;
|
|
92
|
+
}
|
|
93
|
+
return summary;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.SkillsInjector = SkillsInjector;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Sandbox } from '../infra/sandbox';
|
|
2
|
+
import { Skill, SkillResources, SkillsConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Skills 加载器
|
|
5
|
+
*
|
|
6
|
+
* 负责发现和加载 Skills
|
|
7
|
+
*/
|
|
8
|
+
export declare class SkillsLoader {
|
|
9
|
+
private readonly sandbox;
|
|
10
|
+
private readonly parser;
|
|
11
|
+
private readonly validator;
|
|
12
|
+
constructor(sandbox: Sandbox);
|
|
13
|
+
/**
|
|
14
|
+
* 发现所有 Skills(仅加载元数据,不加载完整内容)
|
|
15
|
+
*
|
|
16
|
+
* 这是 Progressive Disclosure 的第一阶段:
|
|
17
|
+
* 只加载 name 和 description,用于注入系统提示
|
|
18
|
+
*/
|
|
19
|
+
discover(config: SkillsConfig): Promise<Skill[]>;
|
|
20
|
+
/**
|
|
21
|
+
* 加载 Skill 元数据(不含完整正文)
|
|
22
|
+
*/
|
|
23
|
+
loadMetadata(skillDir: string, config?: SkillsConfig): Promise<Skill | null>;
|
|
24
|
+
/**
|
|
25
|
+
* 加载完整 Skill 内容(包括正文和资源列表)
|
|
26
|
+
*
|
|
27
|
+
* 这是 Progressive Disclosure 的第二阶段:
|
|
28
|
+
* 当 Agent 激活 Skill 时,加载完整内容
|
|
29
|
+
*/
|
|
30
|
+
loadFull(skillDir: string): Promise<Skill>;
|
|
31
|
+
/**
|
|
32
|
+
* 扫描 Skill 的资源目录
|
|
33
|
+
*/
|
|
34
|
+
scanResources(skillDir: string): Promise<SkillResources>;
|
|
35
|
+
/**
|
|
36
|
+
* 加载资源文件内容
|
|
37
|
+
*/
|
|
38
|
+
loadResource(skillDir: string, resourcePath: string): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* 查找包含 SKILL.md 的目录
|
|
41
|
+
*/
|
|
42
|
+
private findSkillDirs;
|
|
43
|
+
/**
|
|
44
|
+
* 列出目录中的文件
|
|
45
|
+
*/
|
|
46
|
+
private listFiles;
|
|
47
|
+
/**
|
|
48
|
+
* 列出目录中的文件,返回相对 skillDir 的路径(用于 skill_resource 的 resourcePath)
|
|
49
|
+
*/
|
|
50
|
+
private listFilesRelative;
|
|
51
|
+
/**
|
|
52
|
+
* 检查路径是否存在
|
|
53
|
+
*/
|
|
54
|
+
private pathExists;
|
|
55
|
+
/**
|
|
56
|
+
* 检查是否应该包含该 Skill
|
|
57
|
+
*/
|
|
58
|
+
private shouldInclude;
|
|
59
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SkillsLoader = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const parser_1 = require("./parser");
|
|
9
|
+
const validator_1 = require("./validator");
|
|
10
|
+
/**
|
|
11
|
+
* Skills 加载器
|
|
12
|
+
*
|
|
13
|
+
* 负责发现和加载 Skills
|
|
14
|
+
*/
|
|
15
|
+
class SkillsLoader {
|
|
16
|
+
constructor(sandbox) {
|
|
17
|
+
this.sandbox = sandbox;
|
|
18
|
+
this.parser = new parser_1.SkillsParser();
|
|
19
|
+
this.validator = new validator_1.SkillsValidator();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 发现所有 Skills(仅加载元数据,不加载完整内容)
|
|
23
|
+
*
|
|
24
|
+
* 这是 Progressive Disclosure 的第一阶段:
|
|
25
|
+
* 只加载 name 和 description,用于注入系统提示
|
|
26
|
+
*/
|
|
27
|
+
async discover(config) {
|
|
28
|
+
const skills = [];
|
|
29
|
+
for (const searchPath of config.paths) {
|
|
30
|
+
try {
|
|
31
|
+
const resolvedPath = this.sandbox.fs.resolve(searchPath);
|
|
32
|
+
const exists = await this.pathExists(resolvedPath);
|
|
33
|
+
if (!exists)
|
|
34
|
+
continue;
|
|
35
|
+
const dirs = await this.findSkillDirs(resolvedPath);
|
|
36
|
+
for (const dir of dirs) {
|
|
37
|
+
try {
|
|
38
|
+
const skill = await this.loadMetadata(dir, config);
|
|
39
|
+
if (skill && this.shouldInclude(skill.name, config)) {
|
|
40
|
+
skills.push(skill);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// 跳过无效的 Skill 目录,继续处理其他
|
|
45
|
+
console.warn(`[SkillsLoader] Failed to load skill from ${dir}:`, error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.warn(`[SkillsLoader] Failed to scan path ${searchPath}:`, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return skills;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 加载 Skill 元数据(不含完整正文)
|
|
57
|
+
*/
|
|
58
|
+
async loadMetadata(skillDir, config) {
|
|
59
|
+
const skillMdPath = path_1.default.join(skillDir, 'SKILL.md');
|
|
60
|
+
const exists = await this.pathExists(skillMdPath);
|
|
61
|
+
if (!exists) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const content = await this.sandbox.fs.read(skillMdPath);
|
|
65
|
+
const { metadata } = this.parser.parse(content);
|
|
66
|
+
// 可选:验证格式
|
|
67
|
+
if (config?.validateOnLoad) {
|
|
68
|
+
const validation = this.validator.validate(metadata);
|
|
69
|
+
if (!validation.valid) {
|
|
70
|
+
throw new Error(`Skill validation failed: ${validation.errors.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...metadata,
|
|
75
|
+
path: skillDir,
|
|
76
|
+
loadedAt: Date.now(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 加载完整 Skill 内容(包括正文和资源列表)
|
|
81
|
+
*
|
|
82
|
+
* 这是 Progressive Disclosure 的第二阶段:
|
|
83
|
+
* 当 Agent 激活 Skill 时,加载完整内容
|
|
84
|
+
*/
|
|
85
|
+
async loadFull(skillDir) {
|
|
86
|
+
const skillMdPath = path_1.default.join(skillDir, 'SKILL.md');
|
|
87
|
+
const content = await this.sandbox.fs.read(skillMdPath);
|
|
88
|
+
const { metadata, body } = this.parser.parse(content);
|
|
89
|
+
const resources = await this.scanResources(skillDir);
|
|
90
|
+
return {
|
|
91
|
+
...metadata,
|
|
92
|
+
path: skillDir,
|
|
93
|
+
body,
|
|
94
|
+
resources,
|
|
95
|
+
loadedAt: Date.now(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 扫描 Skill 的资源目录
|
|
100
|
+
*/
|
|
101
|
+
async scanResources(skillDir) {
|
|
102
|
+
const resources = {};
|
|
103
|
+
// 扫描 scripts/
|
|
104
|
+
const scriptsDir = path_1.default.join(skillDir, 'scripts');
|
|
105
|
+
if (await this.pathExists(scriptsDir)) {
|
|
106
|
+
resources.scripts = await this.listFilesRelative(skillDir, scriptsDir);
|
|
107
|
+
}
|
|
108
|
+
// 扫描 references/
|
|
109
|
+
const referencesDir = path_1.default.join(skillDir, 'references');
|
|
110
|
+
if (await this.pathExists(referencesDir)) {
|
|
111
|
+
resources.references = await this.listFilesRelative(skillDir, referencesDir);
|
|
112
|
+
}
|
|
113
|
+
// 扫描 assets/
|
|
114
|
+
const assetsDir = path_1.default.join(skillDir, 'assets');
|
|
115
|
+
if (await this.pathExists(assetsDir)) {
|
|
116
|
+
resources.assets = await this.listFilesRelative(skillDir, assetsDir);
|
|
117
|
+
}
|
|
118
|
+
return resources;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 加载资源文件内容
|
|
122
|
+
*/
|
|
123
|
+
async loadResource(skillDir, resourcePath) {
|
|
124
|
+
const fullPath = path_1.default.join(skillDir, resourcePath);
|
|
125
|
+
// 安全检查:防止路径遍历
|
|
126
|
+
const resolvedFull = path_1.default.resolve(fullPath);
|
|
127
|
+
const resolvedSkill = path_1.default.resolve(skillDir);
|
|
128
|
+
if (!resolvedFull.startsWith(resolvedSkill + path_1.default.sep) && resolvedFull !== resolvedSkill) {
|
|
129
|
+
throw new Error('Path traversal not allowed');
|
|
130
|
+
}
|
|
131
|
+
return await this.sandbox.fs.read(fullPath);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 查找包含 SKILL.md 的目录
|
|
135
|
+
*/
|
|
136
|
+
async findSkillDirs(searchPath) {
|
|
137
|
+
const skillDirs = [];
|
|
138
|
+
try {
|
|
139
|
+
// 使用 glob 查找所有 SKILL.md 文件
|
|
140
|
+
const pattern = '*/SKILL.md';
|
|
141
|
+
const matches = await this.sandbox.fs.glob(pattern, {
|
|
142
|
+
cwd: searchPath,
|
|
143
|
+
absolute: true,
|
|
144
|
+
});
|
|
145
|
+
for (const skillMdPath of matches) {
|
|
146
|
+
// 获取 SKILL.md 所在目录
|
|
147
|
+
const skillDir = path_1.default.dirname(skillMdPath);
|
|
148
|
+
skillDirs.push(skillDir);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// 搜索路径不存在或无法读取
|
|
153
|
+
}
|
|
154
|
+
return skillDirs;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 列出目录中的文件
|
|
158
|
+
*/
|
|
159
|
+
async listFiles(dirPath) {
|
|
160
|
+
try {
|
|
161
|
+
// 使用 glob 列出目录中的文件
|
|
162
|
+
const pattern = '*';
|
|
163
|
+
const matches = await this.sandbox.fs.glob(pattern, {
|
|
164
|
+
cwd: dirPath,
|
|
165
|
+
absolute: false,
|
|
166
|
+
});
|
|
167
|
+
return matches;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 列出目录中的文件,返回相对 skillDir 的路径(用于 skill_resource 的 resourcePath)
|
|
175
|
+
*/
|
|
176
|
+
async listFilesRelative(skillDir, dirPath) {
|
|
177
|
+
try {
|
|
178
|
+
const matches = await this.sandbox.fs.glob('*', {
|
|
179
|
+
cwd: dirPath,
|
|
180
|
+
absolute: true,
|
|
181
|
+
});
|
|
182
|
+
return matches.map((abs) => path_1.default.relative(skillDir, abs).split(path_1.default.sep).join('/'));
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 检查路径是否存在
|
|
190
|
+
*/
|
|
191
|
+
async pathExists(filePath) {
|
|
192
|
+
try {
|
|
193
|
+
await this.sandbox.fs.stat(filePath);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 检查是否应该包含该 Skill
|
|
202
|
+
*/
|
|
203
|
+
shouldInclude(name, config) {
|
|
204
|
+
// 检查黑名单
|
|
205
|
+
if (config.exclude?.includes(name)) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
// 如果有白名单,只包含白名单中的
|
|
209
|
+
if (config.include && config.include.length > 0) {
|
|
210
|
+
return config.include.includes(name);
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.SkillsLoader = SkillsLoader;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Sandbox } from '../infra/sandbox';
|
|
2
|
+
import { Store } from '../infra/store';
|
|
3
|
+
import { Skill, SkillMetadata, SkillsConfig, SkillActivation } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Skills 状态管理器
|
|
6
|
+
*
|
|
7
|
+
* 管理 Skills 的发现、激活和持久化状态
|
|
8
|
+
*/
|
|
9
|
+
export declare class SkillsManager {
|
|
10
|
+
private readonly config;
|
|
11
|
+
private readonly sandbox;
|
|
12
|
+
private readonly store?;
|
|
13
|
+
private readonly agentId?;
|
|
14
|
+
private readonly discovered;
|
|
15
|
+
private readonly activated;
|
|
16
|
+
private readonly loader;
|
|
17
|
+
constructor(config: SkillsConfig, sandbox: Sandbox, store?: Store | undefined, agentId?: string | undefined);
|
|
18
|
+
/**
|
|
19
|
+
* 发现所有 Skills(仅加载元数据)
|
|
20
|
+
*
|
|
21
|
+
* 这是 Progressive Disclosure 的第一阶段
|
|
22
|
+
*/
|
|
23
|
+
discover(): Promise<SkillMetadata[]>;
|
|
24
|
+
/**
|
|
25
|
+
* 激活 Skill(加载完整内容)
|
|
26
|
+
*
|
|
27
|
+
* 这是 Progressive Disclosure 的第二阶段
|
|
28
|
+
*/
|
|
29
|
+
activate(name: string, activatedBy?: 'auto' | 'agent' | 'user'): Promise<Skill>;
|
|
30
|
+
/**
|
|
31
|
+
* 停用 Skill
|
|
32
|
+
*/
|
|
33
|
+
deactivate(name: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* 批量自动激活 Skills
|
|
36
|
+
*
|
|
37
|
+
* 用于 Template 配置的 autoActivate Skills
|
|
38
|
+
* 静默失败:如果 Skill 不存在则跳过,不抛错
|
|
39
|
+
*/
|
|
40
|
+
autoActivateSkills(names: string[]): Promise<Skill[]>;
|
|
41
|
+
/**
|
|
42
|
+
* 获取已发现的 Skills 列表
|
|
43
|
+
*/
|
|
44
|
+
list(): Skill[];
|
|
45
|
+
/**
|
|
46
|
+
* 获取单个 Skill
|
|
47
|
+
*/
|
|
48
|
+
get(name: string): Skill | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* 检查 Skill 是否已激活
|
|
51
|
+
*/
|
|
52
|
+
isActivated(name: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* 获取已激活的 Skills
|
|
55
|
+
*/
|
|
56
|
+
getActivated(): Skill[];
|
|
57
|
+
/**
|
|
58
|
+
* 获取 Skill 的激活信息
|
|
59
|
+
*/
|
|
60
|
+
getActivation(name: string): SkillActivation | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* 检查 Skill 是否来自可信来源
|
|
63
|
+
*/
|
|
64
|
+
isTrusted(name: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* 获取 Skill 声明的 allowed-tools
|
|
67
|
+
*/
|
|
68
|
+
getAllowedTools(name: string): string[] | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* 加载 Skill 的资源文件
|
|
71
|
+
*/
|
|
72
|
+
loadResource(skillName: string, resourcePath: string): Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* 持久化状态到 Store
|
|
75
|
+
*/
|
|
76
|
+
persistState(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* 从 Store 恢复状态(用于 Resume)
|
|
79
|
+
*/
|
|
80
|
+
restoreState(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* 获取 Skills 配置
|
|
83
|
+
*/
|
|
84
|
+
getConfig(): SkillsConfig;
|
|
85
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SkillsManager = void 0;
|
|
4
|
+
const loader_1 = require("./loader");
|
|
5
|
+
/**
|
|
6
|
+
* Skills 状态管理器
|
|
7
|
+
*
|
|
8
|
+
* 管理 Skills 的发现、激活和持久化状态
|
|
9
|
+
*/
|
|
10
|
+
class SkillsManager {
|
|
11
|
+
constructor(config, sandbox, store, agentId) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.sandbox = sandbox;
|
|
14
|
+
this.store = store;
|
|
15
|
+
this.agentId = agentId;
|
|
16
|
+
this.discovered = new Map();
|
|
17
|
+
this.activated = new Map();
|
|
18
|
+
this.loader = new loader_1.SkillsLoader(sandbox);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 发现所有 Skills(仅加载元数据)
|
|
22
|
+
*
|
|
23
|
+
* 这是 Progressive Disclosure 的第一阶段
|
|
24
|
+
*/
|
|
25
|
+
async discover() {
|
|
26
|
+
const skills = await this.loader.discover(this.config);
|
|
27
|
+
// Refresh discovery snapshot: remove skills that no longer exist on disk.
|
|
28
|
+
// Preserve previously loaded bodies/resources when possible.
|
|
29
|
+
const previous = new Map(this.discovered);
|
|
30
|
+
this.discovered.clear();
|
|
31
|
+
// Deduplicate by name while keeping later entries higher priority.
|
|
32
|
+
// This matters when multiple search paths contain the same skill name
|
|
33
|
+
// (e.g. bundled skills vs user-level overrides).
|
|
34
|
+
const unique = new Map();
|
|
35
|
+
for (const skill of skills) {
|
|
36
|
+
if (!skill?.name)
|
|
37
|
+
continue;
|
|
38
|
+
if (unique.has(skill.name)) {
|
|
39
|
+
// Move to the end to reflect "last wins" semantics.
|
|
40
|
+
unique.delete(skill.name);
|
|
41
|
+
console.warn(`[SkillsManager] Duplicate skill "${skill.name}" discovered; using the later one.`);
|
|
42
|
+
}
|
|
43
|
+
unique.set(skill.name, skill);
|
|
44
|
+
}
|
|
45
|
+
for (const skill of unique.values()) {
|
|
46
|
+
const prev = previous.get(skill.name);
|
|
47
|
+
if (prev) {
|
|
48
|
+
if (prev.body && !skill.body)
|
|
49
|
+
skill.body = prev.body;
|
|
50
|
+
if (prev.resources && !skill.resources)
|
|
51
|
+
skill.resources = prev.resources;
|
|
52
|
+
if (typeof prev.activatedAt === 'number')
|
|
53
|
+
skill.activatedAt = prev.activatedAt;
|
|
54
|
+
}
|
|
55
|
+
this.discovered.set(skill.name, skill);
|
|
56
|
+
}
|
|
57
|
+
return Array.from(unique.values());
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 激活 Skill(加载完整内容)
|
|
61
|
+
*
|
|
62
|
+
* 这是 Progressive Disclosure 的第二阶段
|
|
63
|
+
*/
|
|
64
|
+
async activate(name, activatedBy = 'agent') {
|
|
65
|
+
const skill = this.discovered.get(name);
|
|
66
|
+
if (!skill) {
|
|
67
|
+
throw new Error(`Skill not found: ${name}. Available skills: ${Array.from(this.discovered.keys()).join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
// 如果尚未加载完整内容,则加载
|
|
70
|
+
if (!skill.body) {
|
|
71
|
+
const fullSkill = await this.loader.loadFull(skill.path);
|
|
72
|
+
Object.assign(skill, fullSkill);
|
|
73
|
+
this.discovered.set(name, skill);
|
|
74
|
+
}
|
|
75
|
+
// 记录激活状态
|
|
76
|
+
skill.activatedAt = Date.now();
|
|
77
|
+
const activation = {
|
|
78
|
+
name,
|
|
79
|
+
activatedAt: skill.activatedAt,
|
|
80
|
+
activatedBy,
|
|
81
|
+
toolsGranted: skill.allowedTools,
|
|
82
|
+
};
|
|
83
|
+
this.activated.set(name, activation);
|
|
84
|
+
// 持久化状态
|
|
85
|
+
await this.persistState();
|
|
86
|
+
return skill;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 停用 Skill
|
|
90
|
+
*/
|
|
91
|
+
async deactivate(name) {
|
|
92
|
+
this.activated.delete(name);
|
|
93
|
+
const skill = this.discovered.get(name);
|
|
94
|
+
if (skill) {
|
|
95
|
+
skill.activatedAt = undefined;
|
|
96
|
+
}
|
|
97
|
+
await this.persistState();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 批量自动激活 Skills
|
|
101
|
+
*
|
|
102
|
+
* 用于 Template 配置的 autoActivate Skills
|
|
103
|
+
* 静默失败:如果 Skill 不存在则跳过,不抛错
|
|
104
|
+
*/
|
|
105
|
+
async autoActivateSkills(names) {
|
|
106
|
+
const activated = [];
|
|
107
|
+
for (const name of names) {
|
|
108
|
+
if (!this.discovered.has(name)) {
|
|
109
|
+
// 静默跳过不存在的 Skill
|
|
110
|
+
console.warn(`[SkillsManager] Skill "${name}" not found for auto-activation, skipping`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (this.isActivated(name)) {
|
|
114
|
+
// 已激活则跳过
|
|
115
|
+
const skill = this.discovered.get(name);
|
|
116
|
+
if (skill)
|
|
117
|
+
activated.push(skill);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const skill = await this.activate(name, 'auto');
|
|
122
|
+
activated.push(skill);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.warn(`[SkillsManager] Failed to auto-activate skill "${name}":`, error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return activated;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 获取已发现的 Skills 列表
|
|
132
|
+
*/
|
|
133
|
+
list() {
|
|
134
|
+
return Array.from(this.discovered.values());
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 获取单个 Skill
|
|
138
|
+
*/
|
|
139
|
+
get(name) {
|
|
140
|
+
return this.discovered.get(name);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 检查 Skill 是否已激活
|
|
144
|
+
*/
|
|
145
|
+
isActivated(name) {
|
|
146
|
+
return this.activated.has(name);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 获取已激活的 Skills
|
|
150
|
+
*/
|
|
151
|
+
getActivated() {
|
|
152
|
+
return Array.from(this.activated.keys())
|
|
153
|
+
.map((name) => this.discovered.get(name))
|
|
154
|
+
.filter((s) => s !== undefined);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 获取 Skill 的激活信息
|
|
158
|
+
*/
|
|
159
|
+
getActivation(name) {
|
|
160
|
+
return this.activated.get(name);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 检查 Skill 是否来自可信来源
|
|
164
|
+
*/
|
|
165
|
+
isTrusted(name) {
|
|
166
|
+
if (!this.config.trusted || this.config.trusted.length === 0) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
return this.config.trusted.includes(name);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 获取 Skill 声明的 allowed-tools
|
|
173
|
+
*/
|
|
174
|
+
getAllowedTools(name) {
|
|
175
|
+
return this.discovered.get(name)?.allowedTools;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 加载 Skill 的资源文件
|
|
179
|
+
*/
|
|
180
|
+
async loadResource(skillName, resourcePath) {
|
|
181
|
+
const skill = this.discovered.get(skillName);
|
|
182
|
+
if (!skill) {
|
|
183
|
+
throw new Error(`Skill not found: ${skillName}`);
|
|
184
|
+
}
|
|
185
|
+
return await this.loader.loadResource(skill.path, resourcePath);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 持久化状态到 Store
|
|
189
|
+
*/
|
|
190
|
+
async persistState() {
|
|
191
|
+
if (!this.store || !this.agentId)
|
|
192
|
+
return;
|
|
193
|
+
const state = {
|
|
194
|
+
discovered: Array.from(this.discovered.keys()),
|
|
195
|
+
activated: Array.from(this.activated.values()),
|
|
196
|
+
lastDiscoveryAt: Date.now(),
|
|
197
|
+
};
|
|
198
|
+
await this.store.saveSkillsState(this.agentId, state);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 从 Store 恢复状态(用于 Resume)
|
|
202
|
+
*/
|
|
203
|
+
async restoreState() {
|
|
204
|
+
if (!this.store || !this.agentId)
|
|
205
|
+
return;
|
|
206
|
+
const state = await this.store.loadSkillsState(this.agentId);
|
|
207
|
+
if (!state)
|
|
208
|
+
return;
|
|
209
|
+
// 恢复激活状态
|
|
210
|
+
for (const activation of state.activated) {
|
|
211
|
+
this.activated.set(activation.name, activation);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 获取 Skills 配置
|
|
216
|
+
*/
|
|
217
|
+
getConfig() {
|
|
218
|
+
return this.config;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.SkillsManager = SkillsManager;
|