@eoasmxd/freya 0.5.0 → 0.5.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 (35) hide show
  1. package/core/dist/context.js +2 -2
  2. package/core/dist/plugin/plugin-manager.d.ts +1 -1
  3. package/core/dist/plugin/plugin-manager.js +8 -8
  4. package/core/dist/prompt/prompt-registry.d.ts +4 -4
  5. package/core/dist/prompt/prompt-registry.js +33 -14
  6. package/core/dist/skill/skill-registry.d.ts +1 -1
  7. package/core/dist/skill/skill-registry.js +5 -5
  8. package/core/dist/utils/paths.d.ts +2 -2
  9. package/core/dist/utils/paths.js +4 -3
  10. package/core/package.json +2 -2
  11. package/doc/tutorials/part5_plugins/10.1_microkernel_decoupling.md +2 -2
  12. package/freya.js +2 -2
  13. package/package.json +2 -2
  14. package/plugins/plugin-gemini/package.json +1 -1
  15. package/plugins/plugin-openai/package.json +1 -1
  16. package/plugins/plugin-telegram-channel/package.json +1 -1
  17. package/plugins/plugin-tool-fs/dist/tools.d.ts +1 -1
  18. package/plugins/plugin-tool-fs/dist/tools.js +58 -15
  19. package/plugins/plugin-tool-fs/package.json +1 -1
  20. package/plugins/plugin-tool-memory/package.json +1 -1
  21. package/plugins/plugin-tool-mysql/package.json +1 -1
  22. package/plugins/plugin-tool-web/package.json +1 -1
  23. package/plugins/plugin-wecom-channel/package.json +1 -1
  24. package/plugins/plugin-weixin-channel/package.json +1 -1
  25. package/src/packages/core/src/context.ts +2 -2
  26. package/src/packages/core/src/plugin/plugin-manager.ts +11 -11
  27. package/src/packages/core/src/prompt/prompt-registry.ts +39 -15
  28. package/src/packages/core/src/skill/skill-registry.ts +7 -7
  29. package/src/packages/core/src/utils/paths.ts +5 -4
  30. package/src/packages/sdk/src/types/context.ts +5 -5
  31. package/src/packages/ui/src/features/config/panels/PluginConfigPanel.tsx +2 -2
  32. package/src/packages/ui/src/features/config/panels/SkillConfigPanel.tsx +2 -2
  33. package/src/plugins/plugin-tool-fs/src/tools.ts +65 -15
  34. package/ui/assets/{index-CSZmaZbL.js → index-CD0PlILU.js} +1 -1
  35. package/ui/index.html +1 -1
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from './utils/paths.js';
2
+ import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from './utils/paths.js';
3
3
  export class DefaultFreyaContext {
4
4
  logger;
5
5
  eventBus;
@@ -16,7 +16,7 @@ export class DefaultFreyaContext {
16
16
  return {
17
17
  appRoot: FREYA_APP,
18
18
  homeDir: FREYA_HOME,
19
- workspaceRoot: FREYA_WORKSPACE,
19
+ launchDir: FREYA_LAUNCH,
20
20
  dataDir: path.join(FREYA_HOME, 'data'),
21
21
  workspaceDir,
22
22
  };
@@ -12,7 +12,7 @@ export interface PluginConfigEntry {
12
12
  displayName?: string;
13
13
  description?: string;
14
14
  version?: string;
15
- source?: 'builtin' | 'workspace' | 'runtime' | 'npm';
15
+ source?: 'builtin' | 'launch' | '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 { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from '../utils/paths.js';
8
+ import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from '../utils/paths.js';
9
9
  import { FreyaPluginRegistry } from './plugin-registry.js';
10
10
  /**
11
11
  * Freya 插件生命周期与模块管理器
@@ -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' || source === 'workspace') && pkg.freya?.defaultEnabled === true);
191
+ const defaultEnabled = ((source === 'builtin' || source === 'launch') && 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(FREYA_WORKSPACE), path.join(FREYA_HOME), path.join(FREYA_APP), process.cwd()]
291
+ paths: [path.join(FREYA_LAUNCH), path.join(FREYA_HOME), path.join(FREYA_APP), process.cwd()]
292
292
  });
293
293
  }
294
294
  catch {
@@ -352,16 +352,16 @@ export class FreyaPluginManager {
352
352
  }
353
353
  }
354
354
  catch { }
355
- const workspaceDir = path.join(FREYA_WORKSPACE, 'plugins');
355
+ const launchPluginsDir = path.join(FREYA_LAUNCH, 'plugins');
356
356
  const resolvedBuiltin = path.resolve(builtinDir);
357
- const resolvedWorkspace = path.resolve(workspaceDir);
357
+ const resolvedLaunch = path.resolve(launchPluginsDir);
358
358
  const resolvedRuntime = path.resolve(path.join(FREYA_HOME, 'plugins'));
359
- if (resolvedWorkspace !== resolvedBuiltin && resolvedWorkspace !== resolvedRuntime) {
359
+ if (resolvedLaunch !== resolvedBuiltin && resolvedLaunch !== resolvedRuntime) {
360
360
  try {
361
- const entries = await fs.readdir(workspaceDir, { withFileTypes: true });
361
+ const entries = await fs.readdir(launchPluginsDir, { withFileTypes: true });
362
362
  for (const entry of entries) {
363
363
  if (entry.isDirectory()) {
364
- const info = await this.inspectPluginPackage(path.join(workspaceDir, entry.name), 'workspace');
364
+ const info = await this.inspectPluginPackage(path.join(launchPluginsDir, entry.name), 'launch');
365
365
  if (info) {
366
366
  const existing = map.get(info.id);
367
367
  if (existing?.source === 'builtin') {
@@ -2,13 +2,13 @@ export interface FreyaPrompt {
2
2
  key: string;
3
3
  content: string;
4
4
  defaultPath: string;
5
- runPath?: string;
5
+ configFileName?: string;
6
6
  }
7
7
  /** 提示词内存注册表,管理所有系统及插件级提示词的分类检索 */
8
8
  export declare class FreyaPromptRegistry {
9
9
  private prompts;
10
- private getRunFilePath;
11
- /** 注册提示词元数据声明并执行异步双读载入 */
10
+ private resolveProbePaths;
11
+ /** 注册提示词元数据声明并执行三层级联探针载入 */
12
12
  register(prompt: Omit<FreyaPrompt, 'content'>): Promise<void>;
13
13
  /** 更新内存中的提示词文本内容 */
14
14
  updateContent(key: string, content: string): void;
@@ -16,7 +16,7 @@ export declare class FreyaPromptRegistry {
16
16
  unregister(key: string): void;
17
17
  get(key: string): string;
18
18
  getPrompts(): Map<string, FreyaPrompt>;
19
- /** 扫描并装载所有内核提示词,支持运行时提示词覆盖默认提示词 */
19
+ /** 扫描并装载所有内核提示词 */
20
20
  loadKernelPrompts(): Promise<void>;
21
21
  /** 获取并拼装完整的核心 System Prompt */
22
22
  getSystemPrompt(): string;
@@ -1,30 +1,49 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { FREYA_APP, FREYA_HOME } from '../utils/paths.js';
3
+ import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from '../utils/paths.js';
4
4
  /** 提示词内存注册表,管理所有系统及插件级提示词的分类检索 */
5
5
  export class FreyaPromptRegistry {
6
6
  prompts = new Map();
7
- getRunFilePath(prompt) {
8
- return prompt.runPath || path.join(FREYA_HOME, 'config', 'prompts', path.basename(prompt.defaultPath));
7
+ resolveProbePaths(prompt) {
8
+ const baseName = path.basename(prompt.defaultPath);
9
+ const rawPaths = [];
10
+ if (prompt.configFileName) {
11
+ rawPaths.push(path.join(FREYA_HOME, 'config', prompt.configFileName));
12
+ rawPaths.push(path.join(FREYA_LAUNCH, 'config', prompt.configFileName));
13
+ }
14
+ rawPaths.push(path.join(FREYA_HOME, 'config', 'prompts', baseName));
15
+ rawPaths.push(path.join(FREYA_LAUNCH, 'config', 'prompts', baseName));
16
+ rawPaths.push(prompt.defaultPath);
17
+ const candidates = [];
18
+ const seen = new Set();
19
+ for (const rawPath of rawPaths) {
20
+ const normalized = path.resolve(rawPath);
21
+ if (!seen.has(normalized)) {
22
+ seen.add(normalized);
23
+ candidates.push(normalized);
24
+ }
25
+ }
26
+ return candidates;
9
27
  }
10
- /** 注册提示词元数据声明并执行异步双读载入 */
28
+ /** 注册提示词元数据声明并执行三层级联探针载入 */
11
29
  async register(prompt) {
12
- const runFilePath = this.getRunFilePath(prompt);
30
+ const probePaths = this.resolveProbePaths(prompt);
13
31
  let content = '';
14
- try {
32
+ for (const filePath of probePaths) {
15
33
  try {
16
- content = await fs.readFile(runFilePath, 'utf-8');
17
- }
18
- catch {
19
- content = await fs.readFile(prompt.defaultPath, 'utf-8');
34
+ const text = await fs.readFile(filePath, 'utf-8');
35
+ if (text.trim().length > 0) {
36
+ content = text;
37
+ break;
38
+ }
20
39
  }
40
+ catch { }
21
41
  }
22
- catch { }
23
42
  this.prompts.set(prompt.key, {
24
43
  key: prompt.key,
25
44
  content: content.trim(),
26
45
  defaultPath: prompt.defaultPath,
27
- runPath: prompt.runPath
46
+ configFileName: prompt.configFileName
28
47
  });
29
48
  }
30
49
  /** 更新内存中的提示词文本内容 */
@@ -44,7 +63,7 @@ export class FreyaPromptRegistry {
44
63
  getPrompts() {
45
64
  return this.prompts;
46
65
  }
47
- /** 扫描并装载所有内核提示词,支持运行时提示词覆盖默认提示词 */
66
+ /** 扫描并装载所有内核提示词 */
48
67
  async loadKernelPrompts() {
49
68
  const defaultDirPath = path.join(FREYA_APP, 'config', 'prompts');
50
69
  try {
@@ -53,7 +72,7 @@ export class FreyaPromptRegistry {
53
72
  await this.register({
54
73
  key: `core.prompt.${name}`,
55
74
  defaultPath: path.join(defaultDirPath, `core.prompt.${name}.md`),
56
- runPath: path.join(FREYA_HOME, 'config', `${name.toUpperCase()}.md`)
75
+ configFileName: `${name.toUpperCase()}.md`
57
76
  });
58
77
  }
59
78
  try {
@@ -5,7 +5,7 @@ export interface FreyaSkill {
5
5
  description: string;
6
6
  content: string;
7
7
  enabled: boolean;
8
- source: 'builtin' | 'workspace' | 'runtime';
8
+ source: 'builtin' | 'launch' | 'runtime';
9
9
  }
10
10
  /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能并管理软开关状态 */
11
11
  export declare class FreyaSkillRegistry {
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from '../utils/paths.js';
3
+ import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from '../utils/paths.js';
4
4
  /** 技能注册表,从 skills/ 目录加载 Markdown 格式技能并管理软开关状态 */
5
5
  export class FreyaSkillRegistry {
6
6
  skills = new Map();
@@ -8,17 +8,17 @@ export class FreyaSkillRegistry {
8
8
  async loadSkills(context) {
9
9
  this.context = context;
10
10
  const defaultSkillsDir = path.join(FREYA_APP, 'skills');
11
- const workspaceSkillsDir = path.join(FREYA_WORKSPACE, 'skills');
11
+ const launchSkillsDir = path.join(FREYA_LAUNCH, 'skills');
12
12
  const runtimeSkillsDir = path.join(FREYA_HOME, 'skills');
13
13
  const configSkillsPath = path.join(FREYA_HOME, 'config', 'skills.json');
14
14
  try {
15
15
  await fs.mkdir(runtimeSkillsDir, { recursive: true });
16
16
  await this.loadSkillsFromDirectory(defaultSkillsDir, 'builtin', context);
17
17
  const resolvedDefault = path.resolve(defaultSkillsDir);
18
- const resolvedWorkspace = path.resolve(workspaceSkillsDir);
18
+ const resolvedLaunch = path.resolve(launchSkillsDir);
19
19
  const resolvedRuntime = path.resolve(runtimeSkillsDir);
20
- if (resolvedWorkspace !== resolvedDefault && resolvedWorkspace !== resolvedRuntime) {
21
- await this.loadSkillsFromDirectory(workspaceSkillsDir, 'workspace', context);
20
+ if (resolvedLaunch !== resolvedDefault && resolvedLaunch !== resolvedRuntime) {
21
+ await this.loadSkillsFromDirectory(launchSkillsDir, 'launch', context);
22
22
  }
23
23
  await this.loadSkillsFromDirectory(runtimeSkillsDir, 'runtime', context);
24
24
  let configList = [];
@@ -2,5 +2,5 @@
2
2
  export declare const FREYA_HOME: string;
3
3
  /** 程序代码物理安装根目录(只读代码与包内默认资源区) */
4
4
  export declare const FREYA_APP: string;
5
- /** 宿主工程/工作区根目录(业务工程代码与定制资源区) */
6
- export declare const FREYA_WORKSPACE: string;
5
+ /** 宿主命令启动执行根目录(默认当前工作目录 process.cwd()) */
6
+ export declare const FREYA_LAUNCH: string;
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import fs from 'node:fs';
5
5
  const customHome = process.env.FREYA_HOME;
6
6
  const customApp = process.env.FREYA_APP;
7
+ const customLaunch = process.env.FREYA_LAUNCH;
7
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
9
  function resolveAppRoot() {
9
10
  let current = __dirname;
@@ -38,7 +39,7 @@ export const FREYA_HOME = customHome
38
39
  export const FREYA_APP = customApp
39
40
  ? path.resolve(customApp)
40
41
  : resolveAppRoot();
41
- /** 宿主工程/工作区根目录(业务工程代码与定制资源区) */
42
- export const FREYA_WORKSPACE = process.env.FREYA_WORKSPACE
43
- ? path.resolve(process.env.FREYA_WORKSPACE)
42
+ /** 宿主命令启动执行根目录(默认当前工作目录 process.cwd()) */
43
+ export const FREYA_LAUNCH = customLaunch
44
+ ? path.resolve(customLaunch)
44
45
  : process.cwd();
package/core/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eoasmxd/freya-core",
3
- "version": "0.5.0",
3
+ "version": "0.5.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.5.0",
19
+ "@eoasmxd/freya-sdk": "^0.5.2",
20
20
  "ws": "^8.18.0"
21
21
  },
22
22
  "devDependencies": {
@@ -85,9 +85,9 @@ export interface Logger {
85
85
  export interface FreyaPaths {
86
86
  appRoot: string; // 程序物理安装根目录
87
87
  homeDir: string; // 运行态持久化主目录 (默认 ~/.freya)
88
- workspaceRoot: string; // 宿主工程根目录 (默认 process.cwd())
88
+ launchDir: string; // 宿主命令启动执行目录 (默认 process.cwd())
89
89
  dataDir: string; // 运行时数据持久化目录
90
- workspaceDir: string; // 隔离的读写文件沙箱
90
+ workspaceDir: string; // 智能体专属读写工作区
91
91
  }
92
92
 
93
93
  export interface FreyaContext {
package/freya.js CHANGED
@@ -123,7 +123,7 @@ if (!cliEnabled && !isForeground) {
123
123
  env: {
124
124
  ...process.env,
125
125
  FREYA_APP: process.env.FREYA_APP || appRoot,
126
- FREYA_WORKSPACE: process.env.FREYA_WORKSPACE || process.cwd()
126
+ FREYA_LAUNCH: process.env.FREYA_LAUNCH || process.cwd()
127
127
  }
128
128
  });
129
129
 
@@ -142,7 +142,7 @@ if (!cliEnabled && !isForeground) {
142
142
  env: {
143
143
  ...process.env,
144
144
  FREYA_APP: process.env.FREYA_APP || appRoot,
145
- FREYA_WORKSPACE: process.env.FREYA_WORKSPACE || process.cwd()
145
+ FREYA_LAUNCH: process.env.FREYA_LAUNCH || process.cwd()
146
146
  }
147
147
  });
148
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eoasmxd/freya",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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.5.0",
38
+ "@eoasmxd/freya-sdk": "^0.5.2",
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.5.0"
21
+ "@eoasmxd/freya-sdk": "^0.5.2"
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.5.0"
21
+ "@eoasmxd/freya-sdk": "^0.5.2"
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.5.0"
22
+ "@eoasmxd/freya-sdk": "^0.5.2"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^22.0.0",
@@ -1,5 +1,5 @@
1
1
  import type { FreyaContext, ToolDefinition, FreyaTool } from '@eoasmxd/freya-sdk';
2
- /** 安全工作区路径获取 (支持指定 scope 作用域) */
2
+ /** 获取经过安全边界校验的绝对路径 */
3
3
  export declare function getSafePath(ctx: FreyaContext, relativePath: string, scope?: string): {
4
4
  targetAbs: string;
5
5
  baseAbs: string;
@@ -1,13 +1,50 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- /** 安全工作区路径获取 (支持指定 scope 作用域) */
3
+ /** 解析作用域实际物理路径,返回 null 表示显式禁用 */
4
+ function resolveScopeBase(envValue, defaultPath, launchDir) {
5
+ if (envValue === undefined) {
6
+ return defaultPath;
7
+ }
8
+ const trimmed = envValue.trim();
9
+ if (trimmed === '' || trimmed.toLowerCase() === 'false') {
10
+ return null;
11
+ }
12
+ return path.isAbsolute(trimmed) ? trimmed : path.resolve(launchDir, trimmed);
13
+ }
14
+ /** 获取当前激活的作用域列表与描述文本 */
15
+ function getActiveScopesInfo() {
16
+ const scopes = ['workspace'];
17
+ const descParts = ['默认为 "workspace" 沙箱'];
18
+ const srcVal = process.env.FREYA_FS_SRC;
19
+ const isSrcDisabled = srcVal !== undefined && (srcVal.trim() === '' || srcVal.trim().toLowerCase() === 'false');
20
+ if (!isSrcDisabled) {
21
+ scopes.push('src');
22
+ descParts.push('支持指定 "src" 源码区');
23
+ }
24
+ const docVal = process.env.FREYA_FS_DOC;
25
+ const isDocDisabled = docVal !== undefined && (docVal.trim() === '' || docVal.trim().toLowerCase() === 'false');
26
+ if (!isDocDisabled) {
27
+ scopes.push('doc');
28
+ descParts.push('指定 "doc" 文档区');
29
+ }
30
+ const description = `读取作用域(可选,${descParts.join(',')})`;
31
+ return { scopes, description };
32
+ }
33
+ /** 获取经过安全边界校验的绝对路径 */
4
34
  export function getSafePath(ctx, relativePath, scope) {
5
- let baseAbs = ctx.paths.workspaceDir;
6
- if (scope === 'src') {
7
- baseAbs = path.join(ctx.paths.appRoot, 'src');
35
+ const targetScope = scope || 'workspace';
36
+ let baseAbs = null;
37
+ if (targetScope === 'workspace') {
38
+ baseAbs = ctx.paths.workspaceDir;
39
+ }
40
+ else if (targetScope === 'src') {
41
+ baseAbs = resolveScopeBase(process.env.FREYA_FS_SRC, path.join(ctx.paths.appRoot, 'src'), ctx.paths.launchDir);
8
42
  }
9
- else if (scope === 'doc') {
10
- baseAbs = path.join(ctx.paths.appRoot, 'doc');
43
+ else if (targetScope === 'doc') {
44
+ baseAbs = resolveScopeBase(process.env.FREYA_FS_DOC, path.join(ctx.paths.appRoot, 'doc'), ctx.paths.launchDir);
45
+ }
46
+ if (!baseAbs) {
47
+ throw new Error(`安全拒绝:作用域 "${targetScope}" 未开放或已被禁用。`);
11
48
  }
12
49
  if (relativePath && path.isAbsolute(relativePath)) {
13
50
  throw new Error('安全拒绝:只能使用相对路径,不允许使用绝对路径。');
@@ -42,6 +79,7 @@ function formatBytes(bytes) {
42
79
  }
43
80
  export class ListDirTool {
44
81
  getDefinition() {
82
+ const { scopes, description } = getActiveScopesInfo();
45
83
  return {
46
84
  name: 'list_dir',
47
85
  description: '列出指定目录下的文件和子文件夹列表。注意:仅允许访问相对路径,不可传绝对路径或向上越级 escape 路径。',
@@ -54,8 +92,8 @@ export class ListDirTool {
54
92
  },
55
93
  scope: {
56
94
  type: 'string',
57
- enum: ['workspace', 'src', 'doc'],
58
- description: '读取作用域(可选,默认为 "workspace" 沙箱,支持指定 "src" 源码区或 "doc" 文档区)'
95
+ enum: scopes,
96
+ description
59
97
  }
60
98
  }
61
99
  }
@@ -101,6 +139,7 @@ export class ListDirTool {
101
139
  }
102
140
  export class ReadFileTool {
103
141
  getDefinition() {
142
+ const { scopes, description } = getActiveScopesInfo();
104
143
  return {
105
144
  name: 'read_file',
106
145
  description: '读取指定文件的文本内容。支持指定行号起止区间切片读取,防范大文件上下文超限。',
@@ -113,8 +152,8 @@ export class ReadFileTool {
113
152
  },
114
153
  scope: {
115
154
  type: 'string',
116
- enum: ['workspace', 'src', 'doc'],
117
- description: '读取作用域(可选,默认为 "workspace" 沙箱,支持指定 "src" 源码区或 "doc" 文档区)'
155
+ enum: scopes,
156
+ description
118
157
  },
119
158
  startLine: {
120
159
  type: 'integer',
@@ -191,7 +230,7 @@ export class WriteFileTool {
191
230
  return '❌ 参数错误:必须指定目标路径与写入内容。';
192
231
  }
193
232
  if (args.scope && args.scope !== 'workspace') {
194
- return `❌ 安全拒绝:物理源码区 (src) 与文档区 (doc) 为只读保护区,严禁写入或修改。`;
233
+ return '❌ 安全拒绝:仅允许在工作区 (workspace) 进行写入操作,其它区域均为只读保护区。';
195
234
  }
196
235
  try {
197
236
  const { targetAbs, baseAbs } = getSafePath(ctx, args.path);
@@ -235,7 +274,7 @@ export class EditFileTool {
235
274
  return '❌ 参数错误:必须指定目标路径、查找目标与替换文本。';
236
275
  }
237
276
  if (args.scope && args.scope !== 'workspace') {
238
- return `❌ 安全拒绝:物理源码区 (src) 与文档区 (doc) 为只读保护区,严禁写入或修改。`;
277
+ return '❌ 安全拒绝:仅允许在工作区 (workspace) 进行修改操作,其它区域均为只读保护区。';
239
278
  }
240
279
  try {
241
280
  const { targetAbs, baseAbs } = getSafePath(ctx, args.path);
@@ -244,11 +283,15 @@ export class EditFileTool {
244
283
  return `❌ 路径 "${args.path}" 不是一个有效的文件。`;
245
284
  }
246
285
  const content = await fs.readFile(targetAbs, 'utf-8');
247
- const index = content.indexOf(args.target);
248
- if (index === -1) {
286
+ const firstIndex = content.indexOf(args.target);
287
+ if (firstIndex === -1) {
249
288
  return `❌ 修改失败:在文件 "${args.path}" 中未找到指定的 target 文本。请确保 target 在大小写、缩进和换行上与文件内完全一致。`;
250
289
  }
251
- const newContent = content.slice(0, index) + args.replacement + content.slice(index + args.target.length);
290
+ const secondIndex = content.indexOf(args.target, firstIndex + args.target.length);
291
+ if (secondIndex !== -1) {
292
+ return `❌ 修改拒绝:在文件 "${args.path}" 中匹配到多处相同的目标文本。请扩大 target 文本段以包含更多上下文行确保唯一性。`;
293
+ }
294
+ const newContent = content.slice(0, firstIndex) + args.replacement + content.slice(firstIndex + args.target.length);
252
295
  await fs.writeFile(targetAbs, newContent, 'utf-8');
253
296
  return `ℹ️ 已成功修改文件 "${args.path}" 的指定部分。`;
254
297
  }
@@ -21,7 +21,7 @@
21
21
  "clean": "rm -rf dist"
22
22
  },
23
23
  "dependencies": {
24
- "@eoasmxd/freya-sdk": "^0.5.0"
24
+ "@eoasmxd/freya-sdk": "^0.5.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@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.5.0"
24
+ "@eoasmxd/freya-sdk": "^0.5.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.0.0",
@@ -23,7 +23,7 @@
23
23
  "clean": "rm -rf dist"
24
24
  },
25
25
  "dependencies": {
26
- "@eoasmxd/freya-sdk": "^0.5.0",
26
+ "@eoasmxd/freya-sdk": "^0.5.2",
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.5.0"
25
+ "@eoasmxd/freya-sdk": "^0.5.2"
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.5.0",
22
+ "@eoasmxd/freya-sdk": "^0.5.2",
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.5.0",
21
+ "@eoasmxd/freya-sdk": "^0.5.2",
22
22
  "qrcode": "^1.5.4"
23
23
  },
24
24
  "devDependencies": {
@@ -6,7 +6,7 @@ import type {
6
6
  Logger,
7
7
  } from '@eoasmxd/freya-sdk';
8
8
  import path from 'node:path';
9
- import { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from './utils/paths.js';
9
+ import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from './utils/paths.js';
10
10
 
11
11
  export class DefaultFreyaContext implements FreyaContext {
12
12
  logger!: Logger;
@@ -25,7 +25,7 @@ export class DefaultFreyaContext implements FreyaContext {
25
25
  return {
26
26
  appRoot: FREYA_APP,
27
27
  homeDir: FREYA_HOME,
28
- workspaceRoot: FREYA_WORKSPACE,
28
+ launchDir: FREYA_LAUNCH,
29
29
  dataDir: path.join(FREYA_HOME, 'data'),
30
30
  workspaceDir,
31
31
  };
@@ -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 { FREYA_APP, FREYA_HOME, FREYA_WORKSPACE } from '../utils/paths.js';
9
+ import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } 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' | 'workspace' | 'runtime' | 'npm';
21
+ source?: 'builtin' | 'launch' | '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' | 'workspace' | 'runtime' | 'npm';
31
+ source: 'builtin' | 'launch' | 'runtime' | 'npm';
32
32
  defaultEnabled?: boolean;
33
33
  prompts?: string[];
34
34
  valid: boolean;
@@ -232,7 +232,7 @@ export class FreyaPluginManager {
232
232
  */
233
233
  private async inspectPluginPackage(
234
234
  dirPath: string,
235
- source: 'builtin' | 'workspace' | 'runtime' | 'npm'
235
+ source: 'builtin' | 'launch' | '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' || source === 'workspace') && pkg.freya?.defaultEnabled === true);
250
+ const defaultEnabled = ((source === 'builtin' || source === 'launch') && 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(FREYA_WORKSPACE), path.join(FREYA_HOME), path.join(FREYA_APP), process.cwd()]
357
+ paths: [path.join(FREYA_LAUNCH), path.join(FREYA_HOME), path.join(FREYA_APP), process.cwd()]
358
358
  });
359
359
  } catch {
360
360
  return {
@@ -418,17 +418,17 @@ export class FreyaPluginManager {
418
418
  }
419
419
  } catch { }
420
420
 
421
- const workspaceDir = path.join(FREYA_WORKSPACE, 'plugins');
421
+ const launchPluginsDir = path.join(FREYA_LAUNCH, 'plugins');
422
422
  const resolvedBuiltin = path.resolve(builtinDir);
423
- const resolvedWorkspace = path.resolve(workspaceDir);
423
+ const resolvedLaunch = path.resolve(launchPluginsDir);
424
424
  const resolvedRuntime = path.resolve(path.join(FREYA_HOME, 'plugins'));
425
425
 
426
- if (resolvedWorkspace !== resolvedBuiltin && resolvedWorkspace !== resolvedRuntime) {
426
+ if (resolvedLaunch !== resolvedBuiltin && resolvedLaunch !== resolvedRuntime) {
427
427
  try {
428
- const entries = await fs.readdir(workspaceDir, { withFileTypes: true });
428
+ const entries = await fs.readdir(launchPluginsDir, { withFileTypes: true });
429
429
  for (const entry of entries) {
430
430
  if (entry.isDirectory()) {
431
- const info = await this.inspectPluginPackage(path.join(workspaceDir, entry.name), 'workspace');
431
+ const info = await this.inspectPluginPackage(path.join(launchPluginsDir, entry.name), 'launch');
432
432
  if (info) {
433
433
  const existing = map.get(info.id);
434
434
  if (existing?.source === 'builtin') {