@eoasmxd/freya 0.4.4 → 0.5.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 (54) 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.d.ts +4 -4
  10. package/core/dist/prompt/prompt-registry.js +34 -15
  11. package/core/dist/session/persistence.js +2 -2
  12. package/core/dist/skill/skill-registry.d.ts +1 -1
  13. package/core/dist/skill/skill-registry.js +18 -8
  14. package/core/dist/utils/paths.d.ts +5 -3
  15. package/core/dist/utils/paths.js +10 -6
  16. package/core/dist/web/web-container.js +5 -5
  17. package/core/package.json +2 -2
  18. package/doc/specifications/config-spec.md +1 -1
  19. package/doc/tutorials/part1_react/3.2_decoupled_architecture.md +1 -1
  20. package/doc/tutorials/part1_react/3.3_freya_dual_read_probe.md +1 -1
  21. package/doc/tutorials/part3_memory/6.2_physical_sandbox_separation.md +5 -5
  22. package/doc/tutorials/part5_plugins/10.1_microkernel_decoupling.md +5 -4
  23. package/freya.js +14 -2
  24. package/package.json +2 -2
  25. package/plugins/plugin-gemini/package.json +1 -1
  26. package/plugins/plugin-openai/package.json +1 -1
  27. package/plugins/plugin-telegram-channel/package.json +1 -1
  28. package/plugins/plugin-tool-fs/package.json +1 -1
  29. package/plugins/plugin-tool-memory/dist/tools.js +2 -2
  30. package/plugins/plugin-tool-memory/package.json +1 -1
  31. package/plugins/plugin-tool-mysql/dist/audit.js +1 -1
  32. package/plugins/plugin-tool-mysql/package.json +1 -1
  33. package/plugins/plugin-tool-web/package.json +1 -1
  34. package/plugins/plugin-wecom-channel/package.json +1 -1
  35. package/plugins/plugin-weixin-channel/package.json +1 -1
  36. package/src/packages/core/src/config/config-manager.ts +3 -3
  37. package/src/packages/core/src/config/file-handler.ts +7 -7
  38. package/src/packages/core/src/context.ts +7 -6
  39. package/src/packages/core/src/llm/llm-logger.ts +2 -2
  40. package/src/packages/core/src/logger.ts +2 -2
  41. package/src/packages/core/src/plugin/plugin-manager.ts +40 -11
  42. package/src/packages/core/src/prompt/prompt-manager.ts +4 -4
  43. package/src/packages/core/src/prompt/prompt-registry.ts +40 -16
  44. package/src/packages/core/src/session/persistence.ts +2 -2
  45. package/src/packages/core/src/skill/skill-registry.ts +24 -10
  46. package/src/packages/core/src/utils/paths.ts +11 -7
  47. package/src/packages/core/src/web/web-container.ts +5 -5
  48. package/src/packages/sdk/src/types/context.ts +4 -2
  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-CyDg-8Iv.js → index-CSZmaZbL.js} +15 -15
  54. package/ui/index.html +1 -1
@@ -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.4",
3
+ "version": "0.5.1",
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.4",
38
+ "@eoasmxd/freya-sdk": "^0.5.1",
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.4"
21
+ "@eoasmxd/freya-sdk": "^0.5.1"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^22.0.0",
@@ -18,7 +18,7 @@
18
18
  "clean": "rm -rf dist"
19
19
  },
20
20
  "dependencies": {
21
- "@eoasmxd/freya-sdk": "^0.4.4"
21
+ "@eoasmxd/freya-sdk": "^0.5.1"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^22.0.0",
@@ -19,7 +19,7 @@
19
19
  "clean": "rm -rf dist"
20
20
  },
21
21
  "dependencies": {
22
- "@eoasmxd/freya-sdk": "^0.4.4"
22
+ "@eoasmxd/freya-sdk": "^0.5.1"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^22.0.0",
@@ -21,7 +21,7 @@
21
21
  "clean": "rm -rf dist"
22
22
  },
