@eoasmxd/freya 0.4.3 → 0.5.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.
Files changed (55) hide show
  1. package/core/dist/config/config-manager.js +3 -3
  2. package/core/dist/config/file-handler.js +7 -7
  3. package/core/dist/context.js +7 -6
  4. package/core/dist/llm/llm-logger.js +2 -2
  5. package/core/dist/logger.js +2 -2
  6. package/core/dist/plugin/plugin-manager.d.ts +1 -1
  7. package/core/dist/plugin/plugin-manager.js +35 -8
  8. package/core/dist/prompt/prompt-manager.js +4 -4
  9. package/core/dist/prompt/prompt-registry.js +4 -4
  10. package/core/dist/session/persistence.js +2 -2
  11. package/core/dist/skill/skill-registry.d.ts +1 -1
  12. package/core/dist/skill/skill-registry.js +18 -8
  13. package/core/dist/utils/paths.d.ts +5 -3
  14. package/core/dist/utils/paths.js +10 -6
  15. package/core/dist/web/web-container.js +5 -5
  16. package/core/package.json +2 -2
  17. package/doc/specifications/config-spec.md +1 -1
  18. package/doc/tutorials/part1_react/3.2_decoupled_architecture.md +1 -1
  19. package/doc/tutorials/part1_react/3.3_freya_dual_read_probe.md +1 -1
  20. package/doc/tutorials/part3_memory/6.2_physical_sandbox_separation.md +5 -5
  21. package/doc/tutorials/part5_plugins/10.1_microkernel_decoupling.md +5 -4
  22. package/freya.js +14 -2
  23. package/package.json +2 -2
  24. package/plugins/plugin-gemini/package.json +1 -1
  25. package/plugins/plugin-openai/package.json +1 -1
  26. package/plugins/plugin-telegram-channel/package.json +1 -1
  27. package/plugins/plugin-tool-fs/package.json +1 -1
  28. package/plugins/plugin-tool-memory/dist/tools.js +2 -2
  29. package/plugins/plugin-tool-memory/package.json +1 -1
  30. package/plugins/plugin-tool-mysql/dist/audit.js +1 -1
  31. package/plugins/plugin-tool-mysql/package.json +1 -1
  32. package/plugins/plugin-tool-web/package.json +1 -1
  33. package/plugins/plugin-wecom-channel/package.json +1 -1
  34. package/plugins/plugin-weixin-channel/package.json +1 -1
  35. package/src/packages/core/src/config/config-manager.ts +3 -3
  36. package/src/packages/core/src/config/file-handler.ts +7 -7
  37. package/src/packages/core/src/context.ts +7 -6
  38. package/src/packages/core/src/llm/llm-logger.ts +2 -2
  39. package/src/packages/core/src/logger.ts +2 -2
  40. package/src/packages/core/src/plugin/plugin-manager.ts +40 -11
  41. package/src/packages/core/src/prompt/prompt-manager.ts +4 -4
  42. package/src/packages/core/src/prompt/prompt-registry.ts +4 -4
  43. package/src/packages/core/src/session/persistence.ts +2 -2
  44. package/src/packages/core/src/skill/skill-registry.ts +24 -10
  45. package/src/packages/core/src/utils/paths.ts +11 -7
  46. package/src/packages/core/src/web/web-container.ts +5 -5
  47. package/src/packages/sdk/src/types/context.ts +4 -2
  48. package/src/packages/ui/src/App.tsx +10 -3
  49. package/src/packages/ui/src/features/config/panels/PluginConfigPanel.tsx +30 -0
  50. package/src/packages/ui/src/features/config/panels/SkillConfigPanel.tsx +17 -5
  51. package/src/plugins/plugin-tool-memory/src/tools.ts +2 -2
  52. package/src/plugins/plugin-tool-mysql/src/audit.ts +1 -1
  53. package/ui/assets/index-CSZmaZbL.js +43 -0
  54. package/ui/index.html +2 -2
  55. package/ui/assets/index-BqPQMflk.js +0 -43
@@ -1,7 +1,7 @@
1
1
  import { FreyaConfigFileHandler } from './file-handler.js';
2
2
  import { FreyaConfigSchemaRegistry } from './schema-registry.js';
3
3
  import path from 'node:path';
