work-agent 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 +234 -0
- package/app/(admin)/approvals/page.tsx +16 -0
- package/app/(admin)/audit/page.tsx +18 -0
- package/app/(admin)/layout.tsx +47 -0
- package/app/(admin)/scheduled-tasks/page.tsx +17 -0
- package/app/(admin)/settings/page.tsx +46 -0
- package/app/(admin)/skills/[name]/page.tsx +378 -0
- package/app/(admin)/skills/page.tsx +406 -0
- package/app/(admin)/statistics/page.tsx +416 -0
- package/app/(admin)/tickets/[id]/page.tsx +348 -0
- package/app/(admin)/tickets/new/page.tsx +309 -0
- package/app/(admin)/tickets/page.tsx +27 -0
- package/app/api/audit/route.ts +30 -0
- package/app/api/auth/feishu/callback/route.ts +72 -0
- package/app/api/auth/feishu/login/route.ts +17 -0
- package/app/api/auth/feishu/sso/route.ts +78 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/oauth/route.ts +168 -0
- package/app/api/config/providers/route.ts +105 -0
- package/app/api/config/route.ts +115 -0
- package/app/api/config/status/route.ts +56 -0
- package/app/api/config/test/route.ts +212 -0
- package/app/api/documents/[id]/route.ts +88 -0
- package/app/api/documents/route.ts +53 -0
- package/app/api/health/route.ts +32 -0
- package/app/api/knowledge/[id]/route.ts +152 -0
- package/app/api/knowledge/from-session/route.ts +27 -0
- package/app/api/knowledge/route.ts +100 -0
- package/app/api/market/knowledge/[id]/route.ts +92 -0
- package/app/api/market/knowledge/route.ts +130 -0
- package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
- package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
- package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
- package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
- package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
- package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/route.ts +177 -0
- package/app/api/marketplace/skills/route.ts +235 -0
- package/app/api/memory/route.ts +40 -0
- package/app/api/my/files/[id]/route.ts +52 -0
- package/app/api/my/files/route.ts +230 -0
- package/app/api/my/knowledge/route.ts +36 -0
- package/app/api/pi-chat/route.ts +443 -0
- package/app/api/recommend/route.ts +38 -0
- package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
- package/app/api/scheduled-tasks/[id]/route.ts +165 -0
- package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
- package/app/api/scheduled-tasks/route.ts +101 -0
- package/app/api/sessions/[id]/messages/route.ts +212 -0
- package/app/api/sessions/route.ts +101 -0
- package/app/api/share/file/[id]/route.ts +37 -0
- package/app/api/skills/[name]/execute/route.ts +121 -0
- package/app/api/skills/[name]/route.ts +167 -0
- package/app/api/skills/create/route.ts +65 -0
- package/app/api/skills/generate/route.ts +405 -0
- package/app/api/skills/installed/route.ts +151 -0
- package/app/api/skills/route.ts +174 -0
- package/app/api/skills/translate/route.ts +40 -0
- package/app/api/skills/user/[name]/route.ts +159 -0
- package/app/api/skills/user/route.ts +90 -0
- package/app/api/statistics/route.ts +94 -0
- package/app/api/task-executions/[id]/route.ts +34 -0
- package/app/api/task-executions/route.ts +29 -0
- package/app/api/tickets/[id]/approve/route.ts +129 -0
- package/app/api/tickets/[id]/execute/route.ts +201 -0
- package/app/api/tickets/[id]/route.ts +127 -0
- package/app/api/tickets/route.ts +103 -0
- package/app/api/user/skills/route.ts +175 -0
- package/app/api/users/route.ts +80 -0
- package/app/chat/page.tsx +5 -0
- package/app/globals.css +84 -0
- package/app/h5/layout.tsx +5 -0
- package/app/h5/mobile-approvals-page.tsx +167 -0
- package/app/h5/mobile-chat-page.tsx +951 -0
- package/app/h5/mobile-profile-page.tsx +147 -0
- package/app/h5/mobile-tickets-page.tsx +121 -0
- package/app/h5/page.tsx +23 -0
- package/app/h5/ticket-action-buttons.tsx +80 -0
- package/app/layout.tsx +26 -0
- package/app/login/page.tsx +318 -0
- package/app/market/knowledge/[id]/page.tsx +77 -0
- package/app/market/knowledge/page.tsx +358 -0
- package/app/market/layout.tsx +29 -0
- package/app/market/page.tsx +18 -0
- package/app/market/skills/page.tsx +397 -0
- package/app/my/files/page.tsx +511 -0
- package/app/my/knowledge/[id]/page.tsx +271 -0
- package/app/my/knowledge/new/page.tsx +234 -0
- package/app/my/knowledge/page.tsx +248 -0
- package/app/my/layout.tsx +32 -0
- package/app/my/memory/page.tsx +164 -0
- package/app/my/page.tsx +18 -0
- package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
- package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
- package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
- package/app/my/scheduled-tasks/new/page.tsx +230 -0
- package/app/my/scheduled-tasks/page.tsx +27 -0
- package/app/my/skills/[name]/page.tsx +320 -0
- package/app/my/skills/new/page.tsx +394 -0
- package/app/my/skills/page.tsx +303 -0
- package/app/page.tsx +2288 -0
- package/app/share/[sessionId]/page.tsx +226 -0
- package/app/share/file/[id]/page.tsx +140 -0
- package/bin/README.md +63 -0
- package/bin/generate-api-system +300 -0
- package/bin/postinstall.js +95 -0
- package/bin/work-agent.js +173 -0
- package/components/ai-elements/agent.tsx +142 -0
- package/components/ai-elements/artifact.tsx +149 -0
- package/components/ai-elements/attachments.tsx +427 -0
- package/components/ai-elements/audio-player.tsx +232 -0
- package/components/ai-elements/canvas.tsx +26 -0
- package/components/ai-elements/chain-of-thought.tsx +223 -0
- package/components/ai-elements/checkpoint.tsx +72 -0
- package/components/ai-elements/code-block.tsx +555 -0
- package/components/ai-elements/commit.tsx +449 -0
- package/components/ai-elements/confirmation.tsx +173 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +410 -0
- package/components/ai-elements/controls.tsx +19 -0
- package/components/ai-elements/conversation.tsx +167 -0
- package/components/ai-elements/edge.tsx +144 -0
- package/components/ai-elements/environment-variables.tsx +325 -0
- package/components/ai-elements/file-tree.tsx +298 -0
- package/components/ai-elements/image.tsx +25 -0
- package/components/ai-elements/inline-citation.tsx +294 -0
- package/components/ai-elements/jsx-preview.tsx +250 -0
- package/components/ai-elements/message.tsx +367 -0
- package/components/ai-elements/mic-selector.tsx +372 -0
- package/components/ai-elements/model-selector.tsx +214 -0
- package/components/ai-elements/node.tsx +72 -0
- package/components/ai-elements/open-in-chat.tsx +367 -0
- package/components/ai-elements/package-info.tsx +235 -0
- package/components/ai-elements/panel.tsx +16 -0
- package/components/ai-elements/persona.tsx +280 -0
- package/components/ai-elements/plan.tsx +144 -0
- package/components/ai-elements/prompt-input.tsx +1341 -0
- package/components/ai-elements/queue.tsx +275 -0
- package/components/ai-elements/reasoning.tsx +355 -0
- package/components/ai-elements/sandbox.tsx +133 -0
- package/components/ai-elements/schema-display.tsx +473 -0
- package/components/ai-elements/shimmer.tsx +78 -0
- package/components/ai-elements/snippet.tsx +141 -0
- package/components/ai-elements/sources.tsx +78 -0
- package/components/ai-elements/speech-input.tsx +324 -0
- package/components/ai-elements/stack-trace.tsx +531 -0
- package/components/ai-elements/suggestion.tsx +58 -0
- package/components/ai-elements/task.tsx +88 -0
- package/components/ai-elements/terminal.tsx +277 -0
- package/components/ai-elements/test-results.tsx +497 -0
- package/components/ai-elements/tool.tsx +174 -0
- package/components/ai-elements/toolbar.tsx +17 -0
- package/components/ai-elements/transcription.tsx +126 -0
- package/components/ai-elements/voice-selector.tsx +525 -0
- package/components/ai-elements/web-preview.tsx +282 -0
- package/components/audit-log-list.tsx +114 -0
- package/components/chat/EmptyPreviewState.tsx +12 -0
- package/components/chat/KnowledgePickerDialog.tsx +464 -0
- package/components/chat/KnowledgePreview.tsx +70 -0
- package/components/chat/KnowledgePreviewPanel.tsx +86 -0
- package/components/chat/MentionInput.tsx +309 -0
- package/components/chat/OrganizeDialog.tsx +258 -0
- package/components/chat/RecommendationBanner.tsx +94 -0
- package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
- package/components/chat/SkillSelector.tsx +305 -0
- package/components/chat/SkillSwitcher.tsx +163 -0
- package/components/client-layout.tsx +15 -0
- package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
- package/components/layout-wrapper.tsx +18 -0
- package/components/mobile-layout.tsx +62 -0
- package/components/scheduled-task-list.tsx +356 -0
- package/components/setup-guide.tsx +484 -0
- package/components/sub-nav.tsx +54 -0
- package/components/ticket-detail-content.tsx +383 -0
- package/components/ticket-list.tsx +366 -0
- package/components/top-nav.tsx +132 -0
- package/components/ui/accordion.tsx +58 -0
- package/components/ui/alert.tsx +59 -0
- package/components/ui/avatar.tsx +50 -0
- package/components/ui/badge.tsx +36 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/card.tsx +91 -0
- package/components/ui/carousel.tsx +262 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +153 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/dropdown-menu.tsx +200 -0
- package/components/ui/hover-card.tsx +29 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +31 -0
- package/components/ui/progress.tsx +28 -0
- package/components/ui/scroll-area.tsx +48 -0
- package/components/ui/select.tsx +174 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +120 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/textarea.tsx +22 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/welcome-guide.tsx +182 -0
- package/components.json +24 -0
- package/lib/command-parser.ts +331 -0
- package/lib/dangerous-commands.ts +672 -0
- package/lib/db.ts +2250 -0
- package/lib/feishu-auth.ts +135 -0
- package/lib/file-storage.ts +306 -0
- package/lib/file-tool.ts +583 -0
- package/lib/knowledge-tool.ts +152 -0
- package/lib/knowledge-types.ts +66 -0
- package/lib/market-client.ts +313 -0
- package/lib/market-db.ts +736 -0
- package/lib/market-types.ts +51 -0
- package/lib/memory-tool.ts +211 -0
- package/lib/memory.ts +197 -0
- package/lib/pi-config.ts +436 -0
- package/lib/pi-session.ts +799 -0
- package/lib/pinyin.ts +13 -0
- package/lib/recommendation.ts +227 -0
- package/lib/risk-estimator.ts +350 -0
- package/lib/scheduled-task-tool.ts +184 -0
- package/lib/scheduler-init.ts +43 -0
- package/lib/scheduler.ts +416 -0
- package/lib/secure-bash-tool.ts +413 -0
- package/lib/skill-engine.ts +396 -0
- package/lib/skill-generator.ts +269 -0
- package/lib/skill-loader.ts +234 -0
- package/lib/skill-tool.ts +188 -0
- package/lib/skill-types.ts +82 -0
- package/lib/skills-init.ts +58 -0
- package/lib/ticket-tool.ts +246 -0
- package/lib/user-skill-types.ts +30 -0
- package/lib/user-skills.ts +362 -0
- package/lib/utils.ts +6 -0
- package/lib/workflow.ts +154 -0
- package/lib/zip-tool.ts +191 -0
- package/next.config.js +8 -0
- package/package.json +106 -0
- package/public/.gitkeep +1 -0
- package/public/icon.svg +1 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一技能加载器
|
|
3
|
+
*
|
|
4
|
+
* 支持从多个来源加载技能:
|
|
5
|
+
* 1. 系统内置技能 (.pi/skills/)
|
|
6
|
+
* 2. 用户私有技能 (data/users/{userId}/skills/)
|
|
7
|
+
* 3. 市场已安装技能 (data/users/{userId}/skills/)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 技能来源枚举
|
|
15
|
+
*/
|
|
16
|
+
export enum SkillSource {
|
|
17
|
+
System = 'system', // 系统内置
|
|
18
|
+
User = 'user', // 用户私有
|
|
19
|
+
Market = 'market' // 市场已安装
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 技能位置信息
|
|
24
|
+
*/
|
|
25
|
+
export interface SkillLocation {
|
|
26
|
+
source: SkillSource;
|
|
27
|
+
path: string; // 实际文件系统路径
|
|
28
|
+
userId?: string; // 对于用户技能
|
|
29
|
+
marketId?: string; // 对于市场技能
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取系统内置技能列表
|
|
34
|
+
*/
|
|
35
|
+
function loadSystemSkills(skills: Map<string, SkillLocation>): void {
|
|
36
|
+
const systemSkillsDir = join(process.cwd(), '.pi', 'skills');
|
|
37
|
+
|
|
38
|
+
if (!existsSync(systemSkillsDir)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const entries = readdirSync(systemSkillsDir, { withFileTypes: true });
|
|
43
|
+
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (!entry.isDirectory()) continue;
|
|
46
|
+
|
|
47
|
+
const skillName = entry.name;
|
|
48
|
+
const skillPath = join(systemSkillsDir, skillName);
|
|
49
|
+
|
|
50
|
+
// 验证技能文件存在
|
|
51
|
+
if (existsSync(join(skillPath, 'SKILL.md'))) {
|
|
52
|
+
skills.set(skillName, {
|
|
53
|
+
source: SkillSource.System,
|
|
54
|
+
path: skillPath,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取用户私有技能列表
|
|
62
|
+
*/
|
|
63
|
+
function loadUserSkills(userId: string | undefined, skills: Map<string, SkillLocation>): void {
|
|
64
|
+
// 如果没有userId,跳过用户技能加载
|
|
65
|
+
if (!userId) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const userSkillsDir = join(process.cwd(), 'data', 'skills', 'users', userId);
|
|
70
|
+
|
|
71
|
+
if (!existsSync(userSkillsDir)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const entries = readdirSync(userSkillsDir, { withFileTypes: true });
|
|
76
|
+
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (!entry.isDirectory()) continue;
|
|
79
|
+
|
|
80
|
+
const skillDirName = entry.name;
|
|
81
|
+
const skillPath = join(userSkillsDir, skillDirName);
|
|
82
|
+
|
|
83
|
+
// 验证技能文件存在
|
|
84
|
+
if (existsSync(join(skillPath, 'SKILL.md'))) {
|
|
85
|
+
// 读取 SKILL.json 获取显示名称
|
|
86
|
+
const skillJsonPath = join(skillPath, 'SKILL.json');
|
|
87
|
+
let displayName = skillDirName;
|
|
88
|
+
try {
|
|
89
|
+
if (existsSync(skillJsonPath)) {
|
|
90
|
+
const skillData = JSON.parse(readFileSync(skillJsonPath, 'utf-8'));
|
|
91
|
+
displayName = skillData.skillName || skillData.displayName || skillDirName;
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
|
|
95
|
+
// 同时用目录名和显示名称作为 key
|
|
96
|
+
skills.set(skillDirName, {
|
|
97
|
+
source: SkillSource.User,
|
|
98
|
+
path: skillPath,
|
|
99
|
+
userId,
|
|
100
|
+
});
|
|
101
|
+
if (displayName !== skillDirName) {
|
|
102
|
+
skills.set(displayName, {
|
|
103
|
+
source: SkillSource.User,
|
|
104
|
+
path: skillPath,
|
|
105
|
+
userId,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 获取市场已安装技能列表
|
|
114
|
+
*/
|
|
115
|
+
function loadMarketInstalledSkills(userId: string | undefined, skills: Map<string, SkillLocation>): void {
|
|
116
|
+
// 如果没有userId,跳过市场技能加载
|
|
117
|
+
if (!userId) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// 从数据库查询用户已安装的技能
|
|
123
|
+
const { getUserInstalledSkills, getMarketSkillById } = require('./market-db');
|
|
124
|
+
const installedSkillIds = getUserInstalledSkills(userId);
|
|
125
|
+
|
|
126
|
+
for (const skillId of installedSkillIds) {
|
|
127
|
+
const skill = getMarketSkillById(skillId);
|
|
128
|
+
if (!skill) {
|
|
129
|
+
console.warn(`[skill-loader] Market skill ${skillId} not found in database`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 市场技能解压到用户目录下,使用技能名称
|
|
134
|
+
const userSkillsDir = join(process.cwd(), 'data', 'users', userId, 'skills');
|
|
135
|
+
const skillPath = join(userSkillsDir, skill.name);
|
|
136
|
+
|
|
137
|
+
// 验证技能文件存在
|
|
138
|
+
if (existsSync(join(skillPath, 'SKILL.md'))) {
|
|
139
|
+
skills.set(skill.name, {
|
|
140
|
+
source: SkillSource.Market,
|
|
141
|
+
path: skillPath,
|
|
142
|
+
userId,
|
|
143
|
+
marketId: skillId,
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
console.warn(`[skill-loader] Market skill files not found: ${skillPath}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('[skill-loader] Failed to load market installed skills:', error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 获取用户可用的所有技能位置
|
|
156
|
+
*
|
|
157
|
+
* @param userId 用户ID(可选,如果不提供则只加载系统技能)
|
|
158
|
+
* @returns Map<技能名称, 技能位置>
|
|
159
|
+
*/
|
|
160
|
+
export function getUserAvailableSkills(userId?: string): Map<string, SkillLocation> {
|
|
161
|
+
const skills = new Map<string, SkillLocation>();
|
|
162
|
+
|
|
163
|
+
// 1. 加载系统内置技能(优先级最低,放在前面被覆盖)
|
|
164
|
+
loadSystemSkills(skills);
|
|
165
|
+
|
|
166
|
+
// 2. 加载用户私有技能(会覆盖同名系统技能)
|
|
167
|
+
loadUserSkills(userId, skills);
|
|
168
|
+
|
|
169
|
+
// 3. 加载市场已安装技能(优先级最高)
|
|
170
|
+
loadMarketInstalledSkills(userId, skills);
|
|
171
|
+
|
|
172
|
+
return skills;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 从路径加载技能内容
|
|
177
|
+
*
|
|
178
|
+
* @param skillPath 技能目录路径
|
|
179
|
+
* @returns SKILL.md 内容,如果不存在则返回 null
|
|
180
|
+
*/
|
|
181
|
+
export function loadSkillFromPath(skillPath: string): string | null {
|
|
182
|
+
const skillMdPath = join(skillPath, 'SKILL.md');
|
|
183
|
+
|
|
184
|
+
if (!existsSync(skillMdPath)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
return readFileSync(skillMdPath, 'utf-8');
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(`[skill-loader] Failed to load skill from ${skillPath}:`, error);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 根据技能名称和用户ID加载技能内容
|
|
198
|
+
*
|
|
199
|
+
* @param skillName 技能名称
|
|
200
|
+
* @param userId 用户ID(可选)
|
|
201
|
+
* @returns SKILL.md 内容,如果不存在则返回 null
|
|
202
|
+
*/
|
|
203
|
+
export function loadSkillContentByName(skillName: string, userId?: string): string | null {
|
|
204
|
+
const locations = getUserAvailableSkills(userId);
|
|
205
|
+
const location = locations.get(skillName);
|
|
206
|
+
|
|
207
|
+
if (!location) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return loadSkillFromPath(location.path);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 获取技能元数据 (SKILL.json)
|
|
216
|
+
*
|
|
217
|
+
* @param skillPath 技能目录路径
|
|
218
|
+
* @returns SKILL.json 内容,如果不存在则返回 null
|
|
219
|
+
*/
|
|
220
|
+
export function loadSkillMetaFromPath(skillPath: string): any | null {
|
|
221
|
+
const skillJsonPath = join(skillPath, 'SKILL.json');
|
|
222
|
+
|
|
223
|
+
if (!existsSync(skillJsonPath)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const content = readFileSync(skillJsonPath, 'utf-8');
|
|
229
|
+
return JSON.parse(content);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error(`[skill-loader] Failed to load skill meta from ${skillPath}:`, error);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill创建工具
|
|
3
|
+
*
|
|
4
|
+
* 提供 create_skill 工具,让 AI 可以通过对话创建通用Skill。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Type } from '@sinclair/typebox';
|
|
8
|
+
import type { ToolDefinition, AgentToolResult, ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
9
|
+
import { createSkill } from './skill-generator';
|
|
10
|
+
import type { CreateSkillRequest, SkillType } from './skill-types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Skill创建工具的参数 Schema
|
|
14
|
+
*/
|
|
15
|
+
const createSkillSchema = Type.Object({
|
|
16
|
+
name: Type.String({
|
|
17
|
+
description: 'Skill名称(小写字母、数字和短横线,如 "docker-ops")',
|
|
18
|
+
}),
|
|
19
|
+
displayName: Type.Optional(Type.String({
|
|
20
|
+
description: '显示名称(如 "Docker容器管理")',
|
|
21
|
+
})),
|
|
22
|
+
description: Type.String({
|
|
23
|
+
description: '自然语言描述skill的功能和用途',
|
|
24
|
+
}),
|
|
25
|
+
type: Type.Optional(Type.Union([
|
|
26
|
+
Type.Literal('documentation'),
|
|
27
|
+
Type.Literal('command'),
|
|
28
|
+
Type.Literal('workflow'),
|
|
29
|
+
Type.Literal('hybrid'),
|
|
30
|
+
], {
|
|
31
|
+
description: 'Skill类型(可选,会根据description自动检测):documentation=知识型, command=命令型, workflow=工作流型, hybrid=混合型',
|
|
32
|
+
})),
|
|
33
|
+
knowledge: Type.Optional(Type.String({
|
|
34
|
+
description: '额外的知识内容或AI指令(用于documentation类型)',
|
|
35
|
+
})),
|
|
36
|
+
commandPatterns: Type.Optional(Type.Array(Type.String({
|
|
37
|
+
description: '命令正则模式(如 "^kubectl\\\\s+")',
|
|
38
|
+
}))),
|
|
39
|
+
riskConfig: Type.Optional(Type.Object({
|
|
40
|
+
default: Type.Union([
|
|
41
|
+
Type.Literal('low'),
|
|
42
|
+
Type.Literal('medium'),
|
|
43
|
+
Type.Literal('high'),
|
|
44
|
+
Type.Literal('critical'),
|
|
45
|
+
], {
|
|
46
|
+
description: '默认风险级别',
|
|
47
|
+
}),
|
|
48
|
+
overrides: Type.Optional(Type.Record(Type.String(), Type.Union([
|
|
49
|
+
Type.Literal('low'),
|
|
50
|
+
Type.Literal('medium'),
|
|
51
|
+
Type.Literal('high'),
|
|
52
|
+
Type.Literal('critical'),
|
|
53
|
+
], {
|
|
54
|
+
description: '命令对应的风险级别覆盖',
|
|
55
|
+
}))),
|
|
56
|
+
})),
|
|
57
|
+
examples: Type.Optional(Type.Array(Type.Object({
|
|
58
|
+
command: Type.String({
|
|
59
|
+
description: '示例命令',
|
|
60
|
+
}),
|
|
61
|
+
description: Type.String({
|
|
62
|
+
description: '示例描述',
|
|
63
|
+
}),
|
|
64
|
+
riskLevel: Type.Optional(Type.Union([
|
|
65
|
+
Type.Literal('low'),
|
|
66
|
+
Type.Literal('medium'),
|
|
67
|
+
Type.Literal('high'),
|
|
68
|
+
Type.Literal('critical'),
|
|
69
|
+
], {
|
|
70
|
+
description: '风险级别',
|
|
71
|
+
})),
|
|
72
|
+
}))),
|
|
73
|
+
author: Type.Optional(Type.String({
|
|
74
|
+
description: '作者名称',
|
|
75
|
+
})),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Skill创建工具的详情类型
|
|
80
|
+
*/
|
|
81
|
+
interface CreateSkillDetails {
|
|
82
|
+
success: boolean;
|
|
83
|
+
skill?: {
|
|
84
|
+
name: string;
|
|
85
|
+
displayName: string;
|
|
86
|
+
type: SkillType;
|
|
87
|
+
path: string;
|
|
88
|
+
files: string[];
|
|
89
|
+
};
|
|
90
|
+
error?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 创建带用户上下文的 Skill 工具
|
|
95
|
+
*/
|
|
96
|
+
export function createSkillTool(userId: string | undefined): ToolDefinition<typeof createSkillSchema, CreateSkillDetails> {
|
|
97
|
+
return {
|
|
98
|
+
name: 'create_skill',
|
|
99
|
+
label: 'Create Skill',
|
|
100
|
+
description: `通过自然语言创建新的通用Skill。
|
|
101
|
+
|
|
102
|
+
Skill类型说明:
|
|
103
|
+
- documentation: 知识型skill,为AI提供领域知识和指令
|
|
104
|
+
- command: 命令型skill,定义命令模式和风险配置
|
|
105
|
+
- workflow: 工作流型skill,多步骤自动化操作
|
|
106
|
+
- hybrid: 混合型skill
|
|
107
|
+
|
|
108
|
+
创建的Skill会保存到用户私有目录。
|
|
109
|
+
|
|
110
|
+
示例用法:
|
|
111
|
+
- 创建Docker管理skill: name="docker-ops", description="管理Docker容器的skill"
|
|
112
|
+
- 创建K8s运维skill: name="k8s-ops", description="Kubernetes运维skill", type="command"
|
|
113
|
+
|
|
114
|
+
注意:此工具用于创建通用skill。如需从Swagger/OpenAPI生成API skill,请使用generate-api-system功能。`,
|
|
115
|
+
|
|
116
|
+
parameters: createSkillSchema,
|
|
117
|
+
|
|
118
|
+
async execute(
|
|
119
|
+
toolCallId: string,
|
|
120
|
+
params: any,
|
|
121
|
+
signal: AbortSignal | undefined,
|
|
122
|
+
onUpdate: ((result: AgentToolResult<CreateSkillDetails>) => void) | undefined,
|
|
123
|
+
ctx: ExtensionContext
|
|
124
|
+
): Promise<AgentToolResult<CreateSkillDetails>> {
|
|
125
|
+
try {
|
|
126
|
+
const request: CreateSkillRequest = {
|
|
127
|
+
name: params.name,
|
|
128
|
+
displayName: params.displayName || params.name,
|
|
129
|
+
description: params.description,
|
|
130
|
+
userId: userId || undefined,
|
|
131
|
+
type: params.type,
|
|
132
|
+
knowledge: params.knowledge,
|
|
133
|
+
commandPatterns: params.commandPatterns,
|
|
134
|
+
riskConfig: params.riskConfig,
|
|
135
|
+
examples: params.examples,
|
|
136
|
+
author: params.author,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = await createSkill(request);
|
|
140
|
+
|
|
141
|
+
if (!result.success) {
|
|
142
|
+
const errorResult: AgentToolResult<CreateSkillDetails> = {
|
|
143
|
+
content: [{ type: 'text', text: `❌ 创建Skill失败: ${result.error}` }],
|
|
144
|
+
details: { success: false, error: result.error },
|
|
145
|
+
};
|
|
146
|
+
onUpdate?.(errorResult);
|
|
147
|
+
return errorResult;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const successResult: AgentToolResult<CreateSkillDetails> = {
|
|
151
|
+
content: [{
|
|
152
|
+
type: 'text',
|
|
153
|
+
text: `✅ Skill已创建成功!
|
|
154
|
+
|
|
155
|
+
**名称:** ${result.skillName}
|
|
156
|
+
**类型:** ${result.type}
|
|
157
|
+
**路径:** ${result.skillPath}
|
|
158
|
+
**文件:** ${result.files.join(', ')}
|
|
159
|
+
|
|
160
|
+
Skill已保存到您的私有目录,可以在会话中通过 @${result.skillName} 激活使用。`,
|
|
161
|
+
}],
|
|
162
|
+
details: {
|
|
163
|
+
success: true,
|
|
164
|
+
skill: {
|
|
165
|
+
name: result.skillName,
|
|
166
|
+
displayName: params.displayName || params.name,
|
|
167
|
+
type: result.type,
|
|
168
|
+
path: result.skillPath,
|
|
169
|
+
files: result.files,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
onUpdate?.(successResult);
|
|
175
|
+
return successResult;
|
|
176
|
+
} catch (error: any) {
|
|
177
|
+
const errorResult: AgentToolResult<CreateSkillDetails> = {
|
|
178
|
+
content: [{ type: 'text', text: `❌ 创建Skill失败: ${error.message}` }],
|
|
179
|
+
details: { success: false, error: error.message },
|
|
180
|
+
};
|
|
181
|
+
onUpdate?.(errorResult);
|
|
182
|
+
return errorResult;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export default createSkillTool;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用Skill类型定义
|
|
3
|
+
*
|
|
4
|
+
* 定义了skill创建、管理所需的类型接口
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Skill类型枚举
|
|
9
|
+
*/
|
|
10
|
+
export type SkillType =
|
|
11
|
+
| 'documentation' // 知识型:提供AI指令和知识
|
|
12
|
+
| 'command' // 命令型:命令模式+风险配置
|
|
13
|
+
| 'workflow' // 工作流型:多步骤操作
|
|
14
|
+
| 'hybrid'; // 混合型
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Skill风险配置
|
|
18
|
+
*/
|
|
19
|
+
export interface SkillRiskConfig {
|
|
20
|
+
default: 'low' | 'medium' | 'high' | 'critical';
|
|
21
|
+
overrides?: Record<string, 'low' | 'medium' | 'high' | 'critical'>; // 命令 -> 风险级别
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 示例命令定义
|
|
26
|
+
*/
|
|
27
|
+
export interface SkillExample {
|
|
28
|
+
command: string;
|
|
29
|
+
description: string;
|
|
30
|
+
riskLevel?: 'low' | 'medium' | 'high' | 'critical';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Skill创建请求
|
|
35
|
+
*/
|
|
36
|
+
export interface CreateSkillRequest {
|
|
37
|
+
name: string; // skill名称 (小写, 短横线)
|
|
38
|
+
displayName: string; // 显示名称
|
|
39
|
+
description: string; // 自然语言描述skill功能
|
|
40
|
+
userId?: string; // 用户ID(可选,不提供则保存到系统目录)
|
|
41
|
+
type?: SkillType; // 类型(可选,自动检测)
|
|
42
|
+
author?: string;
|
|
43
|
+
|
|
44
|
+
// 知识内容(用于documentation类型)
|
|
45
|
+
knowledge?: string; // 额外的知识/指令
|
|
46
|
+
|
|
47
|
+
// 命令模式(用于command类型)
|
|
48
|
+
commandPatterns?: string[]; // 如 ["^kubectl\\s+", "^docker\\s+"]
|
|
49
|
+
|
|
50
|
+
// 风险配置
|
|
51
|
+
riskConfig?: SkillRiskConfig;
|
|
52
|
+
|
|
53
|
+
// 示例命令
|
|
54
|
+
examples?: SkillExample[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Skill创建结果
|
|
59
|
+
*/
|
|
60
|
+
export interface CreateSkillResult {
|
|
61
|
+
success: boolean;
|
|
62
|
+
skillName: string;
|
|
63
|
+
skillPath: string;
|
|
64
|
+
type: SkillType;
|
|
65
|
+
files: string[];
|
|
66
|
+
error?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Skill元数据(SKILL.json的结构)
|
|
71
|
+
*/
|
|
72
|
+
export interface SkillMetadata {
|
|
73
|
+
name: string;
|
|
74
|
+
displayName: string;
|
|
75
|
+
description: string;
|
|
76
|
+
version: string;
|
|
77
|
+
author: string;
|
|
78
|
+
type: SkillType;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
commandPatterns?: string[];
|
|
81
|
+
riskConfig?: SkillRiskConfig;
|
|
82
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills Directory Initialization
|
|
3
|
+
*
|
|
4
|
+
* Ensures that all required skills directories exist at runtime.
|
|
5
|
+
* This module is imported early to create directories before they're needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdirSync, existsSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Base directory for skills data
|
|
13
|
+
*/
|
|
14
|
+
export const SKILLS_BASE_DIR = join(process.cwd(), 'data', 'skills');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Subdirectories for different skill types
|
|
18
|
+
*/
|
|
19
|
+
export const SKILLS_DIRS = {
|
|
20
|
+
users: join(SKILLS_BASE_DIR, 'users'), // User private skills
|
|
21
|
+
market: join(SKILLS_BASE_DIR, 'market'), // Skills from marketplace
|
|
22
|
+
tmp: join(SKILLS_BASE_DIR, 'tmp'), // Temporary files for uploads/downloads
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize skills directories
|
|
27
|
+
* Creates all required directories if they don't exist
|
|
28
|
+
*
|
|
29
|
+
* This function is safe to call multiple times - it will not fail
|
|
30
|
+
* if directories already exist.
|
|
31
|
+
*/
|
|
32
|
+
export function initializeSkillsDirs(): void {
|
|
33
|
+
for (const [name, dir] of Object.entries(SKILLS_DIRS)) {
|
|
34
|
+
if (!existsSync(dir)) {
|
|
35
|
+
try {
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
console.log(`[skills-init] Created directory: ${name} -> ${dir}`);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(`[skills-init] Failed to create directory ${name}:`, error);
|
|
40
|
+
// Re-throw critical errors so the app can fail fast
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Also ensure base directory exists
|
|
47
|
+
if (!existsSync(SKILLS_BASE_DIR)) {
|
|
48
|
+
try {
|
|
49
|
+
mkdirSync(SKILLS_BASE_DIR, { recursive: true });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('[skills-init] Failed to create base directory:', error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Auto-initialize on module import
|
|
58
|
+
initializeSkillsDirs();
|