23
23
  "dependencies": {
24
- "@eoasmxd/freya-sdk": "^0.4.4"
24
+ "@eoasmxd/freya-sdk": "^0.5.1"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.0.0",
@@ -80,8 +80,8 @@ function getFormattedDateTime() {
80
80
  }
81
81
  function cleanPathFromError(err, ctx) {
82
82
  const rawMessage = err?.message || String(err);
83
- const projectRoot = ctx.paths.projectRoot;
84
- const escapedPath = projectRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
83
+ const homeDir = ctx.paths.homeDir;
84
+ const escapedPath = homeDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
85
85
  const regex = new RegExp(escapedPath + '[\\\\/]?', 'g');
86
86
  return rawMessage.replace(regex, '');
87
87
  }
@@ -21,7 +21,7 @@
21
21
  "clean": "rm -rf dist"
22
22
  },
23
23
  "dependencies": {
24
- "@eoasmxd/freya-sdk": "^0.4.4"
24
+ "@eoasmxd/freya-sdk": "^0.5.1"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.0.0",
@@ -10,7 +10,7 @@ export class SqlAuditService {
10
10
  return this.cachedPrompt;
11
11
  }
12
12
  const promptFileName = 'plugin.prompt.mysql.select.audit.md';
13
- const runtimeOverridePath = path.join(ctx.paths.projectRoot, 'config', 'prompts', promptFileName);
13
+ const runtimeOverridePath = path.join(ctx.paths.homeDir, 'config', 'prompts', promptFileName);
14
14
  try {
15
15
  const content = await fs.readFile(runtimeOverridePath, 'utf-8');
16
16
  if (content.trim()) {
@@ -23,7 +23,7 @@
23
23
  "clean": "rm -rf dist"
24
24
  },
25
25
  "dependencies": {
26
- "@eoasmxd/freya-sdk": "^0.4.4",
26
+ "@eoasmxd/freya-sdk": "^0.5.1",
27
27
  "mysql2": "^3.11.0"
28
28
  },
29
29
  "devDependencies": {
@@ -22,7 +22,7 @@
22
22
  "clean": "rm -rf dist"
23
23
  },
24
24
  "dependencies": {
25
- "@eoasmxd/freya-sdk": "^0.4.4"
25
+ "@eoasmxd/freya-sdk": "^0.5.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.0.0",
@@ -19,7 +19,7 @@
19
19
  "clean": "rm -rf dist"
20
20
  },
21
21
  "dependencies": {
22
- "@eoasmxd/freya-sdk": "^0.4.4",
22
+ "@eoasmxd/freya-sdk": "^0.5.1",
23
23
  "ws": "^8.18.0"
24
24
  },
25
25
  "devDependencies": {
@@ -18,7 +18,7 @@
18
18
  "clean": "rm -rf dist"
19
19
  },
20
20
  "dependencies": {
21
- "@eoasmxd/freya-sdk": "^0.4.4",
21
+ "@eoasmxd/freya-sdk": "^0.5.1",
22
22
  "qrcode": "^1.5.4"
23
23
  },
24
24
  "devDependencies": {
@@ -6,7 +6,7 @@ import { FreyaConfigFileHandler } from './file-handler.js';
6
6
  import { FreyaConfigSchemaRegistry } from './schema-registry.js';
7
7
  import type { FreyaSkillRegistry, FreyaSkill } from '../skill/skill-registry.js';
8
8
  import path from 'node:path';
9
- import { PROJECT_ROOT } from '../utils/paths.js';
9
+ import { FREYA_HOME } from '../utils/paths.js';
10
10
 
11
11
  function cleanPathFromError(err: any): string {
12
12
  const rawMessage = err?.message || String(err);
@@ -226,10 +226,10 @@ export class FreyaConfigManager {
226
226
  const cloned = JSON.parse(JSON.stringify(config));
227
227
  if (cloned.workspace && typeof cloned.workspace === 'string') {
228
228
  if (!path.isAbsolute(cloned.workspace)) {
229
- cloned.workspace = path.resolve(PROJECT_ROOT, cloned.workspace);
229
+ cloned.workspace = path.resolve(FREYA_HOME, cloned.workspace);
230
230
  }
231
231
  } else {
232
- cloned.workspace = path.join(PROJECT_ROOT, 'workspace');
232
+ cloned.workspace = path.join(FREYA_HOME, 'workspace');
233
233
  }
234
234
  (this.context as any).config = deepFreeze(cloned);
235
235
  }
@@ -1,11 +1,11 @@
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
 
5
5
  /** 配置文件底层 IO 处理器 */
6
6
  export class FreyaConfigFileHandler {
7
7
  async readFreyaConfig(): Promise<Record<string, any>> {
8
- const filePath = path.join(PROJECT_ROOT, 'config', 'freya.json');
8
+ const filePath = path.join(FREYA_HOME, 'config', 'freya.json');
9
9
  try {
10
10
  const data = await fs.readFile(filePath, 'utf-8');
11
11
  return JSON.parse(data);
@@ -15,13 +15,13 @@ export class FreyaConfigFileHandler {
15
15
  }
16
16
 
17
17
  async writeFreyaConfig(config: Record<string, any>): Promise<void> {
18
- const filePath = path.join(PROJECT_ROOT, 'config', 'freya.json');
18
+ const filePath = path.join(FREYA_HOME, 'config', 'freya.json');
19
19
  await fs.mkdir(path.dirname(filePath), { recursive: true });
20
20
  await fs.writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
21
21
  }
22
22
 
23
23
  async readProviders(): Promise<any[]> {
24
- const filePath = path.join(PROJECT_ROOT, 'config', 'providers.json');
24
+ const filePath = path.join(FREYA_HOME, 'config', 'providers.json');
25
25
  try {
26
26
  const data = await fs.readFile(filePath, 'utf-8');
27
27
  return JSON.parse(data);
@@ -31,18 +31,18 @@ export class FreyaConfigFileHandler {
31
31
  }
32
32
 
33
33
  async writeProviders(providers: any[]): Promise<void> {
34
- const filePath = path.join(PROJECT_ROOT, 'config', 'providers.json');
34
+ const filePath = path.join(FREYA_HOME, 'config', 'providers.json');
35
35
  await fs.mkdir(path.dirname(filePath), { recursive: true });
36
36
  await fs.writeFile(filePath, JSON.stringify(providers, null, 2) + '\n', 'utf-8');
37
37
  }
38
38
 
39
39
  async readPromptFile(name: string): Promise<string> {
40
- const filePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
40
+ const filePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
41
41
  return await fs.readFile(filePath, 'utf-8');
42
42
  }
43
43
 
44
44
  async writePromptFile(name: string, content: string): Promise<void> {
45
- const filePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
45
+ const filePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
46
46
  await fs.mkdir(path.dirname(filePath), { recursive: true });
47
47
  await fs.writeFile(filePath, content.trim() + '\n', 'utf-8');
48
48
  }
@@ -6,7 +6,7 @@ import type {
6
6
  Logger,
7
7
  } from '@eoasmxd/freya-sdk';
8
8
  import path from 'node:path';
9
- import { APP_ROOT, PROJECT_ROOT } from './utils/paths.js';
9
+ import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from './utils/paths.js';
10
10
 
11
11
  export class DefaultFreyaContext implements FreyaContext {
12
12
  logger!: Logger;
@@ -15,17 +15,18 @@ export class DefaultFreyaContext implements FreyaContext {
15
15
  llm!: ILLMService;
16
16
  get paths(): FreyaPaths {
17
17
  const configWorkspace = this.config?.workspace;
18
- let workspaceDir = path.join(PROJECT_ROOT, 'workspace');
18
+ let workspaceDir = path.join(FREYA_HOME, 'workspace');
19
19
  if (configWorkspace && typeof configWorkspace === 'string') {
20
20
  workspaceDir = path.isAbsolute(configWorkspace)
21
21
  ? configWorkspace
22
- : path.resolve(PROJECT_ROOT, configWorkspace);
22
+ : path.resolve(FREYA_HOME, configWorkspace);
23
23
  }
24
24
 
25
25
  return {
26
- appRoot: APP_ROOT,
27
- projectRoot: PROJECT_ROOT,
28
- dataDir: path.join(PROJECT_ROOT, 'data'),
26
+ appRoot: FREYA_APP,
27
+ homeDir: FREYA_HOME,
28
+ workspaceRoot: FREYA_WORKSPACE,
29
+ dataDir: path.join(FREYA_HOME, 'data'),
29
30
  workspaceDir,
30
31
  };
31
32
  }
@@ -1,7 +1,7 @@
1
1
  import type { LLMMessage, LLMTokenUsage, ToolDefinition } from '@eoasmxd/freya-sdk';
2
2
  import fs from 'node:fs';
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
 
6
6
  /** LLM 交互日志器,按日期写入 logs/llm-YYYY-MM-DD.log */
7
7
  export class FreyaLLMLogger {
@@ -9,7 +9,7 @@ export class FreyaLLMLogger {
9
9
  private _enabled: boolean;
10
10
 
11
11
  constructor(enabled: boolean) {
12
- this.logsDir = path.join(PROJECT_ROOT, 'logs');
12
+ this.logsDir = path.join(FREYA_HOME, 'logs');
13
13
  this._enabled = enabled;
14
14
  fs.mkdirSync(this.logsDir, { recursive: true });
15
15
  }
@@ -3,7 +3,7 @@ import fs from 'node:fs';
3
3
  import fsPromises from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { inspect } from 'node:util';
6
- import { PROJECT_ROOT } from './utils/paths.js';
6
+ import { FREYA_HOME } from './utils/paths.js';
7
7
 
8
8
  /** 双轨日志器:按日期滚动写入文件,按配置输出至控制台 */
9
9
  export class FreyaLogger implements Logger {
@@ -19,7 +19,7 @@ export class FreyaLogger implements Logger {
19
19
  private currentLogFilePath = '';
20
20
 
21
21
  constructor() {
22
- this.logsDir = path.join(PROJECT_ROOT, 'logs');
22
+ this.logsDir = path.join(FREYA_HOME, 'logs');
23
23
  fs.mkdirSync(this.logsDir, { recursive: true });
24
24
  }
25
25
 
@@ -6,7 +6,7 @@ import { pathToFileURL } from 'node:url';
6
6
  import { FreyaCommandRegistry } from '../command/command-registry.js';
7
7
  import { FreyaConfigSchemaRegistry } from '../config/schema-registry.js';
8
8
  import { FreyaPromptRegistry } from '../prompt/prompt-registry.js';
9
- import { APP_ROOT, PROJECT_ROOT } from '../utils/paths.js';
9
+ import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from '../utils/paths.js';
10
10
  import { FreyaPluginRegistry } from './plugin-registry.js';
11
11
 
12
12
  export interface PluginConfigEntry {
@@ -18,7 +18,7 @@ export interface PluginConfigEntry {
18
18
  displayName?: string;
19
19
  description?: string;
20
20
  version?: string;
21
- source?: 'builtin' | 'runtime' | 'npm';
21
+ source?: 'builtin' | 'workspace' | 'runtime' | 'npm';
22
22
  }
23
23
 
24
24
  interface DiscoveredPluginInfo {
@@ -28,7 +28,7 @@ interface DiscoveredPluginInfo {
28
28
  displayName: string;
29
29
  description: string;
30
30
  version: string;
31
- source: 'builtin' | 'runtime' | 'npm';
31
+ source: 'builtin' | 'workspace' | 'runtime' | 'npm';
32
32
  defaultEnabled?: boolean;
33
33
  prompts?: string[];
34
34
  valid: boolean;
@@ -164,7 +164,7 @@ export class FreyaPluginManager {
164
164
  entry.enabled = enabled;
165
165
  entry.status = enabled ? 'active' : 'disabled';
166
166
 
167
- const configPluginsPath = path.join(PROJECT_ROOT, 'config', 'plugins.json');
167
+ const configPluginsPath = path.join(FREYA_HOME, 'config', 'plugins.json');
168
168
  try {
169
169
  await fs.mkdir(path.dirname(configPluginsPath), { recursive: true });
170
170
  const rawEntries = this.pluginEntries.map((e) => ({ id: e.id, enabled: e.enabled }));
@@ -232,7 +232,7 @@ export class FreyaPluginManager {
232
232
  */
233
233
  private async inspectPluginPackage(
234
234
  dirPath: string,
235
- source: 'builtin' | 'runtime' | 'npm'
235
+ source: 'builtin' | 'workspace' | 'runtime' | 'npm'
236
236
  ): Promise<DiscoveredPluginInfo | null> {
237
237
  try {
238
238
  const pkgPath = path.join(dirPath, 'package.json');
@@ -247,7 +247,7 @@ export class FreyaPluginManager {
247
247
  const displayName = String(pkg.freya?.displayName || pkg.displayName || id);
248
248
  const description = String(pkg.description || '');
249
249
  const version = String(pkg.version || '0.1.0');
250
- const defaultEnabled = (source === 'builtin' && pkg.freya?.defaultEnabled === true);
250
+ const defaultEnabled = ((source === 'builtin' || source === 'workspace') && pkg.freya?.defaultEnabled === true);
251
251
  const rawPrompts = pkg.freya?.prompts;
252
252
  const prompts = Array.isArray(rawPrompts) ? rawPrompts.map(String) : [];
253
253
 
@@ -354,7 +354,7 @@ export class FreyaPluginManager {
354
354
  let pkgJsonPath = '';
355
355
  try {
356
356
  pkgJsonPath = req.resolve(`${pkgName}/package.json`, {
357
- paths: [path.join(PROJECT_ROOT), path.join(APP_ROOT), process.cwd()]
357
+ paths: [path.join(FREYA_WORKSPACE), path.join(FREYA_HOME), path.join(FREYA_APP), process.cwd()]
358
358
  });
359
359
  } catch {
360
360
  return {
@@ -407,7 +407,7 @@ export class FreyaPluginManager {
407
407
  private async scanAllChannels(configuredIds: Set<string>): Promise<DiscoveredPluginInfo[]> {
408
408
  const map = new Map<string, DiscoveredPluginInfo>();
409
409
 
410
- const builtinDir = path.join(APP_ROOT, 'plugins');
410
+ const builtinDir = path.join(FREYA_APP, 'plugins');
411
411
  try {
412
412
  const entries = await fs.readdir(builtinDir, { withFileTypes: true });
413
413
  for (const entry of entries) {
@@ -418,13 +418,42 @@ export class FreyaPluginManager {
418
418
  }
419
419
  } catch { }
420
420
 
421
- const runtimeDir = path.join(PROJECT_ROOT, 'plugins');
421
+ const workspaceDir = path.join(FREYA_WORKSPACE, 'plugins');
422
+ const resolvedBuiltin = path.resolve(builtinDir);
423
+ const resolvedWorkspace = path.resolve(workspaceDir);
424
+ const resolvedRuntime = path.resolve(path.join(FREYA_HOME, 'plugins'));
425
+
426
+ if (resolvedWorkspace !== resolvedBuiltin && resolvedWorkspace !== resolvedRuntime) {
427
+ try {
428
+ const entries = await fs.readdir(workspaceDir, { withFileTypes: true });
429
+ for (const entry of entries) {
430
+ if (entry.isDirectory()) {
431
+ const info = await this.inspectPluginPackage(path.join(workspaceDir, entry.name), 'workspace');
432
+ if (info) {
433
+ const existing = map.get(info.id);
434
+ if (existing?.source === 'builtin') {
435
+ info.source = 'builtin';
436
+ }
437
+ map.set(info.id, info);
438
+ }
439
+ }
440
+ }
441
+ } catch { }
442
+ }
443
+
444
+ const runtimeDir = path.join(FREYA_HOME, 'plugins');
422
445
  try {
423
446
  const entries = await fs.readdir(runtimeDir, { withFileTypes: true });
424
447
  for (const entry of entries) {
425
448
  if (entry.isDirectory()) {
426
449
  const info = await this.inspectPluginPackage(path.join(runtimeDir, entry.name), 'runtime');
427
- if (info) map.set(info.id, info);
450
+ if (info) {
451
+ const existing = map.get(info.id);
452
+ if (existing?.source === 'builtin') {
453
+ info.source = 'builtin';
454
+ }
455
+ map.set(info.id, info);
456
+ }
428
457
  }
429
458
  }
430
459
  } catch { }
@@ -445,7 +474,7 @@ export class FreyaPluginManager {
445
474
  async loadConfiguredPlugins(pluginRegistry: FreyaPluginRegistry, ctx: FreyaContext): Promise<void> {
446
475
  this.ctx = ctx;
447
476
  this.pluginRegistry = pluginRegistry;
448
- const configPluginsPath = path.join(PROJECT_ROOT, 'config', 'plugins.json');
477
+ const configPluginsPath = path.join(FREYA_HOME, 'config', 'plugins.json');
449
478
 
450
479
  let configList: Array<{ id: string; enabled: boolean }> = [];
451
480
  try {
@@ -2,14 +2,14 @@ import type { Logger } from '@eoasmxd/freya-sdk';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { APP_ROOT, PROJECT_ROOT } from '../utils/paths.js';
5
+ import { FREYA_APP, FREYA_HOME } from '../utils/paths.js';
6
6
  import { FreyaPromptRegistry } from './prompt-registry.js';
7
7
 
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
 
10
10
  /** 提示词物理文件管理器,负责加载、缺失拷贝与持久化覆写 */
11
11
  export class FreyaPromptManager {
12
- private defaultDirPath = path.join(APP_ROOT, 'config', 'prompts');
12
+ private defaultDirPath = path.join(FREYA_APP, 'config', 'prompts');
13
13
 
14
14
  constructor(
15
15
  private promptRegistry: FreyaPromptRegistry,
@@ -31,7 +31,7 @@ export class FreyaPromptManager {
31
31
  throw new Error(`拒绝执行:配置管理工具仅允许管理核心提示词`);
32
32
  }
33
33
 
34
- const runFilePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
34
+ const runFilePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
35
35
  await fs.mkdir(path.dirname(runFilePath), { recursive: true });
36
36
  await fs.writeFile(runFilePath, content, 'utf-8');
37
37
 
@@ -46,7 +46,7 @@ export class FreyaPromptManager {
46
46
  throw new Error(`拒绝执行:配置管理工具仅允许管理核心提示词`);
47
47
  }
48
48
 
49
- const runFilePath = path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`);
49
+ const runFilePath = path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`);
50
50
  const registryKey = `core.prompt.${name.toLowerCase()}`;
51
51
  const currentText = this.promptRegistry.get(registryKey);
52
52
 
@@ -1,39 +1,63 @@
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
 
5
5
  export interface FreyaPrompt {
6
6
  key: string;
7
7
  content: string;
8
8
  defaultPath: string;
9
- runPath?: string;
9
+ configFileName?: string;
10
10
  }
11
11
 
12
12
  /** 提示词内存注册表,管理所有系统及插件级提示词的分类检索 */
13
13
  export class FreyaPromptRegistry {
14
14
  private prompts = new Map<string, FreyaPrompt>();
15
15
 
16
- private getRunFilePath(prompt: Omit<FreyaPrompt, 'content'>): string {
17
- return prompt.runPath || path.join(PROJECT_ROOT, 'config', 'prompts', path.basename(prompt.defaultPath));
16
+ private resolveProbePaths(prompt: Omit<FreyaPrompt, 'content'>): string[] {
17
+ const baseName = path.basename(prompt.defaultPath);
18
+ const rawPaths: string[] = [];
19
+
20
+ if (prompt.configFileName) {
21
+ rawPaths.push(path.join(FREYA_HOME, 'config', prompt.configFileName));
22
+ rawPaths.push(path.join(FREYA_WORKSPACE, 'config', prompt.configFileName));
23
+ }
24
+
25
+ rawPaths.push(path.join(FREYA_HOME, 'config', 'prompts', baseName));
26
+ rawPaths.push(path.join(FREYA_WORKSPACE, 'config', 'prompts', baseName));
27
+ rawPaths.push(prompt.defaultPath);
28
+
29
+ const candidates: string[] = [];
30
+ const seen = new Set<string>();
31
+ for (const rawPath of rawPaths) {
32
+ const normalized = path.resolve(rawPath);
33
+ if (!seen.has(normalized)) {
34
+ seen.add(normalized);
35
+ candidates.push(normalized);
36
+ }
37
+ }
38
+ return candidates;
18
39
  }
19
40
 
20
- /** 注册提示词元数据声明并执行异步双读载入 */
41
+ /** 注册提示词元数据声明并执行三层级联探针载入 */
21
42
  async register(prompt: Omit<FreyaPrompt, 'content'>): Promise<void> {
22
- const runFilePath = this.getRunFilePath(prompt);
43
+ const probePaths = this.resolveProbePaths(prompt);
23
44
  let content = '';
24
- try {
45
+
46
+ for (const filePath of probePaths) {
25
47
  try {
26
- content = await fs.readFile(runFilePath, 'utf-8');
27
- } catch {
28
- content = await fs.readFile(prompt.defaultPath, 'utf-8');
29
- }
30
- } catch {}
48
+ const text = await fs.readFile(filePath, 'utf-8');
49
+ if (text.trim().length > 0) {
50
+ content = text;
51
+ break;
52
+ }
53
+ } catch {}
54
+ }
31
55
 
32
56
  this.prompts.set(prompt.key, {
33
57
  key: prompt.key,
34
58
  content: content.trim(),
35
59
  defaultPath: prompt.defaultPath,
36
- runPath: prompt.runPath
60
+ configFileName: prompt.configFileName
37
61
  });
38
62
  }
39
63
 
@@ -58,16 +82,16 @@ export class FreyaPromptRegistry {
58
82
  return this.prompts;
59
83
  }
60
84
 
61
- /** 扫描并装载所有内核提示词,支持运行时提示词覆盖默认提示词 */
85
+ /** 扫描并装载所有内核提示词 */
62
86
  async loadKernelPrompts(): Promise<void> {
63
- const defaultDirPath = path.join(APP_ROOT, 'config', 'prompts');
87
+ const defaultDirPath = path.join(FREYA_APP, 'config', 'prompts');
64
88
  try {
65
89
  const corePrompts = ['identity', 'soul', 'tools', 'agents', 'user', 'memory'];
66
90
  for (const name of corePrompts) {
67
91
  await this.register({
68
92
  key: `core.prompt.${name}`,
69
93
  defaultPath: path.join(defaultDirPath, `core.prompt.${name}.md`),
70
- runPath: path.join(PROJECT_ROOT, 'config', `${name.toUpperCase()}.md`)
94
+ configFileName: `${name.toUpperCase()}.md`
71
95
  });
72
96
  }
73
97
 
@@ -1,10 +1,10 @@
1
1
  import type { FreyaContext } from '@eoasmxd/freya-sdk';
2
2
  import fs from 'node:fs/promises';
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
  import type { Session, SessionData, SessionIndex, SnapFile } from './types.js';
6
6
 
7
- const DATA_DIR = path.resolve(PROJECT_ROOT, 'data');
7
+ const DATA_DIR = path.resolve(FREYA_HOME, 'data');
8
8
  const SESSIONS_DIR = path.resolve(DATA_DIR, 'sessions');
9
9
  const INDEX_FILE = path.resolve(DATA_DIR, 'sessions.json');
10
10