4
- import { PROJECT_ROOT } from '../utils/paths.js';
4
+ import { FREYA_HOME } from '../utils/paths.js';
5
5
  function cleanPathFromError(err) {
6
6
  const rawMessage = err?.message || String(err);
7
7
  const escapedCwd = process.cwd().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -195,11 +195,11 @@ export class FreyaConfigManager {
195
195
  const cloned = JSON.parse(JSON.stringify(config));
196
196
  if (cloned.workspace && typeof cloned.workspace === 'string') {
197
197
  if (!path.isAbsolute(cloned.workspace)) {
198
- cloned.workspace = path.resolve(PROJECT_ROOT, cloned.workspace);
198
+ cloned.workspace = path.resolve(FREYA_HOME, cloned.workspace);
199
199
  }
200
200
  }
201
201
  else {
202
- cloned.workspace = path.join(PROJECT_ROOT, 'workspace');
202
+ cloned.workspace = path.join(FREYA_HOME, 'workspace');
203
203
  }
204
204
  this.context.config = deepFreeze(cloned);
205
205
  }
@@ -1,10 +1,10 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { PROJECT_ROOT } from '../utils/paths.js';
3
+ import { FREYA_HOME } from '../utils/paths.js';
4
4
  /** 配置文件底层 IO 处理器 */
5
5
  export class FreyaConfigFileHandler {
6
6
  async readFreyaConfig() {
7
- const filePath = path.join(PROJECT_ROOT, 'config', 'freya.json');
7
+ const filePath = path.join(FREYA_HOME, 'config', 'freya.json');
8
8
  try {
9
9
  const data = await fs.readFile(filePath, 'utf-8');
10
10
  return JSON.parse(data);
@@ -14,12 +14,12 @@ export class FreyaConfigFileHandler {
14
14
  }
15
15
  }
16
16
  async writeFreyaConfig(config) {
17
- const filePath = path.join(PROJECT_ROOT, 'config', 'freya.json');
17
+ const filePath = path.join(FREYA_HOME, 'config', 'freya.json');
18
18
  await fs.mkdir(path.dirname(filePath), { recursive: true });
19
19
  await fs.writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
20
20
  }
21
21
  async readProviders() {
22
- const filePath = path.join(PROJECT_ROOT, 'config', 'providers.json');
22
+ const filePath = path.join(FREYA_HOME, 'config', 'providers.json');
23
23
  try {
24
24
  const data = await fs.readFile(filePath, 'utf-8');
25
25
  return JSON.parse(data);
@@ -29,16 +29,16 @@ export class FreyaConfigFileHandler {
29
29
  }
30
30
  }
31
31
  async writeProviders(providers) {
32
- const filePath = path.join(PROJECT_ROOT, 'config', 'providers.json');
32
+ const filePath = path.join(FREYA_HOME, 'config', 'providers.json');
33
33
  await fs.mkdir(path.dirname(filePath), { recursive: true });
34
34
  await fs.writeFile(filePath, JSON.stringify(providers, null, 2) + '\n', 'utf-8');
35
35
  }
36
36
  async readPromptFile(name) {
37
- const filePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
37
+ const filePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
38
38
  return await fs.readFile(filePath, 'utf-8');
39
39
  }
40
40
  async writePromptFile(name, content) {
41
- const filePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
41
+ const filePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
42
42
  await fs.mkdir(path.dirname(filePath), { recursive: true });
43
43
  await fs.writeFile(filePath, content.trim() + '\n', 'utf-8');
44
44
  }
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { APP_ROOT, PROJECT_ROOT } from './utils/paths.js';
2
+ import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from './utils/paths.js';
3
3
  export class DefaultFreyaContext {
4
4
  logger;
5
5
  eventBus;
@@ -7,16 +7,17 @@ export class DefaultFreyaContext {
7
7
  llm;
8
8
  get paths() {
9
9
  const configWorkspace = this.config?.workspace;
10
- let workspaceDir = path.join(PROJECT_ROOT, 'workspace');
10
+ let workspaceDir = path.join(FREYA_HOME, 'workspace');
11
11
  if (configWorkspace && typeof configWorkspace === 'string') {
12
12
  workspaceDir = path.isAbsolute(configWorkspace)
13
13
  ? configWorkspace
14
- : path.resolve(PROJECT_ROOT, configWorkspace);
14
+ : path.resolve(FREYA_HOME, configWorkspace);
15
15
  }
16
16
  return {
17
- appRoot: APP_ROOT,
18
- projectRoot: PROJECT_ROOT,
19
- dataDir: path.join(PROJECT_ROOT, 'data'),
17
+ appRoot: FREYA_APP,
18
+ homeDir: FREYA_HOME,
19
+ workspaceRoot: FREYA_WORKSPACE,
20
+ dataDir: path.join(FREYA_HOME, 'data'),
20
21
  workspaceDir,
21
22
  };
22
23
  }
@@ -1,12 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { PROJECT_ROOT } from '../utils/paths.js';
3
+ import { FREYA_HOME } from '../utils/paths.js';
4
4
  /** LLM 交互日志器,按日期写入 logs/llm-YYYY-MM-DD.log */
5
5
  export class FreyaLLMLogger {
6
6
  logsDir;
7
7
  _enabled;
8
8
  constructor(enabled) {
9
- this.logsDir = path.join(PROJECT_ROOT, 'logs');
9
+ this.logsDir = path.join(FREYA_HOME, 'logs');
10
10
  this._enabled = enabled;
11
11
  fs.mkdirSync(this.logsDir, { recursive: true });
12
12
  }
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import fsPromises from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { inspect } from 'node:util';
5
- import { PROJECT_ROOT } from './utils/paths.js';
5
+ import { FREYA_HOME } from './utils/paths.js';
6
6
  /** 双轨日志器:按日期滚动写入文件,按配置输出至控制台 */
7
7
  export class FreyaLogger {
8
8
  logsDir;
@@ -15,7 +15,7 @@ export class FreyaLogger {
15
15
  currentDay = '';
16
16
  currentLogFilePath = '';
17
17
  constructor() {
18
- this.logsDir = path.join(PROJECT_ROOT, 'logs');
18
+ this.logsDir = path.join(FREYA_HOME, 'logs');
19
19
  fs.mkdirSync(this.logsDir, { recursive: true });
20
20
  }
21
21
  /** 设置各日志级别是否输出至控制台 */
@@ -12,7 +12,7 @@ export interface PluginConfigEntry {
12
12
  displayName?: string;
13
13
  description?: string;
14
14
  version?: string;
15
- source?: 'builtin' | 'runtime' | 'npm';
15
+ source?: 'builtin' | 'workspace' | 'runtime' | 'npm';
16
16
  }
17
17
  /**
18
18
  * Freya 插件生命周期与模块管理器
@@ -5,7 +5,7 @@ import { pathToFileURL } from 'node:url';
5
5
  import { FreyaCommandRegistry } from '../command/command-registry.js';
6
6
  import { FreyaConfigSchemaRegistry } from '../config/schema-registry.js';
7
7
  import { FreyaPromptRegistry } from '../prompt/prompt-registry.js';
8
- import { APP_ROOT, PROJECT_ROOT } from '../utils/paths.js';
8
+ import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from '../utils/paths.js';
9
9
  import { FreyaPluginRegistry } from './plugin-registry.js';
10
10
  /**
11
11
  * Freya 插件生命周期与模块管理器
@@ -115,7 +115,7 @@ export class FreyaPluginManager {
115
115
  }
116
116
  entry.enabled = enabled;
117
117
  entry.status = enabled ? 'active' : 'disabled';
118
- const configPluginsPath = path.join(PROJECT_ROOT, 'config', 'plugins.json');
118
+ const configPluginsPath = path.join(FREYA_HOME, 'config', 'plugins.json');
119
119
  try {
120
120
  await fs.mkdir(path.dirname(configPluginsPath), { recursive: true });
121
121
  const rawEntries = this.pluginEntries.map((e) => ({ id: e.id, enabled: e.enabled }));
@@ -188,7 +188,7 @@ export class FreyaPluginManager {
188
188
  const displayName = String(pkg.freya?.displayName || pkg.displayName || id);
189
189
  const description = String(pkg.description || '');
190
190
  const version = String(pkg.version || '0.1.0');
191
- const defaultEnabled = (source === 'builtin' && pkg.freya?.defaultEnabled === true);
191
+ const defaultEnabled = ((source === 'builtin' || source === 'workspace') && pkg.freya?.defaultEnabled === true);
192
192
  const rawPrompts = pkg.freya?.prompts;
193
193
  const prompts = Array.isArray(rawPrompts) ? rawPrompts.map(String) : [];
194
194
  const isFreyaPlugin = Boolean(pkg.freya ||
@@ -288,7 +288,7 @@ export class FreyaPluginManager {
288
288
  let pkgJsonPath = '';
289
289
  try {
290
290
  pkgJsonPath = req.resolve(`${pkgName}/package.json`, {
291
- paths: [path.join(PROJECT_ROOT), path.join(APP_ROOT), process.cwd()]
291
+ paths: [path.join(FREYA_WORKSPACE), path.join(FREYA_HOME), path.join(FREYA_APP), process.cwd()]
292
292
  });
293
293
  }
294
294
  catch {
@@ -340,7 +340,7 @@ export class FreyaPluginManager {
340
340
  */
341
341
  async scanAllChannels(configuredIds) {
342
342
  const map = new Map();
343
- const builtinDir = path.join(APP_ROOT, 'plugins');
343
+ const builtinDir = path.join(FREYA_APP, 'plugins');
344
344
  try {
345
345
  const entries = await fs.readdir(builtinDir, { withFileTypes: true });
346
346
  for (const entry of entries) {
@@ -352,14 +352,41 @@ export class FreyaPluginManager {
352
352
  }
353
353
  }
354
354
  catch { }
355
- const runtimeDir = path.join(PROJECT_ROOT, 'plugins');
355
+ const workspaceDir = path.join(FREYA_WORKSPACE, 'plugins');
356
+ const resolvedBuiltin = path.resolve(builtinDir);
357
+ const resolvedWorkspace = path.resolve(workspaceDir);
358
+ const resolvedRuntime = path.resolve(path.join(FREYA_HOME, 'plugins'));
359
+ if (resolvedWorkspace !== resolvedBuiltin && resolvedWorkspace !== resolvedRuntime) {
360
+ try {
361
+ const entries = await fs.readdir(workspaceDir, { withFileTypes: true });
362
+ for (const entry of entries) {
363
+ if (entry.isDirectory()) {
364
+ const info = await this.inspectPluginPackage(path.join(workspaceDir, entry.name), 'workspace');
365
+ if (info) {
366
+ const existing = map.get(info.id);
367
+ if (existing?.source === 'builtin') {
368
+ info.source = 'builtin';
369
+ }
370
+ map.set(info.id, info);
371
+ }
372
+ }
373
+ }
374
+ }
375
+ catch { }
376
+ }
377
+ const runtimeDir = path.join(FREYA_HOME, 'plugins');
356
378
  try {
357
379
  const entries = await fs.readdir(runtimeDir, { withFileTypes: true });
358
380
  for (const entry of entries) {
359
381
  if (entry.isDirectory()) {
360
382
  const info = await this.inspectPluginPackage(path.join(runtimeDir, entry.name), 'runtime');
361
- if (info)
383
+ if (info) {
384
+ const existing = map.get(info.id);
385
+ if (existing?.source === 'builtin') {
386
+ info.source = 'builtin';
387
+ }
362
388
  map.set(info.id, info);
389
+ }
363
390
  }
364
391
  }
365
392
  }
@@ -378,7 +405,7 @@ export class FreyaPluginManager {
378
405
  async loadConfiguredPlugins(pluginRegistry, ctx) {
379
406
  this.ctx = ctx;
380
407
  this.pluginRegistry = pluginRegistry;
381
- const configPluginsPath = path.join(PROJECT_ROOT, 'config', 'plugins.json');
408
+ const configPluginsPath = path.join(FREYA_HOME, 'config', 'plugins.json');
382
409
  let configList = [];
383
410
  try {
384
411
  const raw = await fs.readFile(configPluginsPath, 'utf-8');
@@ -1,14 +1,14 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { APP_ROOT, PROJECT_ROOT } from '../utils/paths.js';
4
+ import { FREYA_APP, FREYA_HOME } from '../utils/paths.js';
5
5
  import { FreyaPromptRegistry } from './prompt-registry.js';
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
  /** 提示词物理文件管理器,负责加载、缺失拷贝与持久化覆写 */
8
8
  export class FreyaPromptManager {
9
9
  promptRegistry;
10
10
  logger;
11
- defaultDirPath = path.join(APP_ROOT, 'config', 'prompts');
11
+ defaultDirPath = path.join(FREYA_APP, 'config', 'prompts');
12
12
  constructor(promptRegistry, logger) {
13
13
  this.promptRegistry = promptRegistry;
14
14
  this.logger = logger;
@@ -26,7 +26,7 @@ export class FreyaPromptManager {
26
26
  if (!isCorePrompt) {
27
27
  throw new Error(`拒绝执行:配置管理工具仅允许管理核心提示词`);
28
28
  }
29
- const runFilePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
29
+ const runFilePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
30
30
  await fs.mkdir(path.dirname(runFilePath), { recursive: true });
31
31
  await fs.writeFile(runFilePath, content, 'utf-8');
32
32
  const registryKey = `core.prompt.${name.toLowerCase()}`;
@@ -38,7 +38,7 @@ export class FreyaPromptManager {
38
38
  if (!isCorePrompt) {
39
39
  throw new Error(`拒绝执行:配置管理工具仅允许管理核心提示词`);
40
40
  }
41
- const runFilePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
41
+ const runFilePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
42
42
  const registryKey = `core.prompt.${name.toLowerCase()}`;
43
43
  const currentText = this.promptRegistry.get(registryKey);
44
44
  if (!currentText.includes(targetContent)) {
@@ -1,11 +1,11 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { APP_ROOT, PROJECT_ROOT } from '../utils/paths.js';
3
+ import { FREYA_APP, FREYA_HOME } from '../utils/paths.js';
4
4
  /** 提示词内存注册表,管理所有系统及插件级提示词的分类检索 */
5
5
  export class FreyaPromptRegistry {
6
6
  prompts = new Map();
7
7
  getRunFilePath(prompt) {
8
- return prompt.runPath || path.join(PROJECT_ROOT, 'config', 'prompts', path.basename(prompt.defaultPath));
8
+ return prompt.runPath || path.join(FREYA_HOME, 'config', 'prompts', path.basename(prompt.defaultPath));
9
9
  }
10
10
  /** 注册提示词元数据声明并执行异步双读载入 */
11
11
  async register(prompt) {
@@ -46,14 +46,14 @@ export class FreyaPromptRegistry {
46
46
  }
47
47
  /** 扫描并装载所有内核提示词,支持运行时提示词覆盖默认提示词 */
48
48
  async loadKernelPrompts() {
49
- const defaultDirPath = path.join(APP_ROOT, 'config', 'prompts');
49
+ const defaultDirPath = path.join(FREYA_APP, 'config', 'prompts');
50
50
  try {
51
51
  const corePrompts = ['identity', 'soul', 'tools', 'agents', 'user', 'memory'];
52
52
  for (const name of corePrompts) {
53
53
  await this.register({
54
54
  key: `core.prompt.${name}`,
55
55
  defaultPath: path.join(defaultDirPath, `core.prompt.${name}.md`),
56
- runPath: path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`)
56
+ runPath: path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`)
57
57
  });
58
58
  }
59
59
  try {
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { PROJECT_ROOT } from '../utils/paths.js';
4
- const DATA_DIR = path.resolve(PROJECT_ROOT, 'data');
3
+ import { FREYA_HOME } from '../utils/paths.js';
4
+ const DATA_DIR = path.resolve(FREYA_HOME, 'data');
5
5
  const SESSIONS_DIR = path.resolve(DATA_DIR, 'sessions');
6
6
  const INDEX_FILE = path.resolve(DATA_DIR, 'sessions.json');
7
7
  const sessionDirByUuid = (uuid) => path.resolve(SESSIONS_DIR, uuid);
@@ -5,7 +5,7 @@ export interface FreyaSkill {
5
5
  description: string;
6
6
  content: string;
7
7
  enabled: boolean;
8
- source: 'builtin' | 'runtime';
8
+ source: 'builtin' | 'workspace' | 'runtime';
9
9
  }
10
10
  /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能并管理软开关状态 */
11
11
  export declare class FreyaSkillRegistry {
@@ -1,18 +1,25 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { APP_ROOT, PROJECT_ROOT } from '../utils/paths.js';
3
+ import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from '../utils/paths.js';
4
4
  /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能并管理软开关状态 */
5
5
  export class FreyaSkillRegistry {
6
6
  skills = new Map();
7
7
  context;
8
8
  async loadSkills(context) {
9
9
  this.context = context;
10
- const runtimeSkillsDir = path.join(PROJECT_ROOT, 'skills');
11
- const defaultSkillsDir = path.join(APP_ROOT, 'skills');
12
- const configSkillsPath = path.join(PROJECT_ROOT, 'config', 'skills.json');
10
+ const defaultSkillsDir = path.join(FREYA_APP, 'skills');
11
+ const workspaceSkillsDir = path.join(FREYA_WORKSPACE, 'skills');
12
+ const runtimeSkillsDir = path.join(FREYA_HOME, 'skills');
13
+ const configSkillsPath = path.join(FREYA_HOME, 'config', 'skills.json');
13
14
  try {
14
15
  await fs.mkdir(runtimeSkillsDir, { recursive: true });
15
16
  await this.loadSkillsFromDirectory(defaultSkillsDir, 'builtin', context);
17
+ const resolvedDefault = path.resolve(defaultSkillsDir);
18
+ const resolvedWorkspace = path.resolve(workspaceSkillsDir);
19
+ const resolvedRuntime = path.resolve(runtimeSkillsDir);
20
+ if (resolvedWorkspace !== resolvedDefault && resolvedWorkspace !== resolvedRuntime) {
21
+ await this.loadSkillsFromDirectory(workspaceSkillsDir, 'workspace', context);
22
+ }
16
23
  await this.loadSkillsFromDirectory(runtimeSkillsDir, 'runtime', context);
17
24
  let configList = [];
18
25
  try {
@@ -61,14 +68,17 @@ export class FreyaSkillRegistry {
61
68
  if (metadata.id) {
62
69
  const defaultEnabled = metadata.defaultEnabled !== undefined
63
70
  ? metadata.defaultEnabled !== 'false'
64
- : source === 'builtin';
71
+ : source !== 'runtime';
72
+ const existing = this.skills.get(metadata.id);
73
+ const finalSource = existing?.source === 'builtin' ? 'builtin' : source;
74
+ const finalEnabled = existing !== undefined ? existing.enabled : defaultEnabled;
65
75
  this.skills.set(metadata.id, {
66
76
  id: metadata.id,
67
77
  name: metadata.name || file.replace('.md', ''),
68
78
  description: metadata.description || '',
69
79
  content: content.trim(),
70
- enabled: defaultEnabled,
71
- source
80
+ enabled: finalEnabled,
81
+ source: finalSource
72
82
  });
73
83
  }
74
84
  }
@@ -105,7 +115,7 @@ export class FreyaSkillRegistry {
105
115
  return `ℹ️ 技能 "${skill.name || skillId}" 状态已是 ${enabled ? '启用' : '禁用'}。`;
106
116
  }
107
117
  skill.enabled = enabled;
108
- const configSkillsPath = path.join(PROJECT_ROOT, 'config', 'skills.json');
118
+ const configSkillsPath = path.join(FREYA_HOME, 'config', 'skills.json');
109
119
  try {
110
120
  await this.persistSkillsConfig(configSkillsPath);
111
121
  this.context?.logger.info(`技能 "${skill.name || skillId}" 已切换为: ${enabled ? '启用' : '禁用'}`);
@@ -1,4 +1,6 @@
1
- /** 运行态下的项目根目录(数据与配置存放区) */
2
- export declare const PROJECT_ROOT: string;
1
+ /** 运行态持久化主目录(数据与配置存放区) */
2
+ export declare const FREYA_HOME: string;
3
3
  /** 程序代码物理安装根目录(只读代码与包内默认资源区) */
4
- export declare const APP_ROOT: string;
4
+ export declare const FREYA_APP: string;
5
+ /** 宿主工程/工作区根目录(业务工程代码与定制资源区) */
6
+ export declare const FREYA_WORKSPACE: string;
@@ -3,11 +3,9 @@ import os from 'node:os';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import fs from 'node:fs';
5
5
  const customHome = process.env.FREYA_HOME;
6
+ const customApp = process.env.FREYA_APP;
6
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
8
  function resolveAppRoot() {
8
- if (process.env.FREYA_APP_ROOT) {
9
- return path.resolve(process.env.FREYA_APP_ROOT);
10
- }
11
9
  let current = __dirname;
12
10
  let coreDir = current;
13
11
  while (current) {
@@ -32,9 +30,15 @@ function resolveAppRoot() {
32
30
  }
33
31
  return coreDir;
34
32
  }
35
- /** 运行态下的项目根目录(数据与配置存放区) */
36
- export const PROJECT_ROOT = customHome
33
+ /** 运行态持久化主目录(数据与配置存放区) */
34
+ export const FREYA_HOME = customHome
37
35
  ? path.resolve(customHome)
38
36
  : path.join(os.homedir(), '.freya');
39
37
  /** 程序代码物理安装根目录(只读代码与包内默认资源区) */
40
- export const APP_ROOT = resolveAppRoot();
38
+ export const FREYA_APP = customApp
39
+ ? path.resolve(customApp)
40
+ : resolveAppRoot();
41
+ /** 宿主工程/工作区根目录(业务工程代码与定制资源区) */
42
+ export const FREYA_WORKSPACE = process.env.FREYA_WORKSPACE
43
+ ? path.resolve(process.env.FREYA_WORKSPACE)
44
+ : process.cwd();
@@ -3,7 +3,7 @@ import fsSync from 'node:fs';
3
3
  import fs from 'node:fs/promises';
4
4
  import http from 'node:http';
5
5
  import path from 'node:path';
6
- import { APP_ROOT } from '../utils/paths.js';
6
+ import { FREYA_APP } from '../utils/paths.js';
7
7
  const mimeTypes = {
8
8
  '.html': 'text/html; charset=utf-8',
9
9
  '.css': 'text/css; charset=utf-8',
@@ -36,7 +36,7 @@ export class FreyaWebContainer {
36
36
  async start(ctx, configManager) {
37
37
  this.port = ctx.config?.server?.port ?? 3000;
38
38
  this.configApi = new FreyaConfigApi(configManager);
39
- const uiDist = this.getUiDistPath(APP_ROOT);
39
+ const uiDist = this.getUiDistPath(FREYA_APP);
40
40
  const safePrefix = uiDist.endsWith(path.sep) ? uiDist : uiDist + path.sep;
41
41
  this.httpServer = http.createServer(async (req, res) => {
42
42
  if (this.configApi) {
@@ -103,9 +103,9 @@ export class FreyaWebContainer {
103
103
  }
104
104
  });
105
105
  }
106
- getUiDistPath(projectRoot) {
107
- const devPath = path.join(projectRoot, 'packages', 'ui', 'dist');
108
- const prodPath = path.join(projectRoot, 'ui');
106
+ getUiDistPath(appRoot) {
107
+ const devPath = path.join(appRoot, 'packages', 'ui', 'dist');
108
+ const prodPath = path.join(appRoot, 'ui');
109
109
  if (fsSync.existsSync(devPath))
110
110
  return devPath;
111
111
  return prodPath;
package/core/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eoasmxd/freya-core",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
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.3",
19
+ "@eoasmxd/freya-sdk": "^0.5.0",
20
20
  "ws": "^8.18.0"
21
21
  },
22
22
  "devDependencies": {
@@ -46,7 +46,7 @@ Freya 规范在项目运行根目录下划分了四个功能性区域:
46
46
  1. **Workspace(工作区)**:与宿主隔离的沙箱目录,默认 FS 工具只能读写此目录(`~/.freya/workspace/`)下的文件。
47
47
  2. **Runtime Config(配置目录)**:存放用户的个性化配置,统一保存在运行时目录的 `~/.freya/config/` 下。
48
48
  3. **Runtime Data(数据目录)**:存放运行时持久化的业务状态、历史记录及数据库文件,统一保存在运行时目录的 `~/.freya/data/` 下。
49
- 4. **Runtime Skills(运行时技能卡)**:存放用户自定义技能卡的目录,位于 `~/.freya/skills/` 下。系统在启动时,会同时扫描程序包内置技能目录(`APP_ROOT/skills/`)与此目录下的 Markdown 文件进行双通道动态合并加载。
49
+ 4. **Runtime Skills(运行时技能卡)**:存放用户自定义技能卡的目录,位于 `~/.freya/skills/` 下。系统在启动时,会同时扫描程序包内置技能目录(`FREYA_APP/skills/`)与此目录下的 Markdown 文件进行双通道动态合并加载。
50
50
 
51
51
  ---
52
52
 
@@ -114,7 +114,7 @@ export class FreyaPromptManager {
114
114
  constructor(private promptRegistry: FreyaPromptRegistry) {}
115
115
 
116
116
  async writePrompt(name: string, content: string): Promise<string> {
117
- const runFilePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
117
+ const runFilePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
118
118
 
119
119
  // 1. 异步物理落盘保存
120
120
  await fs.mkdir(path.dirname(runFilePath), { recursive: true });
@@ -61,7 +61,7 @@ export class FreyaPromptRegistry {
61
61
  ### 物理设计优势:
62
62
  这种双读探针(Dual-Read)的设计在生产部署中极为强大:
63
63
  1. **物理防呆与降级**:即使管理员在运行时把外置的配置文件误删了,底座也不会挂起报错,而是会自动回退加载只读包内的默认模板。
64
- 2. **安全隔离**:核心程序包(`APP_ROOT`)可以被设置为只读挂载(在 Docker 容器等环境中),而所有用户配置(`PROJECT_ROOT`)写在隔离的宿主持久化目录中。
64
+ 2. **安全隔离**:核心程序包(`FREYA_APP`)可以被设置为只读挂载(在 Docker 容器等环境中),而所有用户配置(`FREYA_HOME`)写在隔离的宿主持久化目录中。
65
65
 
66
66
  ---
67
67
 
@@ -69,18 +69,18 @@ description: "阐述数据(Data)、配置(Config)与源码(Code)物
69
69
  为了让这一套沙箱架构在开发环境和云端生产环境都能“开箱即用”,底座必须在启动时能够动态定位并自初始化这一套目录结构。
70
70
 
71
71
  ### 1. 动态定位机制
72
- 在 Freya 源码 `packages/core/src/utils/paths.ts` 中,底座通过检测环境变量 `FREYA_HOME` 或用户系统 Home 目录,定义了运行态根目录 `PROJECT_ROOT` 以及程序代码物理安装根目录 `APP_ROOT`:
72
+ 在 Freya 源码 `packages/core/src/utils/paths.ts` 中,底座通过检测环境变量 `FREYA_HOME` 或用户系统 Home 目录,定义了运行态根目录 `FREYA_HOME` 以及程序代码物理安装根目录 `FREYA_APP`:
73
73
 
74
74
  ```typescript
75
75
  const customHome = process.env.FREYA_HOME;
76
76
 
77
77
  /** 运行态下的项目根目录(数据与配置存放区) */
78
- export const PROJECT_ROOT = customHome
78
+ export const FREYA_HOME = customHome
79
79
  ? path.resolve(customHome)
80
80
  : path.join(os.homedir(), '.freya');
81
81
 
82
82
  /** 程序代码物理安装根目录(只读代码与包内默认资源区) */
83
- export const APP_ROOT = resolveAppRoot();
83
+ export const FREYA_APP = resolveAppRoot();
84
84
  ```
85
85
 
86
86
  ### 2. 各模块按需自检初始化 (On-Demand Initialization)
@@ -89,7 +89,7 @@ export const APP_ROOT = resolveAppRoot();
89
89
  例如,在负责多轮会话持久化存储的 `packages/core/src/session/persistence.ts` 模块中:
90
90
 
91
91
  ```typescript
92
- const sessionDirByUuid = (uuid: string) => path.join(PROJECT_ROOT, 'data', 'sessions', uuid);
92
+ const sessionDirByUuid = (uuid: string) => path.join(FREYA_HOME, 'data', 'sessions', uuid);
93
93
 
94
94
  // 在写入会话时,若相应目录不存在,则在底层自动按需创建
95
95
  await fs.mkdir(sessionDirByUuid(uuid), { recursive: true });
@@ -98,7 +98,7 @@ await fs.mkdir(sessionDirByUuid(uuid), { recursive: true });
98
98
  同样地,在负责动态技能扫描的 `packages/core/src/skill/skill-registry.ts` 模块中:
99
99
 
100
100
  ```typescript
101
- const runtimeSkillsDir = path.join(PROJECT_ROOT, 'skills');
101
+ const runtimeSkillsDir = path.join(FREYA_HOME, 'skills');
102
102
  // 自检并按需自动创建运行时用户技能卡目录
103
103
  await fs.mkdir(runtimeSkillsDir, { recursive: true });
104
104
  ```
@@ -83,10 +83,11 @@ export interface Logger {
83
83
  }
84
84
 
85
85
  export interface FreyaPaths {
86
- appRoot: string; // 程序物理安装根目录
87
- projectRoot: string; // 运行态主目录 (默认 ~/.freya)
88
- dataDir: string; // 运行时数据持久化目录
89
- workspaceDir: string; // 隔离的读写文件沙箱
86
+ appRoot: string; // 程序物理安装根目录
87
+ homeDir: string; // 运行态持久化主目录 (默认 ~/.freya)
88
+ workspaceRoot: string; // 宿主工程根目录 (默认 process.cwd())
89
+ dataDir: string; // 运行时数据持久化目录
90
+ workspaceDir: string; // 隔离的读写文件沙箱
90
91
  }
91
92
 
92
93
  export interface FreyaContext {
package/freya.js CHANGED
@@ -106,13 +106,24 @@ await checkSingleInstance();
106
106
  const cliEnabled = await getCliEnabled(process.argv);
107
107
  const isForeground = isForegroundMode(process.argv);
108
108
 
109
+ let appRoot = __dirname;
110
+ try {
111
+ const stat = await fs.stat(path.join(__dirname, 'plugins'));
112
+ if (!stat.isDirectory()) {
113
+ appRoot = path.resolve(__dirname, '..');
114
+ }
115
+ } catch {
116
+ appRoot = path.resolve(__dirname, '..');
117
+ }
118
+
109
119
  if (!cliEnabled && !isForeground) {
110
120
  const child = fork(coreIndex, process.argv.slice(2), {
111
121
  detached: true,
112
122
  stdio: 'ignore',
113
123
  env: {
114
124
  ...process.env,
115
- FREYA_APP_ROOT: __dirname
125
+ FREYA_APP: process.env.FREYA_APP || appRoot,
126
+ FREYA_WORKSPACE: process.env.FREYA_WORKSPACE || process.cwd()
116
127
  }
117
128
  });
118
129
 
@@ -130,7 +141,8 @@ if (!cliEnabled && !isForeground) {
130
141
  stdio: 'inherit',
131
142
  env: {
132
143
  ...process.env,
133
- FREYA_APP_ROOT: __dirname
144
+ FREYA_APP: process.env.FREYA_APP || appRoot,
145
+ FREYA_WORKSPACE: process.env.FREYA_WORKSPACE || process.cwd()
134
146
  }
135
147
  });
136
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eoasmxd/freya",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Freya - 微内核智能体系统",
@@ -35,7 +35,7 @@
35
35
  "src"
36
36
  ],
37
37
  "dependencies": {
38
- "@eoasmxd/freya-sdk": "^0.4.3",
38
+ "@eoasmxd/freya-sdk": "^0.5.0",
39
39
  "ws": "^8.18.0",
40
40
  "mysql2": "^3.11.0",
41
41
  "qrcode": "^1.5.4"
@@ -18,7 +18,7 @@
18
18
  "clean": "rm -rf dist"
19
19
  },
20
20
  "dependencies": {
21
- "@eoasmxd/freya-sdk": "^0.4.3"
21
+ "@eoasmxd/freya-sdk": "^0.5.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^22.0.0",