@eoasmxd/freya 0.3.0 → 0.4.1

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 (114) hide show
  1. package/README.md +9 -3
  2. package/core/dist/command/commands/skill-commands.js +5 -4
  3. package/core/dist/config/config-manager.d.ts +5 -1
  4. package/core/dist/config/config-manager.js +13 -1
  5. package/core/dist/kernel.js +2 -2
  6. package/core/dist/skill/skill-registry.d.ts +12 -2
  7. package/core/dist/skill/skill-registry.js +89 -8
  8. package/core/dist/tools/meta/index.d.ts +3 -1
  9. package/core/dist/tools/meta/index.js +9 -1
  10. package/core/dist/web/config-api.js +18 -0
  11. package/core/package.json +2 -2
  12. package/doc/_index.md +20 -7
  13. package/doc/getting-started.md +1 -1
  14. package/doc/installation-guide.md +2 -2
  15. package/doc/{architecture-design.md → specifications/architecture-design.md} +7 -4
  16. package/doc/{config-spec.md → specifications/config-spec.md} +1 -1
  17. package/doc/{llm-interface-params.md → specifications/llm-interface-params.md} +8 -8
  18. package/doc/{prompt-system.md → specifications/prompt-system.md} +1 -1
  19. package/doc/tutorials/_index.md +96 -0
  20. package/doc/tutorials/part0_basic/0.1_probability_prediction.md +155 -0
  21. package/doc/tutorials/part0_basic/0.2_attention_and_context.md +145 -0
  22. package/doc/tutorials/part0_basic/0.3_generation_parameters.md +132 -0
  23. package/doc/tutorials/part0_basic/0.4_debugging_token.md +99 -0
  24. package/doc/tutorials/part0_basic/1.1_stateless_and_history.md +143 -0
  25. package/doc/tutorials/part0_basic/1.2_chat_data_structure.md +179 -0
  26. package/doc/tutorials/part0_basic/1.3_system_user_assistant.md +147 -0
  27. package/doc/tutorials/part0_basic/1.4_freya_model_proxy.md +203 -0
  28. package/doc/tutorials/part0_basic/_index.md +28 -0
  29. package/doc/tutorials/part1_react/2.1_agency_vs_chatbot.md +127 -0
  30. package/doc/tutorials/part1_react/2.2_react_mind_model.md +144 -0
  31. package/doc/tutorials/part1_react/2.3_freya_agent_executor.md +233 -0
  32. package/doc/tutorials/part1_react/2.4_debugging_loop_deadlock.md +160 -0
  33. package/doc/tutorials/part1_react/3.1_hardcoded_prompt_pain.md +104 -0
  34. package/doc/tutorials/part1_react/3.2_decoupled_architecture.md +138 -0
  35. package/doc/tutorials/part1_react/3.3_freya_dual_read_probe.md +152 -0
  36. package/doc/tutorials/part1_react/3.4_debugging_composition_placeholder.md +97 -0
  37. package/doc/tutorials/part1_react/_index.md +28 -0
  38. package/doc/tutorials/part2_tools/4.1_json_schema_mapping.md +119 -0
  39. package/doc/tutorials/part2_tools/4.2_tool_call_raw_packet.md +105 -0
  40. package/doc/tutorials/part2_tools/4.3_freya_tool_execution.md +145 -0
  41. package/doc/tutorials/part2_tools/4.4_debugging_observation_fix.md +154 -0
  42. package/doc/tutorials/part2_tools/5.1_observation_injection.md +131 -0
  43. package/doc/tutorials/part2_tools/5.2_openai_vs_gemini_protocol.md +125 -0
  44. package/doc/tutorials/part2_tools/5.3_freya_llm_proxy_mapping.md +162 -0
  45. package/doc/tutorials/part2_tools/5.4_debugging_parallel_call_chaos.md +135 -0
  46. package/doc/tutorials/part2_tools/_index.md +28 -0
  47. package/doc/tutorials/part3_memory/6.1_session_state_lifecycle.md +143 -0
  48. package/doc/tutorials/part3_memory/6.2_physical_sandbox_separation.md +108 -0
  49. package/doc/tutorials/part3_memory/6.3_freya_session_storage.md +139 -0
  50. package/doc/tutorials/part3_memory/6.4_debugging_session_concurrency.md +161 -0
  51. package/doc/tutorials/part3_memory/7.1_context_overflow_loss.md +109 -0
  52. package/doc/tutorials/part3_memory/7.2_sliding_window_vs_summary.md +85 -0
  53. package/doc/tutorials/part3_memory/7.3_freya_compactor_impl.md +142 -0
  54. package/doc/tutorials/part3_memory/7.4_debugging_summarize_deadlock.md +160 -0
  55. package/doc/tutorials/part3_memory/_index.md +28 -0
  56. package/doc/tutorials/part4_streaming/8.1_sse_protocol_basics.md +119 -0
  57. package/doc/tutorials/part4_streaming/8.2_hiding_thoughts_in_stream.md +132 -0
  58. package/doc/tutorials/part4_streaming/8.3_freya_event_bus.md +104 -0
  59. package/doc/tutorials/part4_streaming/8.4_debugging_stream_decoder.md +158 -0
  60. package/doc/tutorials/part4_streaming/9.1_abort_signal_braking.md +162 -0
  61. package/doc/tutorials/part4_streaming/9.2_async_event_channels.md +142 -0
  62. package/doc/tutorials/part4_streaming/9.3_freya_abort_billing.md +122 -0
  63. package/doc/tutorials/part4_streaming/9.4_debugging_abort_lock_deadlock.md +187 -0
  64. package/doc/tutorials/part4_streaming/_index.md +28 -0
  65. package/doc/tutorials/part5_plugins/10.1_microkernel_decoupling.md +142 -0
  66. package/doc/tutorials/part5_plugins/10.2_plugin_metadata_security.md +125 -0
  67. package/doc/tutorials/part5_plugins/10.3_channel_plugin_development.md +149 -0
  68. package/doc/tutorials/part5_plugins/10.4_debugging_channel_reconnection.md +160 -0
  69. package/doc/tutorials/part5_plugins/_index.md +21 -0
  70. package/doc/tutorials/part6_advanced/11.1_react_model_flaws.md +111 -0
  71. package/doc/tutorials/part6_advanced/11.2_reflexion_mind_model.md +103 -0
  72. package/doc/tutorials/part6_advanced/11.3_reflexion_hands_on.md +182 -0
  73. package/doc/tutorials/part6_advanced/11.4_debugging_reflexion_convergence.md +108 -0
  74. package/doc/tutorials/part6_advanced/12.1_single_agent_limits.md +100 -0
  75. package/doc/tutorials/part6_advanced/12.2_multi_agent_patterns.md +121 -0
  76. package/doc/tutorials/part6_advanced/12.3_freya_multi_agent_routing.md +143 -0
  77. package/doc/tutorials/part6_advanced/12.4_multi_agent_hands_on.md +176 -0
  78. package/doc/tutorials/part6_advanced/_index.md +28 -0
  79. package/doc/tutorials/preface.md +30 -0
  80. package/package.json +3 -2
  81. package/plugins/plugin-gemini/package.json +1 -1
  82. package/plugins/plugin-openai/package.json +1 -1
  83. package/plugins/plugin-telegram-channel/package.json +1 -1
  84. package/plugins/plugin-tool-fs/package.json +1 -1
  85. package/plugins/plugin-tool-memory/package.json +1 -1
  86. package/plugins/plugin-tool-mysql/config/prompts/plugin.prompt.mysql.md +9 -0
  87. package/plugins/plugin-tool-mysql/config/prompts/plugin.prompt.mysql.select.audit.md +26 -0
  88. package/plugins/plugin-tool-mysql/dist/audit.d.ts +13 -0
  89. package/plugins/plugin-tool-mysql/dist/audit.js +87 -0
  90. package/plugins/plugin-tool-mysql/dist/index.d.ts +14 -0
  91. package/plugins/plugin-tool-mysql/dist/index.js +34 -0
  92. package/plugins/plugin-tool-mysql/dist/pool-manager.d.ts +32 -0
  93. package/plugins/plugin-tool-mysql/dist/pool-manager.js +113 -0
  94. package/plugins/plugin-tool-mysql/dist/tools.d.ts +11 -0
  95. package/plugins/plugin-tool-mysql/dist/tools.js +88 -0
  96. package/plugins/plugin-tool-mysql/package.json +33 -0
  97. package/plugins/plugin-tool-mysql/schema.json +83 -0
  98. package/plugins/plugin-tool-web/package.json +1 -1
  99. package/plugins/plugin-wecom-channel/package.json +1 -1
  100. package/plugins/plugin-weixin-channel/package.json +1 -1
  101. package/src/packages/core/src/command/commands/skill-commands.ts +6 -5
  102. package/src/packages/core/src/config/config-manager.ts +15 -1
  103. package/src/packages/core/src/kernel.ts +3 -2
  104. package/src/packages/core/src/skill/skill-registry.ts +100 -8
  105. package/src/packages/core/src/tools/meta/index.ts +9 -1
  106. package/src/packages/core/src/web/config-api.ts +20 -0
  107. package/src/packages/ui/src/features/config/ConfigModal.tsx +13 -1
  108. package/src/packages/ui/src/features/config/panels/SkillConfigPanel.tsx +137 -0
  109. package/src/plugins/plugin-tool-mysql/src/audit.ts +103 -0
  110. package/src/plugins/plugin-tool-mysql/src/index.ts +44 -0
  111. package/src/plugins/plugin-tool-mysql/src/pool-manager.ts +132 -0
  112. package/src/plugins/plugin-tool-mysql/src/tools.ts +100 -0
  113. package/ui/assets/{index-Be0cAgdB.js → index-BqPQMflk.js} +14 -14
  114. package/ui/index.html +1 -1
