@uniqueli/openwork 0.2.4 → 0.3.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
CHANGED
|
@@ -42,12 +42,12 @@ Or configure them in-app via the settings panel.
|
|
|
42
42
|
|
|
43
43
|
## Supported Models
|
|
44
44
|
|
|
45
|
-
| Provider
|
|
46
|
-
|
|
|
47
|
-
| Anthropic
|
|
48
|
-
| OpenAI
|
|
49
|
-
| Google
|
|
50
|
-
| **Custom**
|
|
45
|
+
| Provider | Models |
|
|
46
|
+
| ---------- | ----------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| Anthropic | Claude Opus 4.5, Claude Sonnet 4.5, Claude Haiku 4.5, Claude Opus 4.1, Claude Sonnet 4 |
|
|
48
|
+
| OpenAI | GPT-5.2, GPT-5.1, o3, o3 Mini, o4 Mini, o1, GPT-4.1, GPT-4o |
|
|
49
|
+
| Google | Gemini 3 Pro Preview, Gemini 3 Flash Preview, Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini 2.5 Flash Lite |
|
|
50
|
+
| **Custom** | **Add unlimited custom providers!** |
|
|
51
51
|
|
|
52
52
|
## ✨ Multiple Custom API Providers
|
|
53
53
|
|
|
@@ -68,6 +68,7 @@ Or configure them in-app via the settings panel.
|
|
|
68
68
|
### Supported Custom APIs
|
|
69
69
|
|
|
70
70
|
Works with any OpenAI-compatible API:
|
|
71
|
+
|
|
71
72
|
- **Chinese AI Providers**: Moonshot AI (Kimi), Zhipu AI (GLM), DeepSeek, Baichuan, etc.
|
|
72
73
|
- **Self-hosted models**: vLLM, Text Generation WebUI, LocalAI, Ollama (with OpenAI compatibility)
|
|
73
74
|
- **Cloud services**: Azure OpenAI, AWS Bedrock (with proxy), Cloudflare AI
|
|
@@ -76,6 +77,7 @@ Works with any OpenAI-compatible API:
|
|
|
76
77
|
### Example Configurations
|
|
77
78
|
|
|
78
79
|
**Moonshot AI (Kimi)**
|
|
80
|
+
|
|
79
81
|
```
|
|
80
82
|
ID: moonshot
|
|
81
83
|
Display Name: Moonshot AI
|
|
@@ -84,6 +86,7 @@ Model Name: kimi-k2-turbo-preview
|
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
**Zhipu AI (GLM)**
|
|
89
|
+
|
|
87
90
|
```
|
|
88
91
|
ID: zhipu
|
|
89
92
|
Display Name: Zhipu AI
|
|
@@ -92,13 +95,16 @@ Model Name: glm-4-plus
|
|
|
92
95
|
```
|
|
93
96
|
|
|
94
97
|
**DeepSeek**
|
|
98
|
+
|
|
95
99
|
```
|
|
96
100
|
ID: deepseek
|
|
97
101
|
Display Name: DeepSeek
|
|
98
102
|
Base URL: https://api.deepseek.com/v1
|
|
99
103
|
Model Name: deepseek-chat
|
|
100
104
|
```
|
|
105
|
+
|
|
101
106
|
Configure via Settings UI or by setting environment variables:
|
|
107
|
+
|
|
102
108
|
```bash
|
|
103
109
|
CUSTOM_BASE_URL=https://api.example.com/v1
|
|
104
110
|
CUSTOM_API_KEY=your-api-key
|
|
@@ -107,25 +113,41 @@ CUSTOM_MODEL=your-model-name # optional
|
|
|
107
113
|
|
|
108
114
|
## Changelog
|
|
109
115
|
|
|
116
|
+
### v0.3.0 (2026-02-09)
|
|
117
|
+
|
|
118
|
+
- 🚀 **Skills System Major Upgrade**: 技能系统重大升级
|
|
119
|
+
- ⚡ **Performance**: 将所有文件操作转换为异步I/O,解决UI阻塞问题
|
|
120
|
+
- 🔒 **Security**: 新增完整的输入验证系统,防止注入攻击和资源耗尽
|
|
121
|
+
- 💾 **Memory**: 实现LRU缓存机制(最大100个技能),防止内存泄漏
|
|
122
|
+
- 🎯 **Skill Combination**: 新增技能组合功能,支持跨学科专业知识整合
|
|
123
|
+
- 📝 **Version Management**: 为所有技能添加语义化版本控制
|
|
124
|
+
- 🛠️ **Error Handling**: 增强错误处理系统,提供中文错误消息和恢复建议
|
|
125
|
+
- 🐛 **UI Fix**: 修复创建技能对话框在From Template模式下窗口过大、关闭按钮不可见的问题
|
|
126
|
+
|
|
110
127
|
### v0.2.4 (2026-02-06)
|
|
128
|
+
|
|
111
129
|
- ✨ **聊天建议卡片**: 新对话空状态下显示可点击的建议卡片(文件整理、内容创作、文档处理),点击即可快速开始对话
|
|
112
130
|
|
|
113
131
|
### v0.2.3 (2026-02-04)
|
|
132
|
+
|
|
114
133
|
- ✨ **Skills System**: 新增技能配置系统,支持 12 个内置技能和自定义技能创建
|
|
115
134
|
- 🐛 修复 `deleteUserSkill` 误删所有技能记录的严重 Bug
|
|
116
135
|
- 🐛 修复 Switch 组件、创建技能对话框、技能过滤等多个问题
|
|
117
136
|
- ⚡ 技能初始化改为懒加载,提升启动性能
|
|
118
137
|
|
|
119
138
|
### v0.2.2 (2026-xx-xx)
|
|
139
|
+
|
|
120
140
|
- ✨ 支持多个自定义 API 配置
|
|
121
141
|
- 🔧 动态 Provider 系统
|
|
122
142
|
|
|
123
143
|
### v0.2.1 (2026-01-19)
|
|
144
|
+
|
|
124
145
|
- 🐛 **Critical Fix**: Fixed "Missing credentials" error for users without OpenAI API key
|
|
125
146
|
- 🔧 Custom API now works correctly even when OPENAI_API_KEY is not set in environment
|
|
126
147
|
- 📝 Improved logging for debugging custom API configurations
|
|
127
148
|
|
|
128
149
|
### v0.2.0 (2026-01-18)
|
|
150
|
+
|
|
129
151
|
- ✨ **Multiple Custom API Providers**: Add unlimited custom providers via UI
|
|
130
152
|
- 🎨 **Improved UX**: One-click provider addition with "+ 添加Provider" button
|
|
131
153
|
- 🔧 **Better Configuration**: Each provider has its own name, base URL, API key, and model
|
|
@@ -133,6 +155,7 @@ CUSTOM_MODEL=your-model-name # optional
|
|
|
133
155
|
- 📝 **Simplified Settings**: Cleaner settings dialog focused on standard providers
|
|
134
156
|
|
|
135
157
|
### v0.1.0 (2026-01-15)
|
|
158
|
+
|
|
136
159
|
- 🎉 Initial release with basic custom API support
|
|
137
160
|
- 🔑 Single custom API configuration via Settings
|
|
138
161
|
|
package/out/main/index.js
CHANGED
|
@@ -1301,6 +1301,7 @@ const BUILTIN_SKILLS = [
|
|
|
1301
1301
|
name: "SQL Expert",
|
|
1302
1302
|
description: "Specialized in SQL query writing, database schema analysis, and query optimization",
|
|
1303
1303
|
category: "data",
|
|
1304
|
+
version: "1.0.0",
|
|
1304
1305
|
prompt: `You are a SQL and database expert. Your expertise includes:
|
|
1305
1306
|
|
|
1306
1307
|
## Core Capabilities
|
|
@@ -1345,6 +1346,7 @@ When writing SQL:
|
|
|
1345
1346
|
name: "Code Reviewer",
|
|
1346
1347
|
description: "Expert in code review, best practices, and identifying potential issues",
|
|
1347
1348
|
category: "coding",
|
|
1349
|
+
version: "1.0.0",
|
|
1348
1350
|
prompt: `You are an expert code reviewer. Your role is to analyze code for:
|
|
1349
1351
|
|
|
1350
1352
|
## Review Focus Areas
|
|
@@ -1408,6 +1410,7 @@ Be specific and actionable. Include code examples for fixes when helpful.`,
|
|
|
1408
1410
|
name: "Technical Writer",
|
|
1409
1411
|
description: "Specializes in writing clear, comprehensive technical documentation",
|
|
1410
1412
|
category: "creative",
|
|
1413
|
+
version: "1.0.0",
|
|
1411
1414
|
prompt: `You are a technical documentation expert. Your expertise includes:
|
|
1412
1415
|
|
|
1413
1416
|
## Documentation Types
|
|
@@ -1478,6 +1481,7 @@ Use markdown formatting with:
|
|
|
1478
1481
|
name: "Debugging Expert",
|
|
1479
1482
|
description: "Specializes in systematic debugging and problem-solving",
|
|
1480
1483
|
category: "system",
|
|
1484
|
+
version: "1.0.0",
|
|
1481
1485
|
prompt: `You are a debugging expert. Follow this systematic approach:
|
|
1482
1486
|
|
|
1483
1487
|
## Debugging Methodology
|
|
@@ -1550,6 +1554,7 @@ Focus on finding the root cause, not just treating symptoms.`,
|
|
|
1550
1554
|
name: "Test Engineer",
|
|
1551
1555
|
description: "Specializes in writing comprehensive tests and test strategies",
|
|
1552
1556
|
category: "coding",
|
|
1557
|
+
version: "1.0.0",
|
|
1553
1558
|
prompt: `You are a test engineering expert. Your expertise includes:
|
|
1554
1559
|
|
|
1555
1560
|
## Testing Philosophy
|
|
@@ -1650,6 +1655,7 @@ When suggesting tests:
|
|
|
1650
1655
|
name: "Refactoring Expert",
|
|
1651
1656
|
description: "Specializes in code refactoring, improving code quality, and reducing technical debt",
|
|
1652
1657
|
category: "coding",
|
|
1658
|
+
version: "1.0.0",
|
|
1653
1659
|
prompt: `You are a refactoring expert. Your expertise includes:
|
|
1654
1660
|
|
|
1655
1661
|
## Refactoring Principles
|
|
@@ -1734,6 +1740,7 @@ When refactoring:
|
|
|
1734
1740
|
name: "API Designer",
|
|
1735
1741
|
description: "Expert in RESTful API design, documentation, and best practices",
|
|
1736
1742
|
category: "coding",
|
|
1743
|
+
version: "1.0.0",
|
|
1737
1744
|
prompt: `You are an API design expert. Your expertise includes:
|
|
1738
1745
|
|
|
1739
1746
|
## RESTful API Design
|
|
@@ -1848,6 +1855,7 @@ When designing APIs:
|
|
|
1848
1855
|
name: "Git Expert",
|
|
1849
1856
|
description: "Specializes in Git workflows, branching strategies, and version control best practices",
|
|
1850
1857
|
category: "system",
|
|
1858
|
+
version: "1.0.0",
|
|
1851
1859
|
prompt: `You are a Git and version control expert. Your expertise includes:
|
|
1852
1860
|
|
|
1853
1861
|
## Git Fundamentals
|
|
@@ -2001,6 +2009,7 @@ When helping with Git:
|
|
|
2001
2009
|
name: "Performance Optimizer",
|
|
2002
2010
|
description: "Expert in code optimization, profiling, and performance improvements",
|
|
2003
2011
|
category: "coding",
|
|
2012
|
+
version: "1.0.0",
|
|
2004
2013
|
prompt: `You are a performance optimization expert. Your expertise includes:
|
|
2005
2014
|
|
|
2006
2015
|
## Performance Optimization Strategy
|
|
@@ -2139,6 +2148,7 @@ When optimizing:
|
|
|
2139
2148
|
name: "Security Auditor",
|
|
2140
2149
|
description: "Expert in identifying security vulnerabilities and implementing secure coding practices",
|
|
2141
2150
|
category: "analysis",
|
|
2151
|
+
version: "1.0.0",
|
|
2142
2152
|
prompt: `You are a security expert. Your expertise includes identifying vulnerabilities and implementing secure coding practices.
|
|
2143
2153
|
|
|
2144
2154
|
## OWASP Top 10
|
|
@@ -2294,6 +2304,7 @@ When auditing code:
|
|
|
2294
2304
|
name: "Python Expert",
|
|
2295
2305
|
description: "Specialized in Python programming, best practices, and ecosystem tools",
|
|
2296
2306
|
category: "coding",
|
|
2307
|
+
version: "1.0.0",
|
|
2297
2308
|
prompt: `You are a Python expert. Your expertise includes:
|
|
2298
2309
|
|
|
2299
2310
|
## Python Best Practices
|
|
@@ -2473,6 +2484,7 @@ When writing Python:
|
|
|
2473
2484
|
name: "JavaScript Expert",
|
|
2474
2485
|
description: "Expert in modern JavaScript (ES6+), TypeScript, and browser APIs",
|
|
2475
2486
|
category: "coding",
|
|
2487
|
+
version: "1.0.0",
|
|
2476
2488
|
prompt: `You are a JavaScript/TypeScript expert. Your expertise includes:
|
|
2477
2489
|
|
|
2478
2490
|
## Modern JavaScript (ES6+)
|
|
@@ -2665,6 +2677,15 @@ function ensureSkillsDir() {
|
|
|
2665
2677
|
fs.mkdirSync(SKILLS_FILE_DIR, { recursive: true });
|
|
2666
2678
|
}
|
|
2667
2679
|
}
|
|
2680
|
+
async function ensureSkillsDirAsync() {
|
|
2681
|
+
try {
|
|
2682
|
+
await fs$1.mkdir(SKILLS_FILE_DIR, { recursive: true });
|
|
2683
|
+
} catch (error) {
|
|
2684
|
+
if (error.code !== "EEXIST") {
|
|
2685
|
+
throw error;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2668
2689
|
function skillToSkillMarkdown(skill) {
|
|
2669
2690
|
return `---
|
|
2670
2691
|
name: ${skill.name}
|
|
@@ -2681,45 +2702,52 @@ ${skill.description}
|
|
|
2681
2702
|
${skill.prompt}
|
|
2682
2703
|
`;
|
|
2683
2704
|
}
|
|
2684
|
-
function saveSkillFile(skill) {
|
|
2685
|
-
|
|
2705
|
+
async function saveSkillFile(skill) {
|
|
2706
|
+
await ensureSkillsDirAsync();
|
|
2686
2707
|
const skillDir = path.join(SKILLS_FILE_DIR, skill.id);
|
|
2687
|
-
|
|
2688
|
-
fs.
|
|
2708
|
+
try {
|
|
2709
|
+
await fs$1.mkdir(skillDir, { recursive: true });
|
|
2710
|
+
} catch (error) {
|
|
2711
|
+
if (error.code !== "EEXIST") {
|
|
2712
|
+
throw error;
|
|
2713
|
+
}
|
|
2689
2714
|
}
|
|
2690
2715
|
const skillFilePath = path.join(skillDir, "SKILL.md");
|
|
2691
2716
|
const content = skillToSkillMarkdown(skill);
|
|
2692
|
-
fs.
|
|
2717
|
+
await fs$1.writeFile(skillFilePath, content, "utf-8");
|
|
2693
2718
|
console.log(`[SkillFileManager] Saved skill file: ${skillFilePath}`);
|
|
2694
2719
|
}
|
|
2695
|
-
function deleteSkillFile(skillId) {
|
|
2720
|
+
async function deleteSkillFile(skillId) {
|
|
2696
2721
|
const skillDir = path.join(SKILLS_FILE_DIR, skillId);
|
|
2697
2722
|
const skillFilePath = path.join(skillDir, "SKILL.md");
|
|
2698
|
-
|
|
2699
|
-
fs.
|
|
2723
|
+
try {
|
|
2724
|
+
await fs$1.unlink(skillFilePath);
|
|
2700
2725
|
console.log(`[SkillFileManager] Deleted skill file: ${skillFilePath}`);
|
|
2726
|
+
} catch (error) {
|
|
2727
|
+
if (error.code !== "ENOENT") {
|
|
2728
|
+
throw error;
|
|
2729
|
+
}
|
|
2701
2730
|
}
|
|
2702
2731
|
}
|
|
2703
2732
|
function getSkillsFileDir() {
|
|
2704
2733
|
ensureSkillsDir();
|
|
2705
2734
|
return SKILLS_FILE_DIR;
|
|
2706
2735
|
}
|
|
2707
|
-
function initializeBuiltinSkills() {
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
saveSkillFile(skill);
|
|
2711
|
-
}
|
|
2736
|
+
async function initializeBuiltinSkills() {
|
|
2737
|
+
await ensureSkillsDirAsync();
|
|
2738
|
+
await Promise.all(BUILTIN_SKILLS.map((skill) => saveSkillFile(skill)));
|
|
2712
2739
|
console.log(`[SkillFileManager] Initialized ${BUILTIN_SKILLS.length} built-in skills`);
|
|
2713
2740
|
}
|
|
2714
|
-
function syncEnabledSkills(enabledSkillIds) {
|
|
2715
|
-
|
|
2716
|
-
|
|
2741
|
+
async function syncEnabledSkills(enabledSkillIds) {
|
|
2742
|
+
await ensureSkillsDirAsync();
|
|
2743
|
+
const syncPromises = BUILTIN_SKILLS.map(async (skill) => {
|
|
2717
2744
|
if (enabledSkillIds.includes(skill.id)) {
|
|
2718
|
-
saveSkillFile(skill);
|
|
2745
|
+
await saveSkillFile(skill);
|
|
2719
2746
|
} else {
|
|
2720
|
-
deleteSkillFile(skill.id);
|
|
2747
|
+
await deleteSkillFile(skill.id);
|
|
2721
2748
|
}
|
|
2722
|
-
}
|
|
2749
|
+
});
|
|
2750
|
+
await Promise.all(syncPromises);
|
|
2723
2751
|
console.log(`[SkillFileManager] Synced ${enabledSkillIds.length} enabled skills`);
|
|
2724
2752
|
}
|
|
2725
2753
|
const BASE_SYSTEM_PROMPT = `You are an AI assistant that helps users with various tasks including coding, research, and analysis.
|
|
@@ -3527,8 +3555,18 @@ class SkillLoader {
|
|
|
3527
3555
|
builtinSkills;
|
|
3528
3556
|
userSkills;
|
|
3529
3557
|
cache = /* @__PURE__ */ new Map();
|
|
3558
|
+
MAX_CACHE_SIZE = 100;
|
|
3559
|
+
// Maximum number of cached skills
|
|
3560
|
+
cacheHits = 0;
|
|
3561
|
+
cacheMisses = 0;
|
|
3530
3562
|
constructor() {
|
|
3531
|
-
this.builtinSkills = new Map(
|
|
3563
|
+
this.builtinSkills = new Map(
|
|
3564
|
+
BUILTIN_SKILLS.map((s) => [
|
|
3565
|
+
s.id,
|
|
3566
|
+
// Ensure version field exists (default to "1.0.0" for compatibility)
|
|
3567
|
+
{ ...s, version: s.version || "1.0.0" }
|
|
3568
|
+
])
|
|
3569
|
+
);
|
|
3532
3570
|
this.userSkills = /* @__PURE__ */ new Map();
|
|
3533
3571
|
this.loadUserSkillsFromStorage();
|
|
3534
3572
|
}
|
|
@@ -3547,6 +3585,8 @@ class SkillLoader {
|
|
|
3547
3585
|
subSkills: skillData.subSkills,
|
|
3548
3586
|
enabled: skillData.enabled,
|
|
3549
3587
|
isBuiltin: skillData.isBuiltin,
|
|
3588
|
+
version: skillData.version || "1.0.0",
|
|
3589
|
+
// Default version for compatibility
|
|
3550
3590
|
createdAt: new Date(skillData.createdAt),
|
|
3551
3591
|
updatedAt: new Date(skillData.updatedAt)
|
|
3552
3592
|
};
|
|
@@ -3575,21 +3615,71 @@ class SkillLoader {
|
|
|
3575
3615
|
* Get a specific skill by ID
|
|
3576
3616
|
*/
|
|
3577
3617
|
getSkill(id) {
|
|
3578
|
-
|
|
3579
|
-
|
|
3618
|
+
const cachedEntry = this.cache.get(id);
|
|
3619
|
+
if (cachedEntry) {
|
|
3620
|
+
this.cacheHits++;
|
|
3621
|
+
cachedEntry.lastAccessed = Date.now();
|
|
3622
|
+
return cachedEntry.skill;
|
|
3580
3623
|
}
|
|
3624
|
+
this.cacheMisses++;
|
|
3581
3625
|
if (this.builtinSkills.has(id)) {
|
|
3582
3626
|
const skill = this.builtinSkills.get(id);
|
|
3583
|
-
this.
|
|
3627
|
+
this.addToCache(id, skill);
|
|
3584
3628
|
return skill;
|
|
3585
3629
|
}
|
|
3586
3630
|
if (this.userSkills.has(id)) {
|
|
3587
3631
|
const skill = this.userSkills.get(id);
|
|
3588
|
-
this.
|
|
3632
|
+
this.addToCache(id, skill);
|
|
3589
3633
|
return skill;
|
|
3590
3634
|
}
|
|
3591
3635
|
return void 0;
|
|
3592
3636
|
}
|
|
3637
|
+
/**
|
|
3638
|
+
* Add skill to cache with LRU eviction
|
|
3639
|
+
*/
|
|
3640
|
+
addToCache(id, skill) {
|
|
3641
|
+
if (this.cache.size >= this.MAX_CACHE_SIZE && !this.cache.has(id)) {
|
|
3642
|
+
let lruId = null;
|
|
3643
|
+
let lruTime = Infinity;
|
|
3644
|
+
for (const [cacheId, entry] of this.cache.entries()) {
|
|
3645
|
+
if (entry.lastAccessed < lruTime) {
|
|
3646
|
+
lruTime = entry.lastAccessed;
|
|
3647
|
+
lruId = cacheId;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
if (lruId) {
|
|
3651
|
+
this.cache.delete(lruId);
|
|
3652
|
+
console.log(`[SkillLoader] Evicted LRU skill from cache: ${lruId}`);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
this.cache.set(id, {
|
|
3656
|
+
skill,
|
|
3657
|
+
lastAccessed: Date.now()
|
|
3658
|
+
});
|
|
3659
|
+
}
|
|
3660
|
+
/**
|
|
3661
|
+
* Clear the cache
|
|
3662
|
+
*/
|
|
3663
|
+
clearCache() {
|
|
3664
|
+
this.cache.clear();
|
|
3665
|
+
this.cacheHits = 0;
|
|
3666
|
+
this.cacheMisses = 0;
|
|
3667
|
+
console.log("[SkillLoader] Cache cleared");
|
|
3668
|
+
}
|
|
3669
|
+
/**
|
|
3670
|
+
* Get cache statistics
|
|
3671
|
+
*/
|
|
3672
|
+
getCacheStats() {
|
|
3673
|
+
const total = this.cacheHits + this.cacheMisses;
|
|
3674
|
+
const hitRate = total > 0 ? this.cacheHits / total * 100 : 0;
|
|
3675
|
+
return {
|
|
3676
|
+
size: this.cache.size,
|
|
3677
|
+
maxSize: this.MAX_CACHE_SIZE,
|
|
3678
|
+
hits: this.cacheHits,
|
|
3679
|
+
misses: this.cacheMisses,
|
|
3680
|
+
hitRate: Math.round(hitRate * 100) / 100
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3593
3683
|
/**
|
|
3594
3684
|
* Get enabled skills
|
|
3595
3685
|
*/
|
|
@@ -3653,7 +3743,7 @@ class SkillLoader {
|
|
|
3653
3743
|
* Refresh user skills from storage
|
|
3654
3744
|
*/
|
|
3655
3745
|
refresh() {
|
|
3656
|
-
this.
|
|
3746
|
+
this.clearCache();
|
|
3657
3747
|
this.userSkills.clear();
|
|
3658
3748
|
this.loadUserSkillsFromStorage();
|
|
3659
3749
|
}
|
|
@@ -3665,7 +3755,7 @@ function getSkillLoader() {
|
|
|
3665
3755
|
}
|
|
3666
3756
|
return globalSkillLoader;
|
|
3667
3757
|
}
|
|
3668
|
-
function createUserSkill(name, description, category, prompt, subSkills) {
|
|
3758
|
+
function createUserSkill(name, description, category, prompt, subSkills, version) {
|
|
3669
3759
|
return {
|
|
3670
3760
|
id: `user-${uuid.v4()}`,
|
|
3671
3761
|
name,
|
|
@@ -3675,6 +3765,8 @@ function createUserSkill(name, description, category, prompt, subSkills) {
|
|
|
3675
3765
|
subSkills,
|
|
3676
3766
|
enabled: false,
|
|
3677
3767
|
isBuiltin: false,
|
|
3768
|
+
version: version || "1.0.0",
|
|
3769
|
+
// Default to version 1.0.0
|
|
3678
3770
|
createdAt: /* @__PURE__ */ new Date(),
|
|
3679
3771
|
updatedAt: /* @__PURE__ */ new Date()
|
|
3680
3772
|
};
|
|
@@ -3689,6 +3781,8 @@ function saveUserSkill(skill) {
|
|
|
3689
3781
|
subSkills: skill.subSkills,
|
|
3690
3782
|
enabled: skill.enabled,
|
|
3691
3783
|
isBuiltin: skill.isBuiltin,
|
|
3784
|
+
version: skill.version,
|
|
3785
|
+
// Include version in storage
|
|
3692
3786
|
createdAt: skill.createdAt.toISOString(),
|
|
3693
3787
|
updatedAt: skill.updatedAt.toISOString()
|
|
3694
3788
|
};
|
|
@@ -3708,6 +3802,348 @@ function deleteUserSkill(skillId) {
|
|
|
3708
3802
|
saveUserSkills(filtered);
|
|
3709
3803
|
getSkillLoader().refresh();
|
|
3710
3804
|
}
|
|
3805
|
+
const SKILL_LIMITS = {
|
|
3806
|
+
NAME_MAX_LENGTH: 100,
|
|
3807
|
+
DESCRIPTION_MAX_LENGTH: 500,
|
|
3808
|
+
PROMPT_MAX_LENGTH: 5e4,
|
|
3809
|
+
// 50k characters ~ 15-20k tokens
|
|
3810
|
+
CATEGORY_MAX_LENGTH: 50,
|
|
3811
|
+
SUBSKILLS_MAX_COUNT: 20
|
|
3812
|
+
};
|
|
3813
|
+
const SKILL_MIN_LENGTHS = {
|
|
3814
|
+
NAME_MIN_LENGTH: 2,
|
|
3815
|
+
DESCRIPTION_MIN_LENGTH: 10,
|
|
3816
|
+
PROMPT_MIN_LENGTH: 50
|
|
3817
|
+
};
|
|
3818
|
+
const VALID_CATEGORIES = [
|
|
3819
|
+
"coding",
|
|
3820
|
+
"analysis",
|
|
3821
|
+
"creative",
|
|
3822
|
+
"data",
|
|
3823
|
+
"system",
|
|
3824
|
+
"custom"
|
|
3825
|
+
];
|
|
3826
|
+
function validateSkillName(name) {
|
|
3827
|
+
if (!name || typeof name !== "string") {
|
|
3828
|
+
return {
|
|
3829
|
+
valid: false,
|
|
3830
|
+
error: "Name is required",
|
|
3831
|
+
field: "name"
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3834
|
+
const trimmed = name.trim();
|
|
3835
|
+
if (trimmed.length < SKILL_MIN_LENGTHS.NAME_MIN_LENGTH) {
|
|
3836
|
+
return {
|
|
3837
|
+
valid: false,
|
|
3838
|
+
error: `Name must be at least ${SKILL_MIN_LENGTHS.NAME_MIN_LENGTH} characters`,
|
|
3839
|
+
field: "name"
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
if (trimmed.length > SKILL_LIMITS.NAME_MAX_LENGTH) {
|
|
3843
|
+
return {
|
|
3844
|
+
valid: false,
|
|
3845
|
+
error: `Name must not exceed ${SKILL_LIMITS.NAME_MAX_LENGTH} characters`,
|
|
3846
|
+
field: "name"
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
if (/[<>{}\\]/.test(trimmed)) {
|
|
3850
|
+
return {
|
|
3851
|
+
valid: false,
|
|
3852
|
+
error: "Name contains invalid characters",
|
|
3853
|
+
field: "name"
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3856
|
+
return { valid: true };
|
|
3857
|
+
}
|
|
3858
|
+
function validateSkillDescription(description) {
|
|
3859
|
+
if (!description || typeof description !== "string") {
|
|
3860
|
+
return {
|
|
3861
|
+
valid: false,
|
|
3862
|
+
error: "Description is required",
|
|
3863
|
+
field: "description"
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
const trimmed = description.trim();
|
|
3867
|
+
if (trimmed.length < SKILL_MIN_LENGTHS.DESCRIPTION_MIN_LENGTH) {
|
|
3868
|
+
return {
|
|
3869
|
+
valid: false,
|
|
3870
|
+
error: `Description must be at least ${SKILL_MIN_LENGTHS.DESCRIPTION_MIN_LENGTH} characters`,
|
|
3871
|
+
field: "description"
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3874
|
+
if (trimmed.length > SKILL_LIMITS.DESCRIPTION_MAX_LENGTH) {
|
|
3875
|
+
return {
|
|
3876
|
+
valid: false,
|
|
3877
|
+
error: `Description must not exceed ${SKILL_LIMITS.DESCRIPTION_MAX_LENGTH} characters`,
|
|
3878
|
+
field: "description"
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
return { valid: true };
|
|
3882
|
+
}
|
|
3883
|
+
function validateSkillPrompt(prompt) {
|
|
3884
|
+
if (!prompt || typeof prompt !== "string") {
|
|
3885
|
+
return {
|
|
3886
|
+
valid: false,
|
|
3887
|
+
error: "Prompt is required",
|
|
3888
|
+
field: "prompt"
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
const trimmed = prompt.trim();
|
|
3892
|
+
if (trimmed.length < SKILL_MIN_LENGTHS.PROMPT_MIN_LENGTH) {
|
|
3893
|
+
return {
|
|
3894
|
+
valid: false,
|
|
3895
|
+
error: `Prompt must be at least ${SKILL_MIN_LENGTHS.PROMPT_MIN_LENGTH} characters`,
|
|
3896
|
+
field: "prompt"
|
|
3897
|
+
};
|
|
3898
|
+
}
|
|
3899
|
+
if (trimmed.length > SKILL_LIMITS.PROMPT_MAX_LENGTH) {
|
|
3900
|
+
return {
|
|
3901
|
+
valid: false,
|
|
3902
|
+
error: `Prompt must not exceed ${SKILL_LIMITS.PROMPT_MAX_LENGTH} characters (currently ${trimmed.length})`,
|
|
3903
|
+
field: "prompt"
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
const dangerousPatterns = [
|
|
3907
|
+
/<script[^>]*>.*?<\/script>/gi,
|
|
3908
|
+
// Script tags
|
|
3909
|
+
/javascript:/gi,
|
|
3910
|
+
// JavaScript protocol
|
|
3911
|
+
/on\w+\s*=/gi,
|
|
3912
|
+
// Event handlers (onclick, onload, etc.)
|
|
3913
|
+
/<iframe[^>]*>/gi,
|
|
3914
|
+
// Iframes
|
|
3915
|
+
/<embed[^>]*>/gi,
|
|
3916
|
+
// Embed tags
|
|
3917
|
+
/<object[^>]*>/gi
|
|
3918
|
+
// Object tags
|
|
3919
|
+
];
|
|
3920
|
+
for (const pattern of dangerousPatterns) {
|
|
3921
|
+
if (pattern.test(trimmed)) {
|
|
3922
|
+
return {
|
|
3923
|
+
valid: false,
|
|
3924
|
+
error: "Prompt contains potentially dangerous content",
|
|
3925
|
+
field: "prompt"
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
const repeatedChars = /(.)\1{100,}/g;
|
|
3930
|
+
if (repeatedChars.test(trimmed)) {
|
|
3931
|
+
return {
|
|
3932
|
+
valid: false,
|
|
3933
|
+
error: "Prompt contains excessive repetition",
|
|
3934
|
+
field: "prompt"
|
|
3935
|
+
};
|
|
3936
|
+
}
|
|
3937
|
+
return { valid: true };
|
|
3938
|
+
}
|
|
3939
|
+
function validateSkillCategory(category) {
|
|
3940
|
+
if (!category || typeof category !== "string") {
|
|
3941
|
+
return {
|
|
3942
|
+
valid: false,
|
|
3943
|
+
error: "Category is required",
|
|
3944
|
+
field: "category"
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3947
|
+
const trimmed = category.trim().toLowerCase();
|
|
3948
|
+
if (trimmed.length > SKILL_LIMITS.CATEGORY_MAX_LENGTH) {
|
|
3949
|
+
return {
|
|
3950
|
+
valid: false,
|
|
3951
|
+
error: `Category must not exceed ${SKILL_LIMITS.CATEGORY_MAX_LENGTH} characters`,
|
|
3952
|
+
field: "category"
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
if (!VALID_CATEGORIES.includes(trimmed)) {
|
|
3956
|
+
return {
|
|
3957
|
+
valid: false,
|
|
3958
|
+
error: `Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`,
|
|
3959
|
+
field: "category"
|
|
3960
|
+
};
|
|
3961
|
+
}
|
|
3962
|
+
return { valid: true };
|
|
3963
|
+
}
|
|
3964
|
+
function validateSubSkills(subSkills) {
|
|
3965
|
+
if (!subSkills || subSkills.length === 0) {
|
|
3966
|
+
return { valid: true };
|
|
3967
|
+
}
|
|
3968
|
+
if (!Array.isArray(subSkills)) {
|
|
3969
|
+
return {
|
|
3970
|
+
valid: false,
|
|
3971
|
+
error: "Sub-skills must be an array",
|
|
3972
|
+
field: "subSkills"
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
if (subSkills.length > SKILL_LIMITS.SUBSKILLS_MAX_COUNT) {
|
|
3976
|
+
return {
|
|
3977
|
+
valid: false,
|
|
3978
|
+
error: `Cannot have more than ${SKILL_LIMITS.SUBSKILLS_MAX_COUNT} sub-skills`,
|
|
3979
|
+
field: "subSkills"
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
for (let i = 0; i < subSkills.length; i++) {
|
|
3983
|
+
const subSkill = subSkills[i];
|
|
3984
|
+
if (typeof subSkill !== "string" || subSkill.trim().length === 0) {
|
|
3985
|
+
return {
|
|
3986
|
+
valid: false,
|
|
3987
|
+
error: `Sub-skill at index ${i} must be a non-empty string`,
|
|
3988
|
+
field: "subSkills"
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
return { valid: true };
|
|
3993
|
+
}
|
|
3994
|
+
function validateSkillData(data) {
|
|
3995
|
+
const nameResult = validateSkillName(data.name);
|
|
3996
|
+
if (!nameResult.valid) {
|
|
3997
|
+
return nameResult;
|
|
3998
|
+
}
|
|
3999
|
+
const descriptionResult = validateSkillDescription(data.description);
|
|
4000
|
+
if (!descriptionResult.valid) {
|
|
4001
|
+
return descriptionResult;
|
|
4002
|
+
}
|
|
4003
|
+
const categoryResult = validateSkillCategory(data.category);
|
|
4004
|
+
if (!categoryResult.valid) {
|
|
4005
|
+
return categoryResult;
|
|
4006
|
+
}
|
|
4007
|
+
const promptResult = validateSkillPrompt(data.prompt);
|
|
4008
|
+
if (!promptResult.valid) {
|
|
4009
|
+
return promptResult;
|
|
4010
|
+
}
|
|
4011
|
+
const subSkillsResult = validateSubSkills(data.subSkills);
|
|
4012
|
+
if (!subSkillsResult.valid) {
|
|
4013
|
+
return subSkillsResult;
|
|
4014
|
+
}
|
|
4015
|
+
return { valid: true };
|
|
4016
|
+
}
|
|
4017
|
+
function sanitizePrompt(prompt) {
|
|
4018
|
+
let sanitized = prompt;
|
|
4019
|
+
sanitized = sanitized.replace(/\0/g, "");
|
|
4020
|
+
sanitized = sanitized.replace(/\s+/g, " ").trim();
|
|
4021
|
+
return sanitized;
|
|
4022
|
+
}
|
|
4023
|
+
function createSkillError(code, message, extras) {
|
|
4024
|
+
return {
|
|
4025
|
+
code,
|
|
4026
|
+
message,
|
|
4027
|
+
...extras
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
function isSkillError(error) {
|
|
4031
|
+
return typeof error === "object" && error !== null && "code" in error && "message" in error;
|
|
4032
|
+
}
|
|
4033
|
+
const SKILL_ERRORS = {
|
|
4034
|
+
// Validation errors
|
|
4035
|
+
VALIDATION_FAILED: createSkillError("VALIDATION_FAILED", "技能数据验证失败", {
|
|
4036
|
+
details: "请确保所有字段符合要求",
|
|
4037
|
+
recovery: [
|
|
4038
|
+
"检查必填字段是否都已填写",
|
|
4039
|
+
"确认字段长度在限制范围内",
|
|
4040
|
+
"验证字段格式是否正确"
|
|
4041
|
+
]
|
|
4042
|
+
}),
|
|
4043
|
+
MISSING_REQUIRED_FIELD: (field) => createSkillError("MISSING_REQUIRED_FIELD", `缺少必填字段`, {
|
|
4044
|
+
field,
|
|
4045
|
+
details: `字段 "${field}" 是必填的`,
|
|
4046
|
+
recovery: [`请填写 ${field} 字段`]
|
|
4047
|
+
}),
|
|
4048
|
+
// Permission errors
|
|
4049
|
+
CANNOT_MODIFY_BUILTIN: createSkillError(
|
|
4050
|
+
"CANNOT_MODIFY_BUILTIN",
|
|
4051
|
+
"无法修改内置技能",
|
|
4052
|
+
{
|
|
4053
|
+
details: "内置技能是系统提供的,不能直接修改",
|
|
4054
|
+
recovery: [
|
|
4055
|
+
"如需自定义,请创建一个新的用户技能",
|
|
4056
|
+
"复制内置技能的内容到新技能中",
|
|
4057
|
+
"根据需要修改新技能的内容"
|
|
4058
|
+
]
|
|
4059
|
+
}
|
|
4060
|
+
),
|
|
4061
|
+
CANNOT_DELETE_BUILTIN: createSkillError(
|
|
4062
|
+
"CANNOT_DELETE_BUILTIN",
|
|
4063
|
+
"无法删除内置技能",
|
|
4064
|
+
{
|
|
4065
|
+
details: "内置技能是系统提供的,不能删除",
|
|
4066
|
+
recovery: [
|
|
4067
|
+
"您可以禁用不需要的内置技能",
|
|
4068
|
+
"在设置中切换技能的启用状态"
|
|
4069
|
+
]
|
|
4070
|
+
}
|
|
4071
|
+
),
|
|
4072
|
+
SKILL_NOT_ENABLED: (skillName) => createSkillError("SKILL_NOT_ENABLED", `技能 "${skillName}" 未启用`, {
|
|
4073
|
+
details: "该技能当前处于禁用状态",
|
|
4074
|
+
recovery: [
|
|
4075
|
+
"在技能面板中启用该技能",
|
|
4076
|
+
"确保该技能在已启用技能列表中",
|
|
4077
|
+
"重新加载技能列表"
|
|
4078
|
+
]
|
|
4079
|
+
}),
|
|
4080
|
+
// Resource errors
|
|
4081
|
+
SKILL_NOT_FOUND: (skillId) => createSkillError("SKILL_NOT_FOUND", `找不到技能: ${skillId}`, {
|
|
4082
|
+
details: "技能 ID 不存在或已被删除",
|
|
4083
|
+
recovery: [
|
|
4084
|
+
"检查技能 ID 是否正确",
|
|
4085
|
+
"刷新技能列表",
|
|
4086
|
+
"该技能可能已被删除"
|
|
4087
|
+
]
|
|
4088
|
+
}),
|
|
4089
|
+
SKILL_ALREADY_EXISTS: (skillId) => createSkillError("SKILL_ALREADY_EXISTS", `技能已存在: ${skillId}`, {
|
|
4090
|
+
details: "具有相同 ID 的技能已经存在",
|
|
4091
|
+
recovery: [
|
|
4092
|
+
"使用不同的 ID 创建新技能",
|
|
4093
|
+
"如果要更新现有技能,请使用更新功能",
|
|
4094
|
+
"删除现有技能后重新创建"
|
|
4095
|
+
]
|
|
4096
|
+
}),
|
|
4097
|
+
// Storage errors
|
|
4098
|
+
STORAGE_ERROR: (details) => createSkillError("STORAGE_ERROR", "存储错误", {
|
|
4099
|
+
details,
|
|
4100
|
+
recovery: [
|
|
4101
|
+
"检查磁盘空间是否充足",
|
|
4102
|
+
"确保有文件写入权限",
|
|
4103
|
+
"尝试重新启动应用",
|
|
4104
|
+
"检查存储路径是否有效"
|
|
4105
|
+
]
|
|
4106
|
+
}),
|
|
4107
|
+
SAVE_FAILED: createSkillError("SAVE_FAILED", "保存技能失败", {
|
|
4108
|
+
details: "无法将技能数据保存到存储",
|
|
4109
|
+
recovery: [
|
|
4110
|
+
"检查技能数据格式是否正确",
|
|
4111
|
+
"确保存储路径可访问",
|
|
4112
|
+
"尝试删除并重新创建技能",
|
|
4113
|
+
"查看控制台日志获取详细错误信息"
|
|
4114
|
+
]
|
|
4115
|
+
})
|
|
4116
|
+
};
|
|
4117
|
+
function wrapSkillError(error, context, code = "UNKNOWN_ERROR") {
|
|
4118
|
+
if (isSkillError(error)) {
|
|
4119
|
+
return error;
|
|
4120
|
+
}
|
|
4121
|
+
if (error instanceof Error) {
|
|
4122
|
+
return createSkillError(code, `${context}: ${error.message}`, {
|
|
4123
|
+
details: error.stack,
|
|
4124
|
+
originalError: error
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
return createSkillError(code, `${context}: 未知错误`, {
|
|
4128
|
+
originalError: error
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
function parseValidationError(error) {
|
|
4132
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4133
|
+
if (errorMsg.includes("name")) {
|
|
4134
|
+
return SKILL_ERRORS.MISSING_REQUIRED_FIELD("name");
|
|
4135
|
+
}
|
|
4136
|
+
if (errorMsg.includes("description")) {
|
|
4137
|
+
return SKILL_ERRORS.MISSING_REQUIRED_FIELD("description");
|
|
4138
|
+
}
|
|
4139
|
+
if (errorMsg.includes("prompt")) {
|
|
4140
|
+
return SKILL_ERRORS.MISSING_REQUIRED_FIELD("prompt");
|
|
4141
|
+
}
|
|
4142
|
+
if (errorMsg.includes("category")) {
|
|
4143
|
+
return SKILL_ERRORS.MISSING_REQUIRED_FIELD("category");
|
|
4144
|
+
}
|
|
4145
|
+
return SKILL_ERRORS.VALIDATION_FAILED;
|
|
4146
|
+
}
|
|
3711
4147
|
function registerSkillsHandlers(ipcMain) {
|
|
3712
4148
|
console.log("[Skills] Registering skills handlers...");
|
|
3713
4149
|
ipcMain.on("skills:list", (event, params) => {
|
|
@@ -3774,12 +4210,29 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
3774
4210
|
ipcMain.on("skills:create", (event, params) => {
|
|
3775
4211
|
console.log("[Skills] Create request:", params.name);
|
|
3776
4212
|
try {
|
|
4213
|
+
const validation = validateSkillData({
|
|
4214
|
+
name: params.name,
|
|
4215
|
+
description: params.description,
|
|
4216
|
+
category: params.category,
|
|
4217
|
+
prompt: params.prompt,
|
|
4218
|
+
subSkills: params.subSkills
|
|
4219
|
+
});
|
|
4220
|
+
if (!validation.valid) {
|
|
4221
|
+
event.reply("skills:create:result", {
|
|
4222
|
+
success: false,
|
|
4223
|
+
error: validation.error || "Validation failed",
|
|
4224
|
+
field: validation.field
|
|
4225
|
+
});
|
|
4226
|
+
return;
|
|
4227
|
+
}
|
|
4228
|
+
const sanitizedPrompt = sanitizePrompt(params.prompt);
|
|
3777
4229
|
const skill = createUserSkill(
|
|
3778
4230
|
params.name,
|
|
3779
4231
|
params.description,
|
|
3780
4232
|
params.category,
|
|
3781
|
-
|
|
3782
|
-
params.subSkills
|
|
4233
|
+
sanitizedPrompt,
|
|
4234
|
+
params.subSkills,
|
|
4235
|
+
params.version
|
|
3783
4236
|
);
|
|
3784
4237
|
saveUserSkill(skill);
|
|
3785
4238
|
event.reply("skills:create:result", {
|
|
@@ -3800,16 +4253,40 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
3800
4253
|
const loader = getSkillLoader();
|
|
3801
4254
|
const existing = loader.getSkill(params.skillId);
|
|
3802
4255
|
if (!existing) {
|
|
4256
|
+
const skillError = SKILL_ERRORS.SKILL_NOT_FOUND(params.skillId);
|
|
3803
4257
|
event.reply("skills:update:result", {
|
|
3804
4258
|
success: false,
|
|
3805
|
-
error:
|
|
4259
|
+
error: skillError.message,
|
|
4260
|
+
details: skillError.details,
|
|
4261
|
+
recovery: skillError.recovery
|
|
3806
4262
|
});
|
|
3807
4263
|
return;
|
|
3808
4264
|
}
|
|
3809
4265
|
if (existing.isBuiltin) {
|
|
3810
4266
|
event.reply("skills:update:result", {
|
|
3811
4267
|
success: false,
|
|
3812
|
-
error:
|
|
4268
|
+
error: SKILL_ERRORS.CANNOT_MODIFY_BUILTIN.message,
|
|
4269
|
+
details: SKILL_ERRORS.CANNOT_MODIFY_BUILTIN.details,
|
|
4270
|
+
recovery: SKILL_ERRORS.CANNOT_MODIFY_BUILTIN.recovery
|
|
4271
|
+
});
|
|
4272
|
+
return;
|
|
4273
|
+
}
|
|
4274
|
+
const updatedData = {
|
|
4275
|
+
name: params.name || existing.name,
|
|
4276
|
+
description: params.description || existing.description,
|
|
4277
|
+
category: params.category || existing.category,
|
|
4278
|
+
prompt: params.prompt || existing.prompt,
|
|
4279
|
+
subSkills: params.subSkills !== void 0 ? params.subSkills : existing.subSkills
|
|
4280
|
+
};
|
|
4281
|
+
const validation = validateSkillData(updatedData);
|
|
4282
|
+
if (!validation.valid) {
|
|
4283
|
+
const error = parseValidationError(validation.error);
|
|
4284
|
+
event.reply("skills:update:result", {
|
|
4285
|
+
success: false,
|
|
4286
|
+
error: error.message,
|
|
4287
|
+
field: error.field,
|
|
4288
|
+
details: error.details,
|
|
4289
|
+
recovery: error.recovery
|
|
3813
4290
|
});
|
|
3814
4291
|
return;
|
|
3815
4292
|
}
|
|
@@ -3818,7 +4295,7 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
3818
4295
|
...params.name && { name: params.name },
|
|
3819
4296
|
...params.description && { description: params.description },
|
|
3820
4297
|
...params.category && { category: params.category },
|
|
3821
|
-
...params.prompt && { prompt: params.prompt },
|
|
4298
|
+
...params.prompt && { prompt: sanitizePrompt(params.prompt) },
|
|
3822
4299
|
...params.subSkills && { subSkills: params.subSkills },
|
|
3823
4300
|
updatedAt: /* @__PURE__ */ new Date()
|
|
3824
4301
|
};
|
|
@@ -3829,9 +4306,12 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
3829
4306
|
});
|
|
3830
4307
|
} catch (error) {
|
|
3831
4308
|
console.error("[Skills] Update error:", error);
|
|
4309
|
+
const wrappedError = wrapSkillError(error, "更新技能失败");
|
|
3832
4310
|
event.reply("skills:update:result", {
|
|
3833
4311
|
success: false,
|
|
3834
|
-
error:
|
|
4312
|
+
error: wrappedError.message,
|
|
4313
|
+
details: wrappedError.details,
|
|
4314
|
+
recovery: wrappedError.recovery
|
|
3835
4315
|
});
|
|
3836
4316
|
}
|
|
3837
4317
|
});
|
|
@@ -3866,12 +4346,12 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
3866
4346
|
});
|
|
3867
4347
|
}
|
|
3868
4348
|
});
|
|
3869
|
-
ipcMain.on("skills:toggle", (event, { skillId, enabled }) => {
|
|
4349
|
+
ipcMain.on("skills:toggle", async (event, { skillId, enabled }) => {
|
|
3870
4350
|
console.log("[Skills] Toggle request:", skillId, enabled);
|
|
3871
4351
|
try {
|
|
3872
4352
|
toggleSkillEnabled(skillId, enabled);
|
|
3873
4353
|
const enabledIds = getEnabledSkillIds();
|
|
3874
|
-
syncEnabledSkills(enabledIds);
|
|
4354
|
+
await syncEnabledSkills(enabledIds);
|
|
3875
4355
|
event.reply("skills:toggle:result", {
|
|
3876
4356
|
success: true,
|
|
3877
4357
|
enabled
|
|
@@ -3884,11 +4364,11 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
3884
4364
|
});
|
|
3885
4365
|
}
|
|
3886
4366
|
});
|
|
3887
|
-
ipcMain.on("skills:setEnabled", (event, { skillIds }) => {
|
|
4367
|
+
ipcMain.on("skills:setEnabled", async (event, { skillIds }) => {
|
|
3888
4368
|
console.log("[Skills] Set enabled request:", skillIds);
|
|
3889
4369
|
try {
|
|
3890
4370
|
setEnabledSkillIds(skillIds);
|
|
3891
|
-
syncEnabledSkills(skillIds);
|
|
4371
|
+
await syncEnabledSkills(skillIds);
|
|
3892
4372
|
event.reply("skills:setEnabled:result", {
|
|
3893
4373
|
success: true,
|
|
3894
4374
|
skillIds
|
|
@@ -3972,35 +4452,63 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
3972
4452
|
});
|
|
3973
4453
|
}
|
|
3974
4454
|
});
|
|
3975
|
-
ipcMain.on(
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4455
|
+
ipcMain.on(
|
|
4456
|
+
"skills:import",
|
|
4457
|
+
(event, {
|
|
4458
|
+
data
|
|
4459
|
+
}) => {
|
|
4460
|
+
console.log("[Skills] Import request:", data.skills.length, "skills");
|
|
4461
|
+
try {
|
|
4462
|
+
const imported = [];
|
|
4463
|
+
const errors = [];
|
|
4464
|
+
for (let i = 0; i < data.skills.length; i++) {
|
|
4465
|
+
const skillData = data.skills[i];
|
|
4466
|
+
const validation = validateSkillData({
|
|
4467
|
+
name: skillData.name,
|
|
4468
|
+
description: skillData.description,
|
|
4469
|
+
category: skillData.category,
|
|
4470
|
+
prompt: skillData.prompt,
|
|
4471
|
+
subSkills: skillData.subSkills
|
|
4472
|
+
});
|
|
4473
|
+
if (!validation.valid) {
|
|
4474
|
+
errors.push({
|
|
4475
|
+
index: i,
|
|
4476
|
+
skill: skillData.name,
|
|
4477
|
+
error: validation.error || "Validation failed"
|
|
4478
|
+
});
|
|
4479
|
+
continue;
|
|
4480
|
+
}
|
|
4481
|
+
const skill = {
|
|
4482
|
+
...skillData,
|
|
4483
|
+
id: `imported-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
4484
|
+
prompt: sanitizePrompt(skillData.prompt),
|
|
4485
|
+
// Sanitize prompt
|
|
4486
|
+
enabled: false,
|
|
4487
|
+
isBuiltin: false,
|
|
4488
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
4489
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
4490
|
+
};
|
|
4491
|
+
saveUserSkill(skill);
|
|
4492
|
+
imported.push(skill);
|
|
4493
|
+
}
|
|
4494
|
+
getSkillLoader().refresh();
|
|
4495
|
+
event.reply("skills:import:result", {
|
|
4496
|
+
success: true,
|
|
4497
|
+
imported: imported.map((s) => ({ id: s.id, name: s.name })),
|
|
4498
|
+
total: data.skills.length,
|
|
4499
|
+
importedCount: imported.length,
|
|
4500
|
+
errorCount: errors.length,
|
|
4501
|
+
errors: errors.length > 0 ? errors : void 0
|
|
4502
|
+
});
|
|
4503
|
+
} catch (error) {
|
|
4504
|
+
console.error("[Skills] Import error:", error);
|
|
4505
|
+
event.reply("skills:import:result", {
|
|
4506
|
+
success: false,
|
|
4507
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
4508
|
+
});
|
|
3990
4509
|
}
|
|
3991
|
-
getSkillLoader().refresh();
|
|
3992
|
-
event.reply("skills:import:result", {
|
|
3993
|
-
success: true,
|
|
3994
|
-
imported: imported.map((s) => ({ id: s.id, name: s.name }))
|
|
3995
|
-
});
|
|
3996
|
-
} catch (error) {
|
|
3997
|
-
console.error("[Skills] Import error:", error);
|
|
3998
|
-
event.reply("skills:import:result", {
|
|
3999
|
-
success: false,
|
|
4000
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
4001
|
-
});
|
|
4002
4510
|
}
|
|
4003
|
-
|
|
4511
|
+
);
|
|
4004
4512
|
ipcMain.on("skills:getStats", (event) => {
|
|
4005
4513
|
console.log("[Skills] Get stats request");
|
|
4006
4514
|
try {
|
|
@@ -4013,10 +4521,13 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
4013
4521
|
builtin: allSkills.filter((s) => s.isBuiltin).length,
|
|
4014
4522
|
user: allSkills.filter((s) => !s.isBuiltin).length,
|
|
4015
4523
|
enabled: enabledIds.length,
|
|
4016
|
-
byCategory: allSkills.reduce(
|
|
4017
|
-
acc
|
|
4018
|
-
|
|
4019
|
-
|
|
4524
|
+
byCategory: allSkills.reduce(
|
|
4525
|
+
(acc, skill) => {
|
|
4526
|
+
acc[skill.category] = (acc[skill.category] || 0) + 1;
|
|
4527
|
+
return acc;
|
|
4528
|
+
},
|
|
4529
|
+
{}
|
|
4530
|
+
),
|
|
4020
4531
|
mostUsed: Object.values(usageStats).sort((a, b) => b.count - a.count).slice(0, 5).map((s) => ({ skillId: s.skillId, count: s.count, lastUsed: s.lastUsed }))
|
|
4021
4532
|
};
|
|
4022
4533
|
event.reply("skills:getStats:result", {
|
|
@@ -4055,6 +4566,39 @@ function registerSkillsHandlers(ipcMain) {
|
|
|
4055
4566
|
});
|
|
4056
4567
|
}
|
|
4057
4568
|
});
|
|
4569
|
+
ipcMain.on("skills:getCacheStats", (event) => {
|
|
4570
|
+
console.log("[Skills] Get cache stats request");
|
|
4571
|
+
try {
|
|
4572
|
+
const loader = getSkillLoader();
|
|
4573
|
+
const stats = loader.getCacheStats();
|
|
4574
|
+
event.reply("skills:getCacheStats:result", {
|
|
4575
|
+
success: true,
|
|
4576
|
+
stats
|
|
4577
|
+
});
|
|
4578
|
+
} catch (error) {
|
|
4579
|
+
console.error("[Skills] Get cache stats error:", error);
|
|
4580
|
+
event.reply("skills:getCacheStats:result", {
|
|
4581
|
+
success: false,
|
|
4582
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
});
|
|
4586
|
+
ipcMain.on("skills:clearCache", (event) => {
|
|
4587
|
+
console.log("[Skills] Clear cache request");
|
|
4588
|
+
try {
|
|
4589
|
+
const loader = getSkillLoader();
|
|
4590
|
+
loader.clearCache();
|
|
4591
|
+
event.reply("skills:clearCache:result", {
|
|
4592
|
+
success: true
|
|
4593
|
+
});
|
|
4594
|
+
} catch (error) {
|
|
4595
|
+
console.error("[Skills] Clear cache error:", error);
|
|
4596
|
+
event.reply("skills:clearCache:result", {
|
|
4597
|
+
success: false,
|
|
4598
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
4601
|
+
});
|
|
4058
4602
|
console.log("[Skills] Handlers registered successfully");
|
|
4059
4603
|
}
|
|
4060
4604
|
let mainWindow = null;
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
--spacing: .25rem;
|
|
97
97
|
--container-md: 28rem;
|
|
98
98
|
--container-lg: 32rem;
|
|
99
|
+
--container-2xl: 42rem;
|
|
99
100
|
--container-3xl: 48rem;
|
|
100
101
|
--text-xs: .75rem;
|
|
101
102
|
--text-xs--line-height: calc(1 / .75);
|
|
@@ -751,6 +752,10 @@
|
|
|
751
752
|
max-height: 80vh;
|
|
752
753
|
}
|
|
753
754
|
|
|
755
|
+
.max-h-\[90vh\] {
|
|
756
|
+
max-height: 90vh;
|
|
757
|
+
}
|
|
758
|
+
|
|
754
759
|
.min-h-0 {
|
|
755
760
|
min-height: calc(var(--spacing) * 0);
|
|
756
761
|
}
|
|
@@ -847,6 +852,10 @@
|
|
|
847
852
|
width: 1px;
|
|
848
853
|
}
|
|
849
854
|
|
|
855
|
+
.max-w-2xl {
|
|
856
|
+
max-width: var(--container-2xl);
|
|
857
|
+
}
|
|
858
|
+
|
|
850
859
|
.max-w-3xl {
|
|
851
860
|
max-width: var(--container-3xl);
|
|
852
861
|
}
|
|
@@ -76278,7 +76278,14 @@ function TabbedPanel({ threadId, showTabBar = true }) {
|
|
|
76278
76278
|
) })
|
|
76279
76279
|
] });
|
|
76280
76280
|
}
|
|
76281
|
-
function Switch({
|
|
76281
|
+
function Switch({
|
|
76282
|
+
checked = false,
|
|
76283
|
+
onCheckedChange,
|
|
76284
|
+
disabled = false,
|
|
76285
|
+
className,
|
|
76286
|
+
onClick,
|
|
76287
|
+
...props
|
|
76288
|
+
}) {
|
|
76282
76289
|
const handleClick = (e) => {
|
|
76283
76290
|
onClick?.(e);
|
|
76284
76291
|
if (!disabled) {
|
|
@@ -76611,19 +76618,20 @@ function CreateSkillDialog({ open, onClose, onCreate }) {
|
|
|
76611
76618
|
};
|
|
76612
76619
|
const currentTemplate = SKILL_TEMPLATES.find((t) => t.id === selectedTemplate);
|
|
76613
76620
|
if (!open) return null;
|
|
76614
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-
|
|
76615
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/5", children: [
|
|
76621
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-2xl bg-[#1A1A1D] rounded-lg shadow-xl border border-white/10 flex flex-col max-h-[90vh]", children: [
|
|
76622
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/5 flex-shrink-0", children: [
|
|
76616
76623
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-lg font-semibold text-white", children: "Create Custom Skill" }),
|
|
76617
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
76618
|
-
"
|
|
76624
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-gray-400 hover:text-white transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
76625
|
+
"path",
|
|
76619
76626
|
{
|
|
76620
|
-
|
|
76621
|
-
|
|
76622
|
-
|
|
76627
|
+
strokeLinecap: "round",
|
|
76628
|
+
strokeLinejoin: "round",
|
|
76629
|
+
strokeWidth: 2,
|
|
76630
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
76623
76631
|
}
|
|
76624
|
-
)
|
|
76632
|
+
) }) })
|
|
76625
76633
|
] }),
|
|
76626
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-6 py-3 border-b border-white/5", children: [
|
|
76634
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-6 py-3 border-b border-white/5 flex-shrink-0", children: [
|
|
76627
76635
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
76628
76636
|
"button",
|
|
76629
76637
|
{
|
|
@@ -76643,7 +76651,7 @@ function CreateSkillDialog({ open, onClose, onCreate }) {
|
|
|
76643
76651
|
}
|
|
76644
76652
|
)
|
|
76645
76653
|
] }),
|
|
76646
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "p-6 space-y-4", children: [
|
|
76654
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "p-6 space-y-4 overflow-y-auto flex-1", children: [
|
|
76647
76655
|
mode === "template" && /* Template Selection */
|
|
76648
76656
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
76649
76657
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium text-gray-300 mb-2", children: "Choose Template" }),
|
|
@@ -76749,7 +76757,7 @@ function CreateSkillDialog({ open, onClose, onCreate }) {
|
|
|
76749
76757
|
),
|
|
76750
76758
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 text-xs text-gray-500", children: "This prompt will be loaded when the agent activates this skill, providing specialized knowledge and instructions." })
|
|
76751
76759
|
] }),
|
|
76752
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-end gap-3 pt-4 border-t border-white/5", children: [
|
|
76760
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-end gap-3 pt-4 border-t border-white/5 flex-shrink-0", children: [
|
|
76753
76761
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
76754
76762
|
"button",
|
|
76755
76763
|
{
|
|
@@ -76780,7 +76788,13 @@ const categoryColors = {
|
|
|
76780
76788
|
system: "bg-orange-500/10 text-orange-500 border-orange-500/20",
|
|
76781
76789
|
custom: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20"
|
|
76782
76790
|
};
|
|
76783
|
-
function SkillDetailDialog({
|
|
76791
|
+
function SkillDetailDialog({
|
|
76792
|
+
skill,
|
|
76793
|
+
open,
|
|
76794
|
+
onClose,
|
|
76795
|
+
onToggle,
|
|
76796
|
+
onDelete
|
|
76797
|
+
}) {
|
|
76784
76798
|
if (!open) return null;
|
|
76785
76799
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-3xl mx-4 max-h-[80vh] bg-[#1A1A1D] rounded-lg shadow-xl border border-white/10 flex flex-col", children: [
|
|
76786
76800
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between px-6 py-4 border-b border-white/5", children: [
|
|
@@ -76803,7 +76817,15 @@ function SkillDetailDialog({ skill, open, onClose, onToggle, onDelete }) {
|
|
|
76803
76817
|
{
|
|
76804
76818
|
onClick: onClose,
|
|
76805
76819
|
className: "ml-4 text-gray-400 hover:text-white transition-colors",
|
|
76806
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
76820
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
76821
|
+
"path",
|
|
76822
|
+
{
|
|
76823
|
+
strokeLinecap: "round",
|
|
76824
|
+
strokeLinejoin: "round",
|
|
76825
|
+
strokeWidth: 2,
|
|
76826
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
76827
|
+
}
|
|
76828
|
+
) })
|
|
76807
76829
|
}
|
|
76808
76830
|
)
|
|
76809
76831
|
] }),
|
|
@@ -77215,7 +77237,15 @@ function RightPanel() {
|
|
|
77215
77237
|
else if (nextPanel === "skills") setSkillsOpen(false);
|
|
77216
77238
|
}
|
|
77217
77239
|
},
|
|
77218
|
-
[
|
|
77240
|
+
[
|
|
77241
|
+
getContentHeights,
|
|
77242
|
+
getAvailableContentHeight,
|
|
77243
|
+
filesOpen,
|
|
77244
|
+
agentsOpen,
|
|
77245
|
+
skillsOpen,
|
|
77246
|
+
agentsHeight,
|
|
77247
|
+
skillsHeight
|
|
77248
|
+
]
|
|
77219
77249
|
);
|
|
77220
77250
|
const handleFilesResize = reactExports.useCallback(
|
|
77221
77251
|
(totalDelta) => {
|
|
@@ -78201,7 +78231,7 @@ function App() {
|
|
|
78201
78231
|
},
|
|
78202
78232
|
children: [
|
|
78203
78233
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-name", children: "OPENWORK" }),
|
|
78204
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-version", children: "0.
|
|
78234
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-version", children: "0.3.0" })
|
|
78205
78235
|
]
|
|
78206
78236
|
}
|
|
78207
78237
|
),
|
package/out/renderer/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
http-equiv="Content-Security-Policy"
|
|
8
8
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
|
9
9
|
/>
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-ZbiHA5we.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="./assets/index-0WBq9FlY.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|