@eoasmxd/freya 0.5.1 → 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.
- package/core/dist/context.js +2 -2
- package/core/dist/plugin/plugin-manager.d.ts +1 -1
- package/core/dist/plugin/plugin-manager.js +8 -8
- package/core/dist/prompt/prompt-registry.js +3 -3
- package/core/dist/skill/skill-registry.d.ts +1 -1
- package/core/dist/skill/skill-registry.js +5 -5
- package/core/dist/utils/paths.d.ts +2 -2
- package/core/dist/utils/paths.js +4 -3
- package/core/package.json +2 -2
- package/doc/tutorials/part5_plugins/10.1_microkernel_decoupling.md +2 -2
- package/freya.js +2 -2
- package/package.json +2 -2
- package/plugins/plugin-gemini/package.json +1 -1
- package/plugins/plugin-openai/package.json +1 -1
- package/plugins/plugin-telegram-channel/package.json +1 -1
- package/plugins/plugin-tool-fs/dist/tools.d.ts +1 -1
- package/plugins/plugin-tool-fs/dist/tools.js +58 -15
- package/plugins/plugin-tool-fs/package.json +1 -1
- package/plugins/plugin-tool-memory/package.json +1 -1
- package/plugins/plugin-tool-mysql/package.json +1 -1
- package/plugins/plugin-tool-web/package.json +1 -1
- package/plugins/plugin-wecom-channel/package.json +1 -1
- package/plugins/plugin-weixin-channel/package.json +1 -1
- package/src/packages/core/src/context.ts +2 -2
- package/src/packages/core/src/plugin/plugin-manager.ts +11 -11
- package/src/packages/core/src/prompt/prompt-registry.ts +3 -3
- package/src/packages/core/src/skill/skill-registry.ts +7 -7
- package/src/packages/core/src/utils/paths.ts +5 -4
- package/src/packages/sdk/src/types/context.ts +5 -5
- package/src/packages/ui/src/features/config/panels/PluginConfigPanel.tsx +2 -2
- package/src/packages/ui/src/features/config/panels/SkillConfigPanel.tsx +2 -2
- package/src/plugins/plugin-tool-fs/src/tools.ts +65 -15
- package/ui/assets/{index-CSZmaZbL.js → index-CD0PlILU.js} +1 -1
- package/ui/index.html +1 -1
package/core/dist/context.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { FREYA_APP, FREYA_HOME,
|
|
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
|
-
|
|
19
|
+
launchDir: FREYA_LAUNCH,
|
|
20
20
|
dataDir: path.join(FREYA_HOME, 'data'),
|
|
21
21
|
workspaceDir,
|
|
22
22
|
};
|
|
@@ -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,
|
|
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 === '
|
|
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(
|
|
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
|
|
355
|
+
const launchPluginsDir = path.join(FREYA_LAUNCH, 'plugins');
|
|
356
356
|
const resolvedBuiltin = path.resolve(builtinDir);
|
|
357
|
-
const
|
|
357
|
+
const resolvedLaunch = path.resolve(launchPluginsDir);
|
|
358
358
|
const resolvedRuntime = path.resolve(path.join(FREYA_HOME, 'plugins'));
|
|
359
|
-
if (
|
|
359
|
+
if (resolvedLaunch !== resolvedBuiltin && resolvedLaunch !== resolvedRuntime) {
|
|
360
360
|
try {
|
|
361
|
-
const entries = await fs.readdir(
|
|
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(
|
|
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') {
|
|
@@ -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,
|
|
3
|
+
import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from '../utils/paths.js';
|
|
4
4
|
/** 提示词内存注册表,管理所有系统及插件级提示词的分类检索 */
|
|
5
5
|
export class FreyaPromptRegistry {
|
|
6
6
|
prompts = new Map();
|
|
@@ -9,10 +9,10 @@ export class FreyaPromptRegistry {
|
|
|
9
9
|
const rawPaths = [];
|
|
10
10
|
if (prompt.configFileName) {
|
|
11
11
|
rawPaths.push(path.join(FREYA_HOME, 'config', prompt.configFileName));
|
|
12
|
-
rawPaths.push(path.join(
|
|
12
|
+
rawPaths.push(path.join(FREYA_LAUNCH, 'config', prompt.configFileName));
|
|
13
13
|
}
|
|
14
14
|
rawPaths.push(path.join(FREYA_HOME, 'config', 'prompts', baseName));
|
|
15
|
-
rawPaths.push(path.join(
|
|
15
|
+
rawPaths.push(path.join(FREYA_LAUNCH, 'config', 'prompts', baseName));
|
|
16
16
|
rawPaths.push(prompt.defaultPath);
|
|
17
17
|
const candidates = [];
|
|
18
18
|
const seen = new Set();
|
|
@@ -5,7 +5,7 @@ export interface FreyaSkill {
|
|
|
5
5
|
description: string;
|
|
6
6
|
content: string;
|
|
7
7
|
enabled: boolean;
|
|
8
|
-
source: 'builtin' | '
|
|
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,
|
|
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
|
|
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
|
|
18
|
+
const resolvedLaunch = path.resolve(launchSkillsDir);
|
|
19
19
|
const resolvedRuntime = path.resolve(runtimeSkillsDir);
|
|
20
|
-
if (
|
|
21
|
-
await this.loadSkillsFromDirectory(
|
|
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
|
|
5
|
+
/** 宿主命令启动执行根目录(默认当前工作目录 process.cwd()) */
|
|
6
|
+
export declare const FREYA_LAUNCH: string;
|
package/core/dist/utils/paths.js
CHANGED
|
@@ -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
|
|
43
|
-
? path.resolve(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FreyaContext, ToolDefinition, FreyaTool } from '@eoasmxd/freya-sdk';
|
|
2
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 (
|
|
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:
|
|
58
|
-
description
|
|
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:
|
|
117
|
-
description
|
|
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
|
|
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
|
|
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
|
|
248
|
-
if (
|
|
286
|
+
const firstIndex = content.indexOf(args.target);
|
|
287
|
+
if (firstIndex === -1) {
|
|
249
288
|
return `❌ 修改失败:在文件 "${args.path}" 中未找到指定的 target 文本。请确保 target 在大小写、缩进和换行上与文件内完全一致。`;
|
|
250
289
|
}
|
|
251
|
-
const
|
|
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
|
}
|
|
@@ -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,
|
|
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
|
-
|
|
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,
|
|
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' | '
|
|
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' | '
|
|
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' | '
|
|
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 === '
|
|
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(
|
|
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
|
|
421
|
+
const launchPluginsDir = path.join(FREYA_LAUNCH, 'plugins');
|
|
422
422
|
const resolvedBuiltin = path.resolve(builtinDir);
|
|
423
|
-
const
|
|
423
|
+
const resolvedLaunch = path.resolve(launchPluginsDir);
|
|
424
424
|
const resolvedRuntime = path.resolve(path.join(FREYA_HOME, 'plugins'));
|
|
425
425
|
|
|
426
|
-
if (
|
|
426
|
+
if (resolvedLaunch !== resolvedBuiltin && resolvedLaunch !== resolvedRuntime) {
|
|
427
427
|
try {
|
|
428
|
-
const entries = await fs.readdir(
|
|
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(
|
|
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') {
|
|
@@ -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,
|
|
3
|
+
import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from '../utils/paths.js';
|
|
4
4
|
|
|
5
5
|
export interface FreyaPrompt {
|
|
6
6
|
key: string;
|
|
@@ -19,11 +19,11 @@ export class FreyaPromptRegistry {
|
|
|
19
19
|
|
|
20
20
|
if (prompt.configFileName) {
|
|
21
21
|
rawPaths.push(path.join(FREYA_HOME, 'config', prompt.configFileName));
|
|
22
|
-
rawPaths.push(path.join(
|
|
22
|
+
rawPaths.push(path.join(FREYA_LAUNCH, 'config', prompt.configFileName));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
rawPaths.push(path.join(FREYA_HOME, 'config', 'prompts', baseName));
|
|
26
|
-
rawPaths.push(path.join(
|
|
26
|
+
rawPaths.push(path.join(FREYA_LAUNCH, 'config', 'prompts', baseName));
|
|
27
27
|
rawPaths.push(prompt.defaultPath);
|
|
28
28
|
|
|
29
29
|
const candidates: string[] = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
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 { FREYA_APP, FREYA_HOME,
|
|
4
|
+
import { FREYA_APP, FREYA_HOME, FREYA_LAUNCH } from '../utils/paths.js';
|
|
5
5
|
|
|
6
6
|
export interface FreyaSkill {
|
|
7
7
|
id: string;
|
|
@@ -9,7 +9,7 @@ export interface FreyaSkill {
|
|
|
9
9
|
description: string;
|
|
10
10
|
content: string;
|
|
11
11
|
enabled: boolean;
|
|
12
|
-
source: 'builtin' | '
|
|
12
|
+
source: 'builtin' | 'launch' | 'runtime';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** 技能注册表,从 skills/ 目录加载 Markdown 格式技能并管理软开关状态 */
|
|
@@ -20,7 +20,7 @@ export class FreyaSkillRegistry {
|
|
|
20
20
|
async loadSkills(context: FreyaContext): Promise<void> {
|
|
21
21
|
this.context = context;
|
|
22
22
|
const defaultSkillsDir = path.join(FREYA_APP, 'skills');
|
|
23
|
-
const
|
|
23
|
+
const launchSkillsDir = path.join(FREYA_LAUNCH, 'skills');
|
|
24
24
|
const runtimeSkillsDir = path.join(FREYA_HOME, 'skills');
|
|
25
25
|
const configSkillsPath = path.join(FREYA_HOME, 'config', 'skills.json');
|
|
26
26
|
|
|
@@ -29,11 +29,11 @@ export class FreyaSkillRegistry {
|
|
|
29
29
|
await this.loadSkillsFromDirectory(defaultSkillsDir, 'builtin', context);
|
|
30
30
|
|
|
31
31
|
const resolvedDefault = path.resolve(defaultSkillsDir);
|
|
32
|
-
const
|
|
32
|
+
const resolvedLaunch = path.resolve(launchSkillsDir);
|
|
33
33
|
const resolvedRuntime = path.resolve(runtimeSkillsDir);
|
|
34
34
|
|
|
35
|
-
if (
|
|
36
|
-
await this.loadSkillsFromDirectory(
|
|
35
|
+
if (resolvedLaunch !== resolvedDefault && resolvedLaunch !== resolvedRuntime) {
|
|
36
|
+
await this.loadSkillsFromDirectory(launchSkillsDir, 'launch', context);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
await this.loadSkillsFromDirectory(runtimeSkillsDir, 'runtime', context);
|
|
@@ -77,7 +77,7 @@ export class FreyaSkillRegistry {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/** 从指定目录加载技能到内存注册表中 */
|
|
80
|
-
private async loadSkillsFromDirectory(dirPath: string, source: 'builtin' | '
|
|
80
|
+
private async loadSkillsFromDirectory(dirPath: string, source: 'builtin' | 'launch' | 'runtime', context: FreyaContext): Promise<void> {
|
|
81
81
|
try {
|
|
82
82
|
const files = await fs.readdir(dirPath);
|
|
83
83
|
for (const file of files) {
|