package/README.md CHANGED
@@ -59,11 +59,17 @@ pnpm start
59
59
 
60
60
  为了便于开发者深入了解 Freya 的底座原理与扩展机制,系统在 [doc/](doc/_index.md) 物理目录下提供了完整的技术文档库:
61
61
 
62
- * 🏗️ **[架构设计说明](doc/architecture-design.md)**:包含 Monorepo 物理结构、核心 ReAct 调用链路图、核心组件职责及 EventBus 异步通信机制。
63
- * ⚙️ **[配置与数据隔离规范](doc/config-spec.md)**:介绍 `~/.freya/` 运行时目录结构、配置/数据物理隔离及 Schema 动态合并策略。
64
- * 📝 **[提示词管理系统](doc/prompt-system.md)**:介绍 6 大维度提示词管理方案、动态 Prompt Composer 拼装结构与 Dual-Read 探针机制。
62
+ ### 🛠️ 技术规范与指南
63
+
65
64
  * 🚀 **[快速使用指引](doc/getting-started.md)**:图形化 LLM 提供商配置、插件开启控制与会话快捷指令。
66
65
  * 🛠️ **[安装与构建运行](doc/installation-guide.md)**:分步说明环境准备、安装、编译与控制台开发模式。
66
+ * 🏗️ **[架构设计说明](doc/specifications/architecture-design.md)**:包含 Monorepo 物理结构、核心 ReAct 调用链路图、核心组件职责及 EventBus 异步通信机制。
67
+ * ⚙️ **[配置与数据隔离规范](doc/specifications/config-spec.md)**:介绍 `~/.freya/` 运行时目录结构、配置/数据物理隔离及 Schema 动态合并策略。
68
+ * 📝 **[提示词管理系统](doc/specifications/prompt-system.md)**:介绍 6 大维度提示词管理方案、动态 Prompt Composer 拼装结构与 Dual-Read 探针机制。
69
+
70
+ ### 🎓 白盒开发教程
71
+
72
+ * 👉 **[智能体开发实战教程](doc/tutorials/_index.md)**:白盒解剖智能体底座,包含 13 个章节硬核教程,从 Next-Token Prediction 到底层 ReAct 循环、多轮会话以及多智能体协同协作等原理解密。
67
73
 
