@eoasmxd/freya 0.4.0 → 0.4.2

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.
Files changed (57) hide show
  1. package/core/dist/agent/agent-executor.js +5 -0
  2. package/core/dist/command/command-executor.js +7 -0
  3. package/core/dist/command/command-registry.d.ts +6 -1
  4. package/core/dist/command/command-registry.js +31 -1
  5. package/core/dist/command/commands/skill-commands.js +5 -4
  6. package/core/dist/config/config-manager.d.ts +5 -1
  7. package/core/dist/config/config-manager.js +48 -1
  8. package/core/dist/kernel.js +4 -4
  9. package/core/dist/skill/skill-registry.d.ts +12 -2
  10. package/core/dist/skill/skill-registry.js +89 -8
  11. package/core/dist/tools/meta/index.d.ts +3 -1
  12. package/core/dist/tools/meta/index.js +9 -1
  13. package/core/dist/tools/tool-registry.d.ts +12 -7
  14. package/core/dist/tools/tool-registry.js +44 -8
  15. package/core/dist/web/config-api.js +18 -0
  16. package/core/package.json +2 -2
  17. package/package.json +4 -3
  18. package/plugins/plugin-gemini/package.json +1 -1
  19. package/plugins/plugin-openai/package.json +1 -1
  20. package/plugins/plugin-telegram-channel/package.json +1 -1
  21. package/plugins/plugin-tool-fs/package.json +1 -1
  22. package/plugins/plugin-tool-memory/package.json +1 -1
  23. package/plugins/plugin-tool-mysql/config/prompts/plugin.prompt.mysql.md +9 -0
  24. package/plugins/plugin-tool-mysql/config/prompts/plugin.prompt.mysql.select.audit.md +26 -0
  25. package/plugins/plugin-tool-mysql/dist/audit.d.ts +13 -0
  26. package/plugins/plugin-tool-mysql/dist/audit.js +87 -0
  27. package/plugins/plugin-tool-mysql/dist/index.d.ts +14 -0
  28. package/plugins/plugin-tool-mysql/dist/index.js +34 -0
  29. package/plugins/plugin-tool-mysql/dist/pool-manager.d.ts +32 -0
  30. package/plugins/plugin-tool-mysql/dist/pool-manager.js +113 -0
  31. package/plugins/plugin-tool-mysql/dist/tools.d.ts +11 -0
  32. package/plugins/plugin-tool-mysql/dist/tools.js +88 -0
  33. package/plugins/plugin-tool-mysql/package.json +33 -0
  34. package/plugins/plugin-tool-mysql/schema.json +83 -0
  35. package/plugins/plugin-tool-web/package.json +1 -1
  36. package/plugins/plugin-wecom-channel/package.json +1 -1
  37. package/plugins/plugin-weixin-channel/dist/index.js +10 -36
  38. package/plugins/plugin-weixin-channel/package.json +3 -2
  39. package/src/packages/core/src/agent/agent-executor.ts +5 -0
  40. package/src/packages/core/src/command/command-executor.ts +8 -0
  41. package/src/packages/core/src/command/command-registry.ts +35 -1
  42. package/src/packages/core/src/command/commands/skill-commands.ts +6 -5
  43. package/src/packages/core/src/config/config-manager.ts +50 -1
  44. package/src/packages/core/src/kernel.ts +5 -4
  45. package/src/packages/core/src/skill/skill-registry.ts +100 -8
  46. package/src/packages/core/src/tools/meta/index.ts +9 -1
  47. package/src/packages/core/src/tools/tool-registry.ts +49 -9
  48. package/src/packages/core/src/web/config-api.ts +20 -0
  49. package/src/packages/ui/src/features/config/ConfigModal.tsx +13 -1
  50. package/src/packages/ui/src/features/config/panels/SkillConfigPanel.tsx +137 -0
  51. package/src/plugins/plugin-tool-mysql/src/audit.ts +103 -0
  52. package/src/plugins/plugin-tool-mysql/src/index.ts +44 -0
  53. package/src/plugins/plugin-tool-mysql/src/pool-manager.ts +132 -0
  54. package/src/plugins/plugin-tool-mysql/src/tools.ts +100 -0
  55. package/src/plugins/plugin-weixin-channel/src/index.ts +12 -37
  56. package/ui/assets/{index-Be0cAgdB.js → index-BqPQMflk.js} +14 -14
  57. package/ui/index.html +1 -1
