@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 | 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!** |
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
- ensureSkillsDir();
2705
+ async function saveSkillFile(skill) {
2706
+ await ensureSkillsDirAsync();
2686
2707
  const skillDir = path.join(SKILLS_FILE_DIR, skill.id);
2687
- if (!fs.existsSync(skillDir)) {
2688
- fs.mkdirSync(skillDir, { recursive: true });
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.writeFileSync(skillFilePath, content, "utf-8");
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
- if (fs.existsSync(skillFilePath)) {
2699
- fs.unlinkSync(skillFilePath);
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
- ensureSkillsDir();
2709
- for (const skill of BUILTIN_SKILLS) {
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
- ensureSkillsDir();
2716
- for (const skill of BUILTIN_SKILLS) {
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(BUILTIN_SKILLS.map((s) => [s.id, s]));
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
- if (this.cache.has(id)) {
3579
- return this.cache.get(id);
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.cache.set(id, skill);
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.cache.set(id, skill);
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.cache.clear();
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
- params.prompt,
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: `Skill not found: ${params.skillId}`
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: "Cannot modify built-in skills"
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: error instanceof Error ? error.message : "Unknown 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("skills:import", (event, { data }) => {
3976
- console.log("[Skills] Import request:", data.skills.length, "skills");
3977
- try {
3978
- const imported = [];
3979
- for (const skillData of data.skills) {
3980
- const skill = {
3981
- ...skillData,
3982
- id: `imported-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
3983
- enabled: false,
3984
- isBuiltin: false,
3985
- createdAt: /* @__PURE__ */ new Date(),
3986
- updatedAt: /* @__PURE__ */ new Date()
3987
- };
3988
- saveUserSkill(skill);
3989
- imported.push(skill);
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((acc, skill) => {
4017
- acc[skill.category] = (acc[skill.category] || 0) + 1;
4018
- return acc;
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({ checked = false, onCheckedChange, disabled = false, className, onClick, ...props }) {
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-3xl mx-4 bg-[#1A1A1D] rounded-lg shadow-xl border border-white/10", children: [
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
- "button",
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
- onClick: onClose,
76621
- className: "text-gray-400 hover:text-white transition-colors",
76622
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
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({ skill, open, onClose, onToggle, onDelete }) {
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("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
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
- [getContentHeights, getAvailableContentHeight, filesOpen, agentsOpen, skillsOpen, agentsHeight, skillsHeight]
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.2.4" })
78234
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-version", children: "0.3.0" })
78205
78235
  ]
78206
78236
  }
78207
78237
  ),
@@ -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-Be3u7LN6.js"></script>
11
- <link rel="stylesheet" crossorigin href="./assets/index-B2t12qbx.css">
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniqueli/openwork",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "A tactical agent interface for deepagentsjs with multiple custom API support",
5
5
  "main": "./out/main/index.js",
6
6
  "files": [