68
74
  ---
69
75
 
@@ -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)) {
@@ -54,7 +54,7 @@ export class FreyaKernel {
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
  }
@@ -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.3.0",
3
+ "version": "0.4.1",
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.3.0",
19
+ "@eoasmxd/freya-sdk": "^0.4.1",
20
20
  "ws": "^8.18.0"
21
21
  },
22
22
  "devDependencies": {
package/doc/_index.md CHANGED
@@ -10,17 +10,30 @@ description: "Freya 项目技术文档与开发使用指南。"
10
10
 
11
11
  ## 📖 文档导航
12
12
 
13
- - 🏗️ **[Freya 架构设计说明](architecture-design.md)**
13
+ ### 🚀 快速开始
14
+
15
+ - 🧭 **[快速使用指引](getting-started.md)**
16
+ 提供系统首次启动配置、插件管理与日常操作快捷指令。
17
+
18
+ - 🛠️ **[安装与构建运行](installation-guide.md)**
19
+ 提供开发环境要求、本地依赖安装、项目编译打包与服务运行调试指南。
20
+
21
+ ### 🏗️ 技术规范与设计参考
22
+
23
+ - 🧱 **[Freya 架构设计说明](specifications/architecture-design.md)**
14
24
  介绍系统的物理模块划分、核心组件职责分工、调用执行链路与异步通信机制。
15
25
 
16
- - ⚙️ **[配置与数据隔离规范](config-spec.md)**
26
+ - ⚙️ **[配置与数据隔离规范](specifications/config-spec.md)**
17
27
  介绍系统的运行时目录结构、配置与数据的隔离设计及初始化合并规范。
18
28
 
19
- - 📝 **[提示词管理系统说明](prompt-system.md)**
29
+ - 📝 **[提示词管理系统说明](specifications/prompt-system.md)**
20
30
  介绍多维度提示词管理方案、运行时 Prompt 拼装结构与动态加载覆盖机制。
21
31
 
22
- - 🚀 **[快速使用指引](getting-started.md)**
23
- 提供系统首次启动配置、插件管理与日常操作快捷指令。
32
+ - 🎛️ **[大模型接口参数规范](specifications/llm-interface-params.md)**
33
+ 介绍大语言模型统一代理层的配置参数与多厂商协议适配规则。
34
+
35
+ ### 🎓 开发实战教程
36
+
37
+ - 📚 **[智能体通用原理与开发实战](tutorials/_index.md)**
38
+ 完全以智能体通用底层原理、通信协议与架构机制为主线的白盒实战开发教程。
24
39
 
25
- - 🛠️ **[安装与构建运行](installation-guide.md)**
26
- 提供开发环境要求、本地依赖安装、项目编译打包与服务运行调试指南。
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: "Freya 快速使用指引"
2
+ title: "快速使用指引"
3
3
  weight: 30
4
4
  description: "介绍如何配置大模型 API Key、开启插件功能以及在控制台使用快捷指令。"
5
5
  ---
@@ -1,10 +1,10 @@
1
1
  ---
2
- title: "Freya 安装与维护完全指南"
2
+ title: "安装与构建运行"
3
3
  weight: 20
4
4
  description: "提供环境准备、NPM 全局安装与源码构建运行的完整安装、日常启动、服务停止及手动版本更新指南。"
5
5
  ---
6
6
 
7
- # Freya 安装与维护完全指南
7
+ # Freya 安装与构建运行
8
8
 
9
9
  ## 1. 环境准备
10
10
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: "Freya 架构设计说明"
2
+ title: "架构设计说明"
3
3
  weight: 10
4
4
  description: "说明项目目录结构、~/.freya 运行时存放规则、插件依赖界限、核心 ReAct 调用链路与 EventBus 异步解耦模式。"
5
5
  ---
@@ -41,15 +41,18 @@ freya/
41
41
  │ ├── sdk/ # 插件开发标准 SDK
42
42
  │ │ ├── src/
43
43
  │ │ │ ├── index.ts # 模块入口与集中导出
44
- │ │ │ └── types.ts # 统一的接口定义
44
+ │ │ │ └── types/ # 统一的接口定义目录
45
45
  │ │ └── package.json
46
46
  │ │
47
47
  │ └── ui/ # 独立的前端 Web 交互页面
48
48
  │ └── package.json
49
49
 
50
50
  ├── plugins/ # 插件根目录
51
+ │ ├── plugin-gemini/ # Gemini 大模型插件
51
52
  │ ├── plugin-openai/ # 对接 OpenAI 标准 API 的大模型插件
52
53
  │ ├── plugin-telegram-channel/ # Telegram 频道插件
54
+ │ ├── plugin-wecom-channel/ # 企业微信频道插件
55
+ │ ├── plugin-weixin-channel/ # 微信频道插件
53
56
  │ ├── plugin-tool-fs/ # 文件系统工具插件
54
57
  │ ├── plugin-tool-memory/ # 记忆工具插件
55
58
  │ └── plugin-tool-web/ # 网页工具插件
@@ -115,8 +118,8 @@ graph TD
115
118
 
116
119
  底座组件之间跨边界异步通信统一通过 **`EventBus` (事件总线)** 异步事件驱动:
117
120
 
118
- * **出站路由**:`Agent` 推理产生的流式 Delta 或最终文本不会被直接投递给 `Channel`。底座仅需广播 `'connection:reply'` / `'connection:reply:delta'` 事件;各个 `Channel` 插件自行订阅事件,读取对应的 `channelId` 并各自向用户的物理长连接发送。
119
- * **多方协同与计费解耦**:当大模型输出产生 `'token:consumed'` 事件时,计费服务(Billing)通过订阅该事件核算费用并广播计费结果,底座连接层再通过 `'connection:event'` 向前端 WebSocket 实时推送 Token 账单与费用通知。
121
+ * **出站路由**:`Agent` 推理产生的流式 Delta 或最终文本不会被直接投递给 `Channel`。底座仅需广播 `'connection:reply'` / `'connection:reply:delta'` 事件;各个 `Channel` 插件自行订阅事件,读取对应的 `connectionId`(连接 ID)过滤并各自向用户的物理长连接发送。
122
+ * **多方协同与计费解耦**:当大模型输出产生 `'token:consumed'` 事件时,计费服务(Billing)通过订阅该事件核算费用并触发 `'billing:session:add'`。会话管理器(SessionManager)监听该事件累加更新会话数据后广播 `'session:billing:update'`,最终底座连接层捕获此更新并通过 `'connection:event'` 向前端 WebSocket 实时推送 `server:billing` 事件。
120
123
 
121
124
 
122
125
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: "Freya 配置与数据隔离规范"
2
+ title: "配置与数据隔离规范"
3
3
  weight: 15
4
4
  description: "介绍 ~/.freya/ 运行时目录结构、配置/数据物理隔离及 Schema 动态合并策略。"
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: "Freya LLM 插件调用接口与参数规范"
2
+ title: "LLM 插件调用接口与参数规范"
3
3
  weight: 18
4
4
  description: "定义 Freya 统一的大模型(LLM)插件调用接口参数规范。"
5
5
  ---
@@ -18,7 +18,7 @@ description: "定义 Freya 统一的大模型(LLM)插件调用接口参数
18
18
  async chat(
19
19
  messages: LLMMessage[],
20
20
  tools?: ToolDefinition[],
21
- options?: LLMOptions
21
+ options?: LLMPluginOptions
22
22
  ): Promise<{
23
23
  message: LLMMessage;
24
24
  usage?: LLMTokenUsage;
@@ -31,7 +31,7 @@ async chat(
31
31
 
32
32
  为了屏蔽不同大模型服务商在参数命名上的差异(如 `max_tokens` 与 `maxOutputTokens`),Freya 规定了一套**驼峰命名(CamelCase)**的通用生成参数标准。
33
33
 
34
- 在调用 `llm.chat` 时,应通过 `options.modelParams` 传递这些参数。各插件仅负责从其中提取支持的驼峰属性,并准确转换为服务商 API 要求的格式。
34
+ 在调用 `llm.chat` 时,应通过 `options.modelParams` 传递这些参数。各插件仅负责从其中提取支持的属性,并准确转换为服务商 API 要求的格式。
35
35
 
36
36
  ### 统一参数字段一览
37
37
 
@@ -87,22 +87,22 @@ if (typeof params.maxTokens === 'number' && params.maxTokens > 0) {
87
87
 
88
88
  ---
89
89
 
90
- ## 4. 大模型消息结构与属性规范 (`LLMMessage`)
90
+ ## 5. 大模型消息结构与属性规范 (`LLMMessage`)
91
91
 
92
92
  Freya 内核通过统一的 `LLMMessage` 对象来表示对话历史中的每一个消息节点。为了兼容和抽象不同大模型服务商在多轮工具调用(Function Calling)以及推理校验上的核心流派差异,`LLMMessage` 中的主要属性字段定义及职责如下:
93
93
 
94
- ### 4.1 基础属性
94
+ ### 5.1 基础属性
95
95
  * **`role`** (`'user' | 'assistant' | 'system' | 'tool'`):消息在对话中的角色。
96
96
  * *映射规范*:对于不支持显式 `'tool'` 角色的厂商 API(如 Gemini REST 接口),插件层应当在转换对话历史时将其统一映射为 `"user"` 以符合接口的协议规范。
97
97
  * **`content`** (`string`):消息的文本正文。
98
- * **`attachments`** (`ChannelAttachment[]`):消息附带的多模态附件(如图像等)。
98
+ * **`attachments`** (`FreyaAttachment[]`):消息附带的多模态附件(如图像等)。
99
99
 
100
- ### 4.2 工具调用关联属性(基于 ID 关联流派)
100
+ ### 5.2 工具调用关联属性(基于 ID 关联流派)
101
101
  主要服务于以 OpenAI 协议为代表、在多轮工具调用中通过特定的**唯一 ID** 关联工具调用与工具执行结果的流派:
102
102
  * **`toolCalls`** (`LLMToolCall[]`):大模型在 `'assistant'` 消息中输出的工具调用指令列表。每个 `LLMToolCall` 包含 `id`、`name`(工具名)与 `arguments`(参数)。
103
103
  * **`toolCallId`** (`string`):在 `'tool'` 角色的消息中,用来指定该条工具执行结果所对应的工具调用 `id`。
104
104
 
105
- ### 4.3 推理校验与工具原名属性(基于名称与签名校验流派)
105
+ ### 5.3 推理校验与工具原名属性(基于名称与签名校验流派)
106
106
  主要服务于以 Gemini 协议为代表、在多轮工具调用中通过**工具名称**关联响应,且必须回传推理状态签名的流派:
107
107
  * **`thoughtSignature`** (`string`):由支持 stateless 思考(Reasoning)的模型在响应中输出的加密思考签名。
108
108
  * *插件职责*:解析响应时,将模型返回的签名捕获并挂载在 `message.thoughtSignature` 根属性上;回传历史消息时,按照厂商 API 规定的结构(如 Gemini 要求其作为同级属性嵌套在对应的 `functionCall` Part 元素中并列发送)回传。
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: "Freya 提示词管理系统说明"
2
+ title: "提示词管理系统说明"
3
3
  weight: 16
4
4
  description: "介绍 6 大维度提示词管理方案、动态 Prompt Composer 拼装结构与 Dual-Read 探针机制。"
5
5
  ---