@@ -187,6 +187,11 @@ export class FreyaAgentExecutor {
187
187
  const nextToolboxIdleRounds = {};
188
188
  const deactivatedIds = [];
189
189
  for (const id of activeToolboxIds) {
190
+ if (!this.toolRegistry.isToolboxEnabled(id)) {
191
+ deactivatedIds.push(id);
192
+ this.context.logger.info(`[FreyaAgentExecutor] 工具箱 [${id}] 已被全局禁用,自动从当前会话卸载。`);
193
+ continue;
194
+ }
190
195
  const boxTools = this.toolRegistry.getToolsInBox(id);
191
196
  const isUsed = boxTools.some((tool) => executedToolNames.has(tool.getDefinition().name));
192
197
  if (isUsed) {
@@ -23,6 +23,13 @@ export class FreyaCommandExecutor {
23
23
  });
24
24
  return true;
25
25
  }
26
+ if (!this.registry.isCommandEnabled(commandName)) {
27
+ this.context.eventBus.emit('session:reply:error', {
28
+ sessionId,
29
+ message: `❌ 权限拒绝:系统指令 "/${commandName}" 已被系统管理员全局禁用。`
30
+ });
31
+ return true;
32
+ }
26
33
  try {
27
34
  const replyContent = await cmd.execute(args, sessionId, this.context, connectionId);
28
35
  if (replyContent) {
@@ -1,9 +1,14 @@
1
- import type { FreyaCommand } from '@eoasmxd/freya-sdk';
1
+ import type { FreyaCommand, FreyaContext } from '@eoasmxd/freya-sdk';
2
2
  /** 系统指令注册表:维护主指令与别名的映射字典 */
3
3
  export declare class FreyaCommandRegistry {
4
+ private context?;
4
5
  private commands;
5
6
  private aliasMap;
6
7
  private pluginCommandsMap;
8
+ constructor(context?: FreyaContext | undefined);
9
+ setContext(context: FreyaContext): void;
10
+ /** 判断指定指令当前是否已注册且处于可用启用状态 */
11
+ isCommandEnabled(name: string): boolean;
7
12
  /** 注册新的系统指令并建立别名路由 */
8
13
  register(command: FreyaCommand, pluginId?: string): void;
9
14
  /** 注销指定插件注册的所有指令与别名 */
@@ -1,8 +1,38 @@
1
+ const BUILTIN_COMMAND_CATEGORY_MAP = {
2
+ approve: 'auth',
3
+ reject: 'auth',
4
+ session: 'session',
5
+ model: 'model',
6
+ models: 'model'
7
+ };
1
8
  /** 系统指令注册表:维护主指令与别名的映射字典 */
2
9
  export class FreyaCommandRegistry {
10
+ context;
3
11
  commands = new Map();
4
12
  aliasMap = new Map();
5
13
  pluginCommandsMap = new Map();
14
+ constructor(context) {
15
+ this.context = context;
16
+ }
17
+ setContext(context) {
18
+ this.context = context;
19
+ }
20
+ /** 判断指定指令当前是否已注册且处于可用启用状态 */
21
+ isCommandEnabled(name) {
22
+ const lowerName = name.toLowerCase();
23
+ const primaryName = this.aliasMap.get(lowerName) || lowerName;
24
+ if (!this.commands.has(primaryName)) {
25
+ return false;
26
+ }
27
+ const category = BUILTIN_COMMAND_CATEGORY_MAP[primaryName];
28
+ if (category) {
29
+ const commandsConfig = this.context?.config?.commands?.builtin;
30
+ if (commandsConfig && typeof commandsConfig[category]?.enabled === 'boolean') {
31
+ return commandsConfig[category].enabled;
32
+ }
33
+ }
34
+ return true;
35
+ }
6
36
  /** 注册新的系统指令并建立别名路由 */
7
37
  register(command, pluginId) {
8
38
  const name = command.name.toLowerCase();
@@ -54,6 +84,6 @@ export class FreyaCommandRegistry {
54
84
  return undefined;
55
85
  }
56
86
  list() {
57
- return Array.from(this.commands.values());
87
+ return Array.from(this.commands.values()).filter((cmd) => this.isCommandEnabled(cmd.name));
58
88
  }
59
89
  }
@@ -10,10 +10,11 @@ export function registerSkillCommands(deps) {
10
10
  return '🔧 当前未激活任何技能。';
11
11
  };
12
12
  const handleList = async () => {
13
- if (skills.size === 0) {
13
+ const activeSkills = Array.from(skills.values()).filter((s) => s.enabled !== false);
14
+ if (activeSkills.length === 0) {
14
15
  return '📋 暂无可用技能。';
15
16
  }
16
- const lines = Array.from(skills.values()).map((s) => `- **${s.name}** — ${s.description || '无描述'}`).join('\n');
17
+ const lines = activeSkills.map((s) => `- **${s.name}** \`${s.id}\` — ${s.description || '无描述'}`).join('\n');
17
18
  return `### 📋 可用技能\n\n${lines}`;
18
19
  };
19
20
  const handleSet = async (skillId, sessionId) => {
@@ -21,8 +22,8 @@ export function registerSkillCommands(deps) {
21
22
  return '❌ 用法:`\`/skill set <skillId>\``。';
22
23
  }
23
24
  const skill = skills.get(skillId);
24
- if (!skill) {
25
- return `❌ 技能 \`${skillId}\` 不存在。请使用 \`/skill list\` 查看可用技能。`;
25
+ if (!skill || skill.enabled === false) {
26
+ return `❌ 技能 \`${skillId}\` 不存在或未启用。请使用 \`/skill list\` 查看可用技能。`;
26
27
  }
27
28
  await sessionManager.updateSession(sessionId, { activeSkillId: skillId });
28
29
  return `✅ 已激活技能: **${skill.name}** \`${skillId}\`。`;
@@ -3,6 +3,7 @@ import type { FreyaLLMRegistry } from '../llm/llm-registry.js';
3
3
  import type { FreyaPluginManager } from '../plugin/plugin-manager.js';
4
4
  import type { FreyaPromptManager } from '../prompt/prompt-manager.js';
5
5
  import { FreyaConfigSchemaRegistry } from './schema-registry.js';
6
+ import type { FreyaSkillRegistry, FreyaSkill } from '../skill/skill-registry.js';
6
7
  /** 核心统一配置管理器 */
7
8
  export declare class FreyaConfigManager {
8
9
  private context;
@@ -11,7 +12,8 @@ export declare class FreyaConfigManager {
11
12
  private llmRegistry;
12
13
  private pluginManager;
13
14
  private promptManager;
14
- constructor(context: FreyaContext, schemaRegistry: FreyaConfigSchemaRegistry, promptManager: FreyaPromptManager, llmRegistry: FreyaLLMRegistry, pluginManager: FreyaPluginManager);
15
+ private skillRegistry?;
16
+ constructor(context: FreyaContext, schemaRegistry: FreyaConfigSchemaRegistry, promptManager: FreyaPromptManager, llmRegistry: FreyaLLMRegistry, pluginManager: FreyaPluginManager, skillRegistry?: FreyaSkillRegistry);
15
17
  /** 获取全部敏感字段的 keyPath 列表 */
16
18
  getSensitiveKeys(): string[];
17
19
  loadAndInit(): Promise<void>;
@@ -38,6 +40,8 @@ export declare class FreyaConfigManager {
38
40
  removeModel(providerId: string, modelId: string): Promise<string>;
39
41
  listPlugins(): Promise<any[]>;
40
42
  togglePlugin(pluginId: string, enabled: boolean): Promise<string>;
43
+ listSkills(): FreyaSkill[];
44
+ toggleSkill(skillId: string, enabled: boolean): Promise<string>;
41
45
  readPrompt(name: string): Promise<string>;
42
46
  writePrompt(name: string, content: string): Promise<string>;
43
47
  editPrompt(name: string, targetContent: string, replacementContent: string): Promise<string>;
@@ -138,12 +138,14 @@ export class FreyaConfigManager {
138
138
  llmRegistry;
139
139
  pluginManager;
140
140
  promptManager;
141
- constructor(context, schemaRegistry, promptManager, llmRegistry, pluginManager) {
141
+ skillRegistry;
142
+ constructor(context, schemaRegistry, promptManager, llmRegistry, pluginManager, skillRegistry) {
142
143
  this.context = context;
143
144
  this.schemaRegistry = schemaRegistry;
144
145
  this.promptManager = promptManager;
145
146
  this.llmRegistry = llmRegistry;
146
147
  this.pluginManager = pluginManager;
148
+ this.skillRegistry = skillRegistry;
147
149
  }
148
150
  /** 获取全部敏感字段的 keyPath 列表 */
149
151
  getSensitiveKeys() {
@@ -475,6 +477,16 @@ export class FreyaConfigManager {
475
477
  return '❌ 插件服务未初始化。';
476
478
  return await this.pluginManager.togglePlugin(pluginId, enabled);
477
479
  }
480
+ listSkills() {
481
+ if (!this.skillRegistry)
482
+ return [];
483
+ return this.skillRegistry.getAllSkills();
484
+ }
485
+ async toggleSkill(skillId, enabled) {
486
+ if (!this.skillRegistry)
487
+ return '❌ 技能注册表服务未初始化。';
488
+ return await this.skillRegistry.toggleSkill(skillId, enabled);
489
+ }
478
490
  async readPrompt(name) {
479
491
  const promptName = String(name).trim().toUpperCase();
480
492
  if (!ALLOWED_PROMPTS.has(promptName)) {
@@ -676,6 +688,41 @@ export class FreyaConfigManager {
676
688
  min: 10,
677
689
  max: 300,
678
690
  category: '安全'
691
+ },
692
+ {
693
+ key: 'tools.builtin.config.enabled',
694
+ defaultValue: true,
695
+ description: '是否启用系统核心配置工具箱(允许大模型查看与修改系统配置)',
696
+ type: 'boolean',
697
+ category: '系统工具'
698
+ },
699
+ {
700
+ key: 'tools.builtin.session.enabled',
701
+ defaultValue: true,
702
+ description: '是否启用会话与子任务管理工具箱(允许大模型查阅会话历史与派生子任务)',
703
+ type: 'boolean',
704
+ category: '系统工具'
705
+ },
706
+ {
707
+ key: 'commands.builtin.auth.enabled',
708
+ defaultValue: true,
709
+ description: '是否启用敏感操作授权审批指令(/approve 与 /reject)',
710
+ type: 'boolean',
711
+ category: '系统指令'
712
+ },
713
+ {
714
+ key: 'commands.builtin.session.enabled',
715
+ defaultValue: true,
716
+ description: '是否启用会话管理与路由指令(/session 及其子命令)',
717
+ type: 'boolean',
718
+ category: '系统指令'
719
+ },
720
+ {
721
+ key: 'commands.builtin.model.enabled',
722
+ defaultValue: true,
723
+ description: '是否启用模型查看与切换指令(/model 及其子命令)',
724
+ type: 'boolean',
725
+ category: '系统指令'
679
726
  }
680
727
  ];
681
728
  this.schemaRegistry.register('core', coreFields);
@@ -44,17 +44,17 @@ export class FreyaKernel {
44
44
  ctx.logger.info('Freya 核心服务正在启动...');
45
45
  ctx.eventBus = new FreyaEventBus();
46
46
  const configSchemaRegistry = new FreyaConfigSchemaRegistry();
47
- const toolRegistry = new FreyaToolRegistry();
47
+ const toolRegistry = new FreyaToolRegistry(ctx);
48
48
  const llmRegistry = new FreyaLLMRegistry();
49
49
  const promptRegistry = new FreyaPromptRegistry();
50
- const commandRegistry = new FreyaCommandRegistry();
50
+ const commandRegistry = new FreyaCommandRegistry(ctx);
51
51
  this.channelRegistry = new FreyaChannelRegistry();
52
52
  const pluginRegistry = new FreyaPluginRegistry(toolRegistry, llmRegistry, this.channelRegistry);
53
53
  const skillRegistry = new FreyaSkillRegistry();
54
54
  const promptManager = new FreyaPromptManager(promptRegistry, ctx.logger);
55
55
  this.pluginManager = new FreyaPluginManager(configSchemaRegistry, commandRegistry, promptRegistry);
56
56
  await this.pluginManager.loadConfiguredPlugins(pluginRegistry, ctx);
57
- const configManager = new FreyaConfigManager(ctx, configSchemaRegistry, promptManager, llmRegistry, this.pluginManager);
57
+ const configManager = new FreyaConfigManager(ctx, configSchemaRegistry, promptManager, llmRegistry, this.pluginManager, skillRegistry);
58
58
  configManager.registerCoreSchema();
59
59
  await configManager.loadAndInit();
60
60
  await configManager.resolveAndFreeze();
@@ -72,7 +72,7 @@ export class FreyaKernel {
72
72
  this.connectionManager = new FreyaConnectionManager(ctx.eventBus, ctx.logger);
73
73
  const configToolbox = new ConfigToolbox(configManager, ctx);
74
74
  const sessionToolbox = new SessionToolbox(this.sessionManager);
75
- const metaToolbox = new FreyaMetaToolbox(this.sessionManager, toolRegistry);
75
+ const metaToolbox = new FreyaMetaToolbox(this.sessionManager, toolRegistry, skillRegistry);
76
76
  toolRegistry.registerToolbox(configToolbox);
77
77
  toolRegistry.registerToolbox(sessionToolbox);
78
78
  toolRegistry.registerToolbox(metaToolbox);
@@ -4,14 +4,24 @@ export interface FreyaSkill {
4
4
  name: string;
5
5
  description: string;
6
6
  content: string;
7
+ enabled: boolean;
8
+ source: 'builtin' | 'runtime';
7
9
  }
8
- /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能 */
10
+ /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能并管理软开关状态 */
9
11
  export declare class FreyaSkillRegistry {
10
12
  private skills;
13
+ private context?;
11
14
  loadSkills(context: FreyaContext): Promise<void>;
12
15
  /** 从指定目录加载技能到内存注册表中 */
13
16
  private loadSkillsFromDirectory;
14
- getSkills(): Map<string, FreyaSkill>;
17
+ /** 获取技能列表(默认只返回启用状态的技能) */
18
+ getSkills(onlyEnabled?: boolean): Map<string, FreyaSkill>;
19
+ /** 获取全量技能数组(供管理控制台使用) */
20
+ getAllSkills(): FreyaSkill[];
21
+ /** 切换技能的启用/禁用状态并持久化 */
22
+ toggleSkill(skillId: string, enabled: boolean): Promise<string>;
23
+ /** 持久化当前所有技能启停状态至 skills.json */
24
+ private persistSkillsConfig;
15
25
  get(id: string): FreyaSkill | undefined;
16
26
  has(id: string): boolean;
17
27
  /** 解析技能文件的 YAML Frontmatter */
@@ -1,24 +1,57 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { APP_ROOT, PROJECT_ROOT } from '../utils/paths.js';
4
- /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能 */
4
+ /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能并管理软开关状态 */
5
5
  export class FreyaSkillRegistry {
6
6
  skills = new Map();
7
+ context;
7
8
  async loadSkills(context) {
9
+ this.context = context;
8
10
  const runtimeSkillsDir = path.join(PROJECT_ROOT, 'skills');
9
11
  const defaultSkillsDir = path.join(APP_ROOT, 'skills');
12
+ const configSkillsPath = path.join(PROJECT_ROOT, 'config', 'skills.json');
10
13
  try {
11
14
  await fs.mkdir(runtimeSkillsDir, { recursive: true });
12
- await this.loadSkillsFromDirectory(defaultSkillsDir, context);
13
- await this.loadSkillsFromDirectory(runtimeSkillsDir, context);
14
- context.logger.info(`动态技能扫描完成。共加载了 ${this.skills.size} 个物理技能。`);
15
+ await this.loadSkillsFromDirectory(defaultSkillsDir, 'builtin', context);
16
+ await this.loadSkillsFromDirectory(runtimeSkillsDir, 'runtime', context);
17
+ let configList = [];
18
+ try {
19
+ const raw = await fs.readFile(configSkillsPath, 'utf-8');
20
+ const parsed = JSON.parse(raw);
21
+ if (Array.isArray(parsed)) {
22
+ configList = parsed;
23
+ }
24
+ }
25
+ catch {
26
+ configList = [];
27
+ }
28
+ const configMap = new Map();
29
+ for (const item of configList) {
30
+ if (item && typeof item.id === 'string' && typeof item.enabled === 'boolean') {
31
+ configMap.set(item.id, item.enabled);
32
+ }
33
+ }
34
+ let configChanged = false;
35
+ for (const skill of this.skills.values()) {
36
+ if (configMap.has(skill.id)) {
37
+ skill.enabled = configMap.get(skill.id);
38
+ }
39
+ else {
40
+ configChanged = true;
41
+ }
42
+ }
43
+ if (configChanged || configList.length !== this.skills.size) {
44
+ await this.persistSkillsConfig(configSkillsPath);
45
+ }
46
+ const enabledCount = Array.from(this.skills.values()).filter((s) => s.enabled).length;
47
+ context.logger.info(`动态技能扫描完成。共加载 ${this.skills.size} 个物理技能 (已启用: ${enabledCount})。`);
15
48
  }
16
49
  catch (err) {
17
50
  context.logger.error('扫描物理技能 skills 目录遭遇故障:', err);
18
51
  }
19
52
  }
20
53
  /** 从指定目录加载技能到内存注册表中 */
21
- async loadSkillsFromDirectory(dirPath, context) {
54
+ async loadSkillsFromDirectory(dirPath, source, context) {
22
55
  try {
23
56
  const files = await fs.readdir(dirPath);
24
57
  for (const file of files) {
@@ -26,11 +59,16 @@ export class FreyaSkillRegistry {
26
59
  const rawContent = await fs.readFile(path.join(dirPath, file), 'utf-8');
27
60
  const { metadata, content } = this.parseFrontmatter(rawContent);
28
61
  if (metadata.id) {
62
+ const defaultEnabled = metadata.defaultEnabled !== undefined
63
+ ? metadata.defaultEnabled !== 'false'
64
+ : source === 'builtin';
29
65
  this.skills.set(metadata.id, {
30
66
  id: metadata.id,
31
67
  name: metadata.name || file.replace('.md', ''),
32
68
  description: metadata.description || '',
33
- content: content.trim()
69
+ content: content.trim(),
70
+ enabled: defaultEnabled,
71
+ source
34
72
  });
35
73
  }
36
74
  }
@@ -40,8 +78,51 @@ export class FreyaSkillRegistry {
40
78
  context.logger.warn(`扫描技能目录失败: ${dirPath}`, err);
41
79
  }
42
80
  }
43
- getSkills() {
44
- return this.skills;
81
+ /** 获取技能列表(默认只返回启用状态的技能) */
82
+ getSkills(onlyEnabled = true) {
83
+ if (!onlyEnabled) {
84
+ return this.skills;
85
+ }
86
+ const filtered = new Map();
87
+ for (const [id, skill] of this.skills.entries()) {
88
+ if (skill.enabled) {
89
+ filtered.set(id, skill);
90
+ }
91
+ }
92
+ return filtered;
93
+ }
94
+ /** 获取全量技能数组(供管理控制台使用) */
95
+ getAllSkills() {
96
+ return Array.from(this.skills.values());
97
+ }
98
+ /** 切换技能的启用/禁用状态并持久化 */
99
+ async toggleSkill(skillId, enabled) {
100
+ const skill = this.skills.get(skillId);
101
+ if (!skill) {
102
+ return `❌ 未找到 ID 为 "${skillId}" 的技能,请检查名称是否正确。`;
103
+ }
104
+ if (skill.enabled === enabled) {
105
+ return `ℹ️ 技能 "${skill.name || skillId}" 状态已是 ${enabled ? '启用' : '禁用'}。`;
106
+ }
107
+ skill.enabled = enabled;
108
+ const configSkillsPath = path.join(PROJECT_ROOT, 'config', 'skills.json');
109
+ try {
110
+ await this.persistSkillsConfig(configSkillsPath);
111
+ this.context?.logger.info(`技能 "${skill.name || skillId}" 已切换为: ${enabled ? '启用' : '禁用'}`);
112
+ return `✅ 技能 "${skill.name || skillId}" 已成功${enabled ? '启用' : '禁用'}。`;
113
+ }
114
+ catch (err) {
115
+ return `❌ 技能状态变更成功,但写入 skills.json 失败: ${err.message}`;
116
+ }
117
+ }
118
+ /** 持久化当前所有技能启停状态至 skills.json */
119
+ async persistSkillsConfig(configSkillsPath) {
120
+ await fs.mkdir(path.dirname(configSkillsPath), { recursive: true });
121
+ const payload = Array.from(this.skills.values()).map((s) => ({
122
+ id: s.id,
123
+ enabled: s.enabled
124
+ }));
125
+ await fs.writeFile(configSkillsPath, JSON.stringify(payload, null, 2) + '\n', 'utf-8');
45
126
  }
46
127
  get(id) {
47
128
  return this.skills.get(id);
@@ -1,10 +1,12 @@
1
1
  import type { FreyaTool, FreyaToolbox } from '@eoasmxd/freya-sdk';
2
2
  import type { FreyaSessionManager } from '../../session/session-manager.js';
3
3
  import type { FreyaToolRegistry } from '../tool-registry.js';
4
+ import type { FreyaSkillRegistry } from '../../skill/skill-registry.js';
4
5
  export declare class FreyaMetaToolbox implements FreyaToolbox {
5
6
  private sessionManager;
6
7
  private toolRegistry?;
7
- constructor(sessionManager: FreyaSessionManager, toolRegistry?: FreyaToolRegistry | undefined);
8
+ private skillRegistry?;
9
+ constructor(sessionManager: FreyaSessionManager, toolRegistry?: FreyaToolRegistry | undefined, skillRegistry?: FreyaSkillRegistry | undefined);
8
10
  getId(): string;
9
11
  getInstructionPrompt(): string;
10
12
  getTools(): FreyaTool[];
@@ -1,9 +1,11 @@
1
1
  export class FreyaMetaToolbox {
2
2
  sessionManager;
3
3
  toolRegistry;
4
- constructor(sessionManager, toolRegistry) {
4
+ skillRegistry;
5
+ constructor(sessionManager, toolRegistry, skillRegistry) {
5
6
  this.sessionManager = sessionManager;
6
7
  this.toolRegistry = toolRegistry;
8
+ this.skillRegistry = skillRegistry;
7
9
  }
8
10
  getId() {
9
11
  return 'meta';
@@ -99,6 +101,12 @@ export class FreyaMetaToolbox {
99
101
  if (!sessionId) {
100
102
  return '❌ 错误:无法从执行上下文中提取当前会话ID。';
101
103
  }
104
+ if (this.skillRegistry) {
105
+ const skill = this.skillRegistry.get(skillId);
106
+ if (!skill || !skill.enabled) {
107
+ return `❌ 错误:技能 [${skillId}] 不存在或已被系统管理员禁用,无法切入该模式。`;
108
+ }
109
+ }
102
110
  await this.sessionManager.updateSession(sessionId, { activeSkillId: skillId });
103
111
  return `已成功切入 [${skillId}] 技能特长工作模式。`;
104
112
  }
@@ -1,22 +1,27 @@
1
- import type { FreyaTool, FreyaToolbox } from '@eoasmxd/freya-sdk';
1
+ import type { FreyaContext, FreyaTool, FreyaToolbox } from '@eoasmxd/freya-sdk';
2
2
  import type { FreyaPromptRegistry } from '../prompt/prompt-registry.js';
3
3
  /**
4
4
  * 内核工具注册表。
5
- * 统一聚合内置工具箱与来自插件体系的外部工具/工具箱。
5
+ * 统一聚合内置工具箱与来自插件体系的外部工具箱。
6
6
  */
7
7
  export declare class FreyaToolRegistry {
8
+ private context?;
8
9
  private toolboxes;
9
- /** 注册一个工具箱(内置或外部插件)。 */
10
+ constructor(context?: FreyaContext | undefined);
11
+ setContext(context: FreyaContext): void;
12
+ /** 判断指定工具箱当前是否已注册且处于可用启用状态 */
13
+ isToolboxEnabled(toolboxId: string): boolean;
14
+ /** 注册工具箱 */
10
15
  registerToolbox(toolbox: FreyaToolbox): void;
11
16
  /** 注销工具箱 */
12
17
  unregisterToolbox(id: string): void;
13
- /** 获取指定 ID 的工具箱中的所有原子工具。 */
18
+ /** 获取指定 ID 的工具箱中的所有原子工具 */
14
19
  getToolsInBox(id: string): FreyaTool[];
15
- /** 聚合所有来源的工具,返回完整工具字典。 */
20
+ /** 聚合所有来源的已启用工具 */
16
21
  getAllTools(): Map<string, FreyaTool>;
17
- /** 根据当前会话已激活的工具箱列表,动态过滤获取所需的工具字典 */
22
+ /** 根据当前会话已激活的工具箱列表,过滤获取所需的工具字典 */
18
23
  getFilteredTools(activeToolboxIds: string[]): Map<string, FreyaTool>;
19
- /** 聚合所有来源的工具提示词引导说明,延迟解析 key → 内容 */
24
+ /** 聚合所有已启用的工具箱提示词引导说明 */
20
25
  getToolInstructions(promptRegistry: FreyaPromptRegistry): string[];
21
26
  getRegisteredToolboxIds(): string[];
22
27
  }
@@ -1,10 +1,32 @@
1
1
  /**
2
2
  * 内核工具注册表。
3
- * 统一聚合内置工具箱与来自插件体系的外部工具/工具箱。
3
+ * 统一聚合内置工具箱与来自插件体系的外部工具箱。
4
4
  */
5
5
  export class FreyaToolRegistry {
6
+ context;
6
7
  toolboxes = [];
7
- /** 注册一个工具箱(内置或外部插件)。 */
8
+ constructor(context) {
9
+ this.context = context;
10
+ }
11
+ setContext(context) {
12
+ this.context = context;
13
+ }
14
+ /** 判断指定工具箱当前是否已注册且处于可用启用状态 */
15
+ isToolboxEnabled(toolboxId) {
16
+ if (toolboxId === 'meta') {
17
+ return true;
18
+ }
19
+ const isRegistered = this.toolboxes.some((tb) => tb.getId() === toolboxId);
20
+ if (!isRegistered) {
21
+ return false;
22
+ }
23
+ const toolsConfig = this.context?.config?.tools?.builtin;
24
+ if (toolsConfig && typeof toolsConfig[toolboxId]?.enabled === 'boolean') {
25
+ return toolsConfig[toolboxId].enabled;
26
+ }
27
+ return true;
28
+ }
29
+ /** 注册工具箱 */
8
30
  registerToolbox(toolbox) {
9
31
  const newId = toolbox.getId();
10
32
  const existingIndex = this.toolboxes.findIndex((tb) => tb.getId() === newId || tb === toolbox);
@@ -19,27 +41,36 @@ export class FreyaToolRegistry {
19
41
  unregisterToolbox(id) {
20
42
  this.toolboxes = this.toolboxes.filter((tb) => tb.getId() !== id);
21
43
  }
22
- /** 获取指定 ID 的工具箱中的所有原子工具。 */
44
+ /** 获取指定 ID 的工具箱中的所有原子工具 */
23
45
  getToolsInBox(id) {
46
+ if (!this.isToolboxEnabled(id)) {
47
+ return [];
48
+ }
24
49
  const tb = this.toolboxes.find((t) => t.getId() === id);
25
50
  return tb ? tb.getTools() : [];
26
51
  }
27
- /** 聚合所有来源的工具,返回完整工具字典。 */
52
+ /** 聚合所有来源的已启用工具 */
28
53
  getAllTools() {
29
54
  const tools = new Map();
30
55
  for (const toolbox of this.toolboxes) {
56
+ if (!this.isToolboxEnabled(toolbox.getId())) {
57
+ continue;
58
+ }
31
59
  for (const tool of toolbox.getTools()) {
32
60
  tools.set(tool.getDefinition().name, tool);
33
61
  }
34
62
  }
35
63
  return tools;
36
64
  }
37
- /** 根据当前会话已激活的工具箱列表,动态过滤获取所需的工具字典 */
65
+ /** 根据当前会话已激活的工具箱列表,过滤获取所需的工具字典 */
38
66
  getFilteredTools(activeToolboxIds) {
39
67
  const activeSet = new Set(activeToolboxIds || []);
40
68
  const tools = new Map();
41
69
  for (const toolbox of this.toolboxes) {
42
70
  const toolboxId = toolbox.getId();
71
+ if (!this.isToolboxEnabled(toolboxId)) {
72
+ continue;
73
+ }
43
74
  if (toolboxId === 'meta' || activeSet.has(toolboxId)) {
44
75
  for (const tool of toolbox.getTools()) {
45
76
  tools.set(tool.getDefinition().name, tool);
@@ -48,22 +79,27 @@ export class FreyaToolRegistry {
48
79
  }
49
80
  return tools;
50
81
  }
51
- /** 聚合所有来源的工具提示词引导说明,延迟解析 key → 内容 */
82
+ /** 聚合所有已启用的工具箱提示词引导说明 */
52
83
  getToolInstructions(promptRegistry) {
53
84
  const instructions = [];
54
85
  for (const toolbox of this.toolboxes) {
86
+ const toolboxId = toolbox.getId();
87
+ if (!this.isToolboxEnabled(toolboxId)) {
88
+ continue;
89
+ }
55
90
  const key = toolbox.getInstructionPrompt?.();
56
91
  if (!key)
57
92
  continue;
58
93
  const resolved = promptRegistry.get(key);
59
94
  if (resolved) {
60
- const toolboxId = toolbox.getId();
61
95
  instructions.push(`### 工具箱能力说明 [激活ID: "${toolboxId}"]\n${resolved}`);
62
96
  }
63
97
  }
64
98
  return instructions;
65
99
  }
66
100
  getRegisteredToolboxIds() {
67
- return this.toolboxes.map((tb) => tb.getId());
101
+ return this.toolboxes
102
+ .map((tb) => tb.getId())
103
+ .filter((id) => this.isToolboxEnabled(id));
68
104
  }
69
105
  }
@@ -177,6 +177,24 @@ export class FreyaConfigApi {
177
177
  res.end(JSON.stringify({ success: !msg.startsWith('❌'), message: msg }));
178
178
  return true;
179
179
  }
180
+ if (pathname === '/api/config/skills' && req.method === 'GET') {
181
+ const skills = this.configManager.listSkills();
182
+ res.writeHead(200, this.headers);
183
+ res.end(JSON.stringify({ success: true, data: skills }));
184
+ return true;
185
+ }
186
+ if (pathname === '/api/config/skills/toggle' && req.method === 'POST') {
187
+ const { skillId, enabled } = await this.getBody(req);
188
+ if (!skillId) {
189
+ res.writeHead(200, this.headers);
190
+ res.end(JSON.stringify({ success: false, error: '缺少必要参数: skillId' }));
191
+ return true;
192
+ }
193
+ const msg = await this.configManager.toggleSkill(skillId, enabled);
194
+ res.writeHead(200, this.headers);
195
+ res.end(JSON.stringify({ success: !msg.startsWith('❌'), message: msg }));
196
+ return true;
197
+ }
180
198
  const promptMatch = pathname.match(/^\/api\/config\/prompts\/([^/]+)$/);
181
199
  if (promptMatch && req.method === 'GET') {
182
200
  const promptName = decodeURIComponent(promptMatch[1]);
package/core/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eoasmxd/freya-core",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -16,7 +16,7 @@
16
16
  "clean": "rm -rf dist"
17
17
  },
18
18
  "dependencies": {
19
- "@eoasmxd/freya-sdk": "^0.4.0",
19
+ "@eoasmxd/freya-sdk": "^0.4.2",
20
20
  "ws": "^8.18.0"
21
21
  },
22
22
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eoasmxd/freya",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Freya - 微内核智能体系统",
@@ -35,9 +35,10 @@
35
35
  "src"
36
36
  ],
37
37
  "dependencies": {
38
- "@eoasmxd/freya-sdk": "^0.4.0",
38
+ "@eoasmxd/freya-sdk": "^0.4.2",
39
39
  "ws": "^8.18.0",
40
- "qrcode-terminal": "^0.12.0"
40
+ "mysql2": "^3.11.0",
41
+ "qrcode": "^1.5.4"
41
42
  },
42
43
  "scripts": {
43
44
  "start": "node freya.js",