mioku 0.8.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.
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/cli.cjs +426 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +425 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +616 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +529 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +529 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +613 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const fs_promises = require_chunk.__toESM(require("fs/promises"));
|
|
3
|
+
const path = require_chunk.__toESM(require("path"));
|
|
4
|
+
const fs = require_chunk.__toESM(require("fs"));
|
|
5
|
+
|
|
6
|
+
//#region src/core/logger.ts
|
|
7
|
+
const fallbackLogger = {
|
|
8
|
+
error: (...args) => console.error(...args),
|
|
9
|
+
warn: (...args) => console.warn(...args),
|
|
10
|
+
info: (...args) => console.info(...args),
|
|
11
|
+
debug: (...args) => console.debug(...args),
|
|
12
|
+
trace: (...args) => console.debug(...args)
|
|
13
|
+
};
|
|
14
|
+
let activeLogger = fallbackLogger;
|
|
15
|
+
function setMiokuLogger(logger$1) {
|
|
16
|
+
activeLogger = logger$1 || fallbackLogger;
|
|
17
|
+
}
|
|
18
|
+
const logger = {
|
|
19
|
+
error: (...args) => activeLogger.error(...args),
|
|
20
|
+
warn: (...args) => activeLogger.warn(...args),
|
|
21
|
+
info: (...args) => activeLogger.info(...args),
|
|
22
|
+
debug: (...args) => activeLogger.debug(...args),
|
|
23
|
+
trace: (...args) => activeLogger.trace?.(...args)
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/core/plugin-linker.ts
|
|
28
|
+
const DEFAULT_RUNTIME_PLUGINS_DIR = ".mioku/plugins";
|
|
29
|
+
async function pathExists$3(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
await fs_promises.access(filePath);
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function removeIfBrokenSymlink(entryPath) {
|
|
38
|
+
let stat;
|
|
39
|
+
try {
|
|
40
|
+
stat = await fs_promises.lstat(entryPath);
|
|
41
|
+
} catch {
|
|
42
|
+
return "removed";
|
|
43
|
+
}
|
|
44
|
+
if (!stat.isSymbolicLink()) return "blocked";
|
|
45
|
+
try {
|
|
46
|
+
await fs_promises.realpath(entryPath);
|
|
47
|
+
return "ok";
|
|
48
|
+
} catch {
|
|
49
|
+
await fs_promises.rm(entryPath, { force: true });
|
|
50
|
+
logger.warn(`[plugin-linker] Removed broken plugin link: ${entryPath}`);
|
|
51
|
+
return "removed";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function relativeSymlinkTarget(linkPath, targetPath) {
|
|
55
|
+
const relativePath = path.relative(path.dirname(linkPath), targetPath);
|
|
56
|
+
return relativePath || ".";
|
|
57
|
+
}
|
|
58
|
+
async function ensurePluginLink(runtimePluginsDir, metadata) {
|
|
59
|
+
const linkPath = path.join(runtimePluginsDir, metadata.name);
|
|
60
|
+
const targetPath = metadata.path;
|
|
61
|
+
let stat;
|
|
62
|
+
try {
|
|
63
|
+
stat = await fs_promises.lstat(linkPath);
|
|
64
|
+
} catch {
|
|
65
|
+
stat = null;
|
|
66
|
+
}
|
|
67
|
+
if (stat?.isSymbolicLink()) try {
|
|
68
|
+
const currentTarget = await fs_promises.realpath(linkPath);
|
|
69
|
+
const expectedTarget = await fs_promises.realpath(targetPath);
|
|
70
|
+
if (currentTarget === expectedTarget) return true;
|
|
71
|
+
await fs_promises.rm(linkPath, { force: true });
|
|
72
|
+
logger.info(`[plugin-linker] Rebuilding plugin link: ${metadata.name}`);
|
|
73
|
+
} catch {
|
|
74
|
+
await fs_promises.rm(linkPath, { force: true });
|
|
75
|
+
logger.warn(`[plugin-linker] Removed broken plugin link: ${linkPath}`);
|
|
76
|
+
}
|
|
77
|
+
else if (stat) {
|
|
78
|
+
logger.warn(`[plugin-linker] ${linkPath} exists and is not a symlink, skip linking ${metadata.name}`);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (!await pathExists$3(targetPath)) {
|
|
82
|
+
logger.warn(`[plugin-linker] Plugin target missing, skip linking ${metadata.name}: ${targetPath}`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
await fs_promises.symlink(relativeSymlinkTarget(linkPath, targetPath), linkPath, "dir");
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
async function prepareRuntimePluginLinks(plugins, runtimePluginsDir = path.resolve(process.cwd(), DEFAULT_RUNTIME_PLUGINS_DIR)) {
|
|
89
|
+
await fs_promises.mkdir(runtimePluginsDir, { recursive: true });
|
|
90
|
+
const discoveredNames = new Set(plugins.map((plugin) => plugin.name));
|
|
91
|
+
const entries = await fs_promises.readdir(runtimePluginsDir, { withFileTypes: true });
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
const entryPath = path.join(runtimePluginsDir, entry.name);
|
|
94
|
+
const symlinkState = await removeIfBrokenSymlink(entryPath);
|
|
95
|
+
if (symlinkState !== "ok") continue;
|
|
96
|
+
if (entry.isSymbolicLink() && !discoveredNames.has(entry.name)) {
|
|
97
|
+
await fs_promises.rm(entryPath, { force: true });
|
|
98
|
+
logger.info(`[plugin-linker] Removed stale plugin link: ${entry.name}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const linkedNames = [];
|
|
102
|
+
for (const metadata of plugins) if (await ensurePluginLink(runtimePluginsDir, metadata)) linkedNames.push(metadata.name);
|
|
103
|
+
return linkedNames;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/core/plugin-manager.ts
|
|
108
|
+
const PLUGIN_MANAGER_SYMBOL = Symbol.for("mioku.plugin-manager");
|
|
109
|
+
async function pathExists$2(filePath) {
|
|
110
|
+
try {
|
|
111
|
+
await fs_promises.access(filePath);
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 插件管理器
|
|
119
|
+
*
|
|
120
|
+
* Discover and manage plugins from both local directories and node_modules.
|
|
121
|
+
*/
|
|
122
|
+
var PluginManager = class PluginManager {
|
|
123
|
+
pluginMetadata = new Map();
|
|
124
|
+
static getInstance() {
|
|
125
|
+
const g = global;
|
|
126
|
+
if (!g[PLUGIN_MANAGER_SYMBOL]) g[PLUGIN_MANAGER_SYMBOL] = new PluginManager();
|
|
127
|
+
return g[PLUGIN_MANAGER_SYMBOL];
|
|
128
|
+
}
|
|
129
|
+
async discoverPlugins(miokuConfig = {}) {
|
|
130
|
+
const configuredPluginsDir = miokuConfig.plugins_dir;
|
|
131
|
+
const pluginsDir = configuredPluginsDir && configuredPluginsDir !== DEFAULT_RUNTIME_PLUGINS_DIR ? path.resolve(process.cwd(), configuredPluginsDir) : path.resolve(process.cwd(), "plugins");
|
|
132
|
+
this.pluginMetadata.clear();
|
|
133
|
+
if (!await pathExists$2(pluginsDir)) (0, fs.mkdirSync)(pluginsDir, { recursive: true });
|
|
134
|
+
const discovered = [];
|
|
135
|
+
if (await pathExists$2(pluginsDir)) {
|
|
136
|
+
const localPlugins = await this.discoverFromDir(pluginsDir);
|
|
137
|
+
discovered.push(...localPlugins);
|
|
138
|
+
}
|
|
139
|
+
const nodeModulesPlugins = await this.discoverFromNodeModules();
|
|
140
|
+
discovered.push(...nodeModulesPlugins);
|
|
141
|
+
logger.info(`O.o 发现了 ${this.pluginMetadata.size} 个插件`);
|
|
142
|
+
return Array.from(this.pluginMetadata.values());
|
|
143
|
+
}
|
|
144
|
+
async discoverFromDir(pluginsDir) {
|
|
145
|
+
const discovered = [];
|
|
146
|
+
try {
|
|
147
|
+
const entries = await fs_promises.readdir(pluginsDir, { withFileTypes: true });
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const pluginPath = path.join(pluginsDir, entry.name);
|
|
150
|
+
const metadataPath = await this.resolveDirectoryPath(pluginPath);
|
|
151
|
+
if (!metadataPath) continue;
|
|
152
|
+
const metadata = await this.loadPluginMetadata(entry.name, pluginPath);
|
|
153
|
+
if (metadata) {
|
|
154
|
+
discovered.push(metadata);
|
|
155
|
+
this.pluginMetadata.set(metadata.name, metadata);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
logger.error(`扫描插件目录失败: ${error}`);
|
|
160
|
+
}
|
|
161
|
+
return discovered;
|
|
162
|
+
}
|
|
163
|
+
async discoverFromNodeModules() {
|
|
164
|
+
const discovered = [];
|
|
165
|
+
const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
|
|
166
|
+
if (!await pathExists$2(nodeModulesPath)) return discovered;
|
|
167
|
+
try {
|
|
168
|
+
const entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
if (!entry.name.startsWith("mioku-plugin-")) continue;
|
|
171
|
+
const pluginName = entry.name.replace(/^mioku-plugin-/, "");
|
|
172
|
+
const pluginPath = path.join(nodeModulesPath, entry.name);
|
|
173
|
+
const metadata = await this.loadPluginMetadata(pluginName, pluginPath);
|
|
174
|
+
if (metadata) {
|
|
175
|
+
discovered.push(metadata);
|
|
176
|
+
this.pluginMetadata.set(metadata.name, metadata);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logger.debug(`扫描 node_modules 插件失败: ${error}`);
|
|
181
|
+
}
|
|
182
|
+
return discovered;
|
|
183
|
+
}
|
|
184
|
+
async resolveDirectoryPath(entryPath) {
|
|
185
|
+
try {
|
|
186
|
+
const stat = await fs_promises.stat(entryPath);
|
|
187
|
+
return stat.isDirectory() ? entryPath : null;
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async loadPluginMetadata(name, pluginPath) {
|
|
193
|
+
let resolvedPath = pluginPath;
|
|
194
|
+
try {
|
|
195
|
+
resolvedPath = await fs_promises.realpath(pluginPath);
|
|
196
|
+
} catch {}
|
|
197
|
+
const packageJsonPath = path.join(resolvedPath, "package.json");
|
|
198
|
+
let packageJson = null;
|
|
199
|
+
try {
|
|
200
|
+
const content = await fs_promises.readFile(packageJsonPath, "utf-8");
|
|
201
|
+
packageJson = JSON.parse(content);
|
|
202
|
+
} catch {}
|
|
203
|
+
const metadata = {
|
|
204
|
+
name,
|
|
205
|
+
version: packageJson?.version || "0.0.0",
|
|
206
|
+
description: packageJson?.description,
|
|
207
|
+
path: resolvedPath,
|
|
208
|
+
packageJson,
|
|
209
|
+
config: packageJson?.mioku || {}
|
|
210
|
+
};
|
|
211
|
+
return metadata;
|
|
212
|
+
}
|
|
213
|
+
collectRequiredServices() {
|
|
214
|
+
const services = new Set();
|
|
215
|
+
for (const metadata of this.pluginMetadata.values()) if (metadata.config.services) metadata.config.services.forEach((s) => services.add(s));
|
|
216
|
+
return services;
|
|
217
|
+
}
|
|
218
|
+
getPluginMetadata(name) {
|
|
219
|
+
return this.pluginMetadata.get(name);
|
|
220
|
+
}
|
|
221
|
+
getAllMetadata() {
|
|
222
|
+
return Array.from(this.pluginMetadata.values());
|
|
223
|
+
}
|
|
224
|
+
reset() {
|
|
225
|
+
this.pluginMetadata.clear();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var plugin_manager_default = PluginManager.getInstance();
|
|
229
|
+
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/core/service-manager.ts
|
|
232
|
+
const SERVICE_MANAGER_SYMBOL = Symbol.for("mioku.service-manager");
|
|
233
|
+
async function pathExists$1(filePath) {
|
|
234
|
+
try {
|
|
235
|
+
await fs_promises.access(filePath);
|
|
236
|
+
return true;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 服务管理器
|
|
243
|
+
*/
|
|
244
|
+
var ServiceManager = class ServiceManager {
|
|
245
|
+
services = new Map();
|
|
246
|
+
serviceMetadata = new Map();
|
|
247
|
+
servicesDir = "services";
|
|
248
|
+
static getInstance() {
|
|
249
|
+
const g = global;
|
|
250
|
+
if (!g[SERVICE_MANAGER_SYMBOL]) g[SERVICE_MANAGER_SYMBOL] = new ServiceManager();
|
|
251
|
+
return g[SERVICE_MANAGER_SYMBOL];
|
|
252
|
+
}
|
|
253
|
+
async discoverServices(miokuConfig = {}) {
|
|
254
|
+
if (miokuConfig.services_dir) this.servicesDir = path.resolve(process.cwd(), miokuConfig.services_dir);
|
|
255
|
+
else this.servicesDir = path.resolve(process.cwd(), "services");
|
|
256
|
+
const discovered = [];
|
|
257
|
+
this.serviceMetadata.clear();
|
|
258
|
+
if ((0, fs.existsSync)(this.servicesDir)) {
|
|
259
|
+
const localServices = await this.discoverFromDir(this.servicesDir);
|
|
260
|
+
discovered.push(...localServices);
|
|
261
|
+
} else (0, fs.mkdirSync)(this.servicesDir, { recursive: true });
|
|
262
|
+
await this.loadBuiltinServices();
|
|
263
|
+
logger.info(`o.O 发现了 ${this.serviceMetadata.size} 个服务`);
|
|
264
|
+
return Array.from(this.serviceMetadata.values());
|
|
265
|
+
}
|
|
266
|
+
async discoverFromDir(servicesDir) {
|
|
267
|
+
const discovered = [];
|
|
268
|
+
try {
|
|
269
|
+
const entries = await fs_promises.readdir(servicesDir, { withFileTypes: true });
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
if (!entry.isDirectory()) continue;
|
|
272
|
+
const servicePath = path.join(servicesDir, entry.name);
|
|
273
|
+
const metadata = await this.loadServiceMetadata(entry.name, servicePath);
|
|
274
|
+
if (metadata) {
|
|
275
|
+
discovered.push(metadata);
|
|
276
|
+
this.serviceMetadata.set(entry.name, metadata);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
logger.error(`扫描服务目录失败: ${error}`);
|
|
281
|
+
}
|
|
282
|
+
return discovered;
|
|
283
|
+
}
|
|
284
|
+
async loadServiceMetadata(name, servicePath) {
|
|
285
|
+
let resolvedPath = servicePath;
|
|
286
|
+
try {
|
|
287
|
+
resolvedPath = await fs_promises.realpath(servicePath);
|
|
288
|
+
} catch {}
|
|
289
|
+
const packageJsonPath = path.join(resolvedPath, "package.json");
|
|
290
|
+
if (!await pathExists$1(packageJsonPath)) return null;
|
|
291
|
+
try {
|
|
292
|
+
const packageJson = JSON.parse(await fs_promises.readFile(packageJsonPath, "utf-8"));
|
|
293
|
+
const metadata = {
|
|
294
|
+
name,
|
|
295
|
+
version: packageJson.version || "0.0.0",
|
|
296
|
+
description: packageJson.description,
|
|
297
|
+
path: resolvedPath,
|
|
298
|
+
packageJson
|
|
299
|
+
};
|
|
300
|
+
return metadata;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
logger.error(`解析服务 ${name} 失败: ${error.message}`);
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Load built-in services from the package
|
|
308
|
+
*/
|
|
309
|
+
async loadBuiltinServices() {
|
|
310
|
+
await this.discoverFromNodeModules();
|
|
311
|
+
}
|
|
312
|
+
async discoverFromNodeModules() {
|
|
313
|
+
const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
|
|
314
|
+
if (!await pathExists$1(nodeModulesPath)) return;
|
|
315
|
+
try {
|
|
316
|
+
const entries = await fs_promises.readdir(nodeModulesPath, { withFileTypes: true });
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
if (!entry.name.startsWith("mioku-service-")) continue;
|
|
319
|
+
const serviceName = entry.name.replace(/^mioku-service-/, "");
|
|
320
|
+
const servicePath = path.join(nodeModulesPath, entry.name);
|
|
321
|
+
const metadata = await this.loadServiceMetadata(serviceName, servicePath);
|
|
322
|
+
if (metadata) this.serviceMetadata.set(serviceName, metadata);
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
logger.debug(`扫描 node_modules 服务失败: ${error}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async checkMissingServices(requiredServices) {
|
|
329
|
+
const missing = [];
|
|
330
|
+
for (const serviceName of requiredServices) if (!this.serviceMetadata.has(serviceName)) missing.push(serviceName);
|
|
331
|
+
return missing;
|
|
332
|
+
}
|
|
333
|
+
async loadAllServices(ctx) {
|
|
334
|
+
const allMetadata = Array.from(this.serviceMetadata.values());
|
|
335
|
+
logger.info(`O.o 准备加载 ${allMetadata.length} 个服务...`);
|
|
336
|
+
for (const metadata of allMetadata) await this.loadService(metadata, ctx);
|
|
337
|
+
}
|
|
338
|
+
async loadService(metadata, ctx) {
|
|
339
|
+
try {
|
|
340
|
+
const indexPath = path.join(metadata.path, "index.ts");
|
|
341
|
+
const indexJsPath = path.join(metadata.path, "index.js");
|
|
342
|
+
const indexExists = await pathExists$1(indexPath);
|
|
343
|
+
const indexJsExists = await pathExists$1(indexJsPath);
|
|
344
|
+
const entryPoint = indexExists ? indexPath : indexJsPath;
|
|
345
|
+
if (!entryPoint || !indexExists && !indexJsExists) {
|
|
346
|
+
logger.error(`服务 ${metadata.name} 入口丢失`);
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
let importPath = entryPoint;
|
|
350
|
+
if (process.platform === "win32") importPath = "file:///" + entryPoint.replace(/\\/g, "/");
|
|
351
|
+
const serviceModule = await import(importPath);
|
|
352
|
+
const service = serviceModule.default || serviceModule;
|
|
353
|
+
if (!service || typeof service.init !== "function") return false;
|
|
354
|
+
await service.init();
|
|
355
|
+
if (service.api) {
|
|
356
|
+
if (!ctx.services) ctx.services = {};
|
|
357
|
+
ctx.services[metadata.name] = service.api;
|
|
358
|
+
}
|
|
359
|
+
this.services.set(metadata.name, service);
|
|
360
|
+
return true;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
logger.error(`加载服务 ${metadata.name} 失败: ${error.message}`);
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Register a builtin service directly
|
|
368
|
+
*/
|
|
369
|
+
registerBuiltinService(name, service) {
|
|
370
|
+
this.services.set(name, service);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get a loaded service
|
|
374
|
+
*/
|
|
375
|
+
getService(name) {
|
|
376
|
+
return this.services.get(name);
|
|
377
|
+
}
|
|
378
|
+
async disposeAll() {
|
|
379
|
+
for (const [name, service] of this.services) if (service.dispose) await service.dispose();
|
|
380
|
+
this.services.clear();
|
|
381
|
+
}
|
|
382
|
+
reset() {
|
|
383
|
+
this.services.clear();
|
|
384
|
+
this.serviceMetadata.clear();
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
var service_manager_default = ServiceManager.getInstance();
|
|
388
|
+
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/core/plugin-artifact-registry.ts
|
|
391
|
+
async function pathExists(filePath) {
|
|
392
|
+
try {
|
|
393
|
+
await fs_promises.access(filePath);
|
|
394
|
+
return true;
|
|
395
|
+
} catch {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function toImportPath(filePath) {
|
|
400
|
+
if (process.platform === "win32") return "file:///" + filePath.replace(/\\/g, "/");
|
|
401
|
+
return filePath;
|
|
402
|
+
}
|
|
403
|
+
function isAISkill(value) {
|
|
404
|
+
return value && typeof value === "object" && typeof value.name === "string" && Array.isArray(value.tools);
|
|
405
|
+
}
|
|
406
|
+
function extractSkills(moduleExports) {
|
|
407
|
+
const candidates = [
|
|
408
|
+
moduleExports?.default,
|
|
409
|
+
moduleExports?.skills,
|
|
410
|
+
moduleExports
|
|
411
|
+
];
|
|
412
|
+
for (const candidate of candidates) {
|
|
413
|
+
if (Array.isArray(candidate)) return candidate.filter(isAISkill);
|
|
414
|
+
if (isAISkill(candidate)) return [candidate];
|
|
415
|
+
}
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
async function resolveSkillsEntry(pluginPath) {
|
|
419
|
+
const tsPath = path.join(pluginPath, "skills.ts");
|
|
420
|
+
if (await pathExists(tsPath)) return tsPath;
|
|
421
|
+
const jsPath = path.join(pluginPath, "skills.js");
|
|
422
|
+
if (await pathExists(jsPath)) return jsPath;
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Auto-register plugin help manifests and AI skills
|
|
427
|
+
*/
|
|
428
|
+
async function registerPluginArtifacts(ctx) {
|
|
429
|
+
const enabledPlugins = new Set(Array.isArray(ctx.botConfig?.plugins) ? ctx.botConfig.plugins : []);
|
|
430
|
+
const pluginMetadata = plugin_manager_default.getAllMetadata().filter((metadata) => enabledPlugins.size > 0 ? enabledPlugins.has(metadata.name) : true);
|
|
431
|
+
const helpService = ctx.services?.help;
|
|
432
|
+
const aiService = ctx.services?.ai;
|
|
433
|
+
if (helpService) {
|
|
434
|
+
let helpCount = 0;
|
|
435
|
+
for (const metadata of pluginMetadata) {
|
|
436
|
+
if (!metadata.config.help) continue;
|
|
437
|
+
helpService.registerHelp(metadata.name, metadata.config.help);
|
|
438
|
+
helpCount += 1;
|
|
439
|
+
}
|
|
440
|
+
logger.info(`[plugin-artifacts] Registered ${helpCount} help manifest(s)`);
|
|
441
|
+
}
|
|
442
|
+
if (!aiService) return;
|
|
443
|
+
let skillCount = 0;
|
|
444
|
+
for (const metadata of pluginMetadata) {
|
|
445
|
+
const skillsEntry = await resolveSkillsEntry(metadata.path);
|
|
446
|
+
if (!skillsEntry) continue;
|
|
447
|
+
try {
|
|
448
|
+
const moduleExports = await import(toImportPath(skillsEntry));
|
|
449
|
+
const skills = extractSkills(moduleExports);
|
|
450
|
+
if (skills.length === 0) {
|
|
451
|
+
logger.warn(`[plugin-artifacts] Plugin ${metadata.name} has ${path.basename(skillsEntry)} but exported no valid skill`);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
for (const skill of skills) {
|
|
455
|
+
aiService.registerSkill(skill);
|
|
456
|
+
skillCount += 1;
|
|
457
|
+
}
|
|
458
|
+
} catch (error) {
|
|
459
|
+
logger.error(`[plugin-artifacts] Failed to load skills for plugin ${metadata.name}: ${error?.message || error}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
logger.info(`[plugin-artifacts] Registered ${skillCount} skill(s)`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/service-types.ts
|
|
467
|
+
const TOOL_RESULT_FOLLOWUP_KEY = "__miokuFollowup";
|
|
468
|
+
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region src/core/data-paths.ts
|
|
471
|
+
/**
|
|
472
|
+
* Get the data directory for a specific plugin
|
|
473
|
+
* Returns: {cwd}/data/{pluginName}
|
|
474
|
+
*/
|
|
475
|
+
function getPluginDataDir(pluginName) {
|
|
476
|
+
const dataDir = path.join(process.cwd(), "data", pluginName);
|
|
477
|
+
return dataDir;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Get the data directory for a service
|
|
481
|
+
* Returns: {cwd}/data/{serviceName}
|
|
482
|
+
*/
|
|
483
|
+
function getServiceDataDir(serviceName) {
|
|
484
|
+
const dataDir = path.join(process.cwd(), "data", serviceName);
|
|
485
|
+
return dataDir;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Get the main data directory
|
|
489
|
+
* Returns: {cwd}/data
|
|
490
|
+
*/
|
|
491
|
+
function getDataDir() {
|
|
492
|
+
return path.join(process.cwd(), "data");
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get the config directory for a plugin
|
|
496
|
+
* Returns: {cwd}/config/{pluginName}
|
|
497
|
+
*/
|
|
498
|
+
function getPluginConfigDir(pluginName) {
|
|
499
|
+
const configDir = path.join(process.cwd(), "config", pluginName);
|
|
500
|
+
return configDir;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Get the config directory for a service
|
|
504
|
+
* Returns: {cwd}/config/{serviceName}
|
|
505
|
+
*/
|
|
506
|
+
function getServiceConfigDir(serviceName) {
|
|
507
|
+
const configDir = path.join(process.cwd(), "config", serviceName);
|
|
508
|
+
return configDir;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Get the main config directory
|
|
512
|
+
* Returns: {cwd}/config
|
|
513
|
+
*/
|
|
514
|
+
function getConfigDir() {
|
|
515
|
+
return path.join(process.cwd(), "config");
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Ensure a directory exists, creating it if necessary
|
|
519
|
+
*/
|
|
520
|
+
function ensureDataDir(pluginName) {
|
|
521
|
+
const dir = getPluginDataDir(pluginName);
|
|
522
|
+
const { existsSync: existsSync$2, mkdirSync: mkdirSync$3 } = require("fs");
|
|
523
|
+
if (!existsSync$2(dir)) mkdirSync$3(dir, { recursive: true });
|
|
524
|
+
return dir;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
//#endregion
|
|
528
|
+
//#region src/core/plugin-runtime-state.ts
|
|
529
|
+
const runtimeState = {};
|
|
530
|
+
/**
|
|
531
|
+
* Get the runtime state for a plugin
|
|
532
|
+
*/
|
|
533
|
+
function getPluginRuntimeState(pluginName) {
|
|
534
|
+
if (!runtimeState[pluginName]) runtimeState[pluginName] = {};
|
|
535
|
+
return runtimeState[pluginName];
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Set/update the runtime state for a plugin
|
|
539
|
+
*/
|
|
540
|
+
function setPluginRuntimeState(pluginName, state) {
|
|
541
|
+
if (!runtimeState[pluginName]) runtimeState[pluginName] = {};
|
|
542
|
+
Object.assign(runtimeState[pluginName], state);
|
|
543
|
+
return runtimeState[pluginName];
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Reset the runtime state for a plugin
|
|
547
|
+
*/
|
|
548
|
+
function resetPluginRuntimeState(pluginName) {
|
|
549
|
+
delete runtimeState[pluginName];
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/index.ts
|
|
554
|
+
function definePlugin(plugin) {
|
|
555
|
+
return plugin;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Start Mioku with plugin and service discovery
|
|
559
|
+
*/
|
|
560
|
+
async function start(options = {}) {
|
|
561
|
+
const { cwd = process.cwd() } = options;
|
|
562
|
+
if (cwd) process.chdir(cwd);
|
|
563
|
+
const { start: startMioki, logger: logger$1, botConfig } = await import("mioki");
|
|
564
|
+
setMiokuLogger(logger$1);
|
|
565
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
566
|
+
let miokuConfig = {};
|
|
567
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
568
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
569
|
+
miokuConfig = pkg.mioki || {};
|
|
570
|
+
}
|
|
571
|
+
logger$1.info("こんにちは..");
|
|
572
|
+
logger$1.info("---------------------------------------");
|
|
573
|
+
logger$1.info("---------- Mioku 正在启动 ------------");
|
|
574
|
+
logger$1.info("---------------------------------------");
|
|
575
|
+
const requiredDirs = [
|
|
576
|
+
"data",
|
|
577
|
+
"config",
|
|
578
|
+
"temp"
|
|
579
|
+
];
|
|
580
|
+
for (const dir of requiredDirs) if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
581
|
+
logger$1.info("O.o Miku 正在翻找插件..");
|
|
582
|
+
const discoveredPlugins = await plugin_manager_default.discoverPlugins(miokuConfig);
|
|
583
|
+
logger$1.info(`O.o 共发现 ${discoveredPlugins.length} 个插件: ${discoveredPlugins.map((p) => p.name).join(", ")}`);
|
|
584
|
+
const runtimePluginsDir = path.resolve(process.cwd(), DEFAULT_RUNTIME_PLUGINS_DIR);
|
|
585
|
+
const linkedPluginNames = await prepareRuntimePluginLinks(discoveredPlugins, runtimePluginsDir);
|
|
586
|
+
botConfig.plugins_dir = DEFAULT_RUNTIME_PLUGINS_DIR;
|
|
587
|
+
logger$1.info("o.O Miku 正在翻找服务..");
|
|
588
|
+
await service_manager_default.discoverServices(miokuConfig);
|
|
589
|
+
const requiredServices = plugin_manager_default.collectRequiredServices();
|
|
590
|
+
const missingServices = await service_manager_default.checkMissingServices(requiredServices);
|
|
591
|
+
if (missingServices.length > 0) logger$1.warn(`发现缺失服务: ${missingServices.join(", ")}`);
|
|
592
|
+
const discoveredPluginNames = linkedPluginNames;
|
|
593
|
+
for (const name of discoveredPluginNames) if (!botConfig.plugins.includes(name)) botConfig.plugins.push(name);
|
|
594
|
+
await startMioki({ cwd });
|
|
595
|
+
}
|
|
596
|
+
const version = "1.0.0";
|
|
597
|
+
|
|
598
|
+
//#endregion
|
|
599
|
+
exports.TOOL_RESULT_FOLLOWUP_KEY = TOOL_RESULT_FOLLOWUP_KEY;
|
|
600
|
+
exports.definePlugin = definePlugin;
|
|
601
|
+
exports.ensureDataDir = ensureDataDir;
|
|
602
|
+
exports.getConfigDir = getConfigDir;
|
|
603
|
+
exports.getDataDir = getDataDir;
|
|
604
|
+
exports.getPluginConfigDir = getPluginConfigDir;
|
|
605
|
+
exports.getPluginDataDir = getPluginDataDir;
|
|
606
|
+
exports.getPluginRuntimeState = getPluginRuntimeState;
|
|
607
|
+
exports.getServiceConfigDir = getServiceConfigDir;
|
|
608
|
+
exports.getServiceDataDir = getServiceDataDir;
|
|
609
|
+
exports.pluginManager = plugin_manager_default;
|
|
610
|
+
exports.registerPluginArtifacts = registerPluginArtifacts;
|
|
611
|
+
exports.resetPluginRuntimeState = resetPluginRuntimeState;
|
|
612
|
+
exports.serviceManager = service_manager_default;
|
|
613
|
+
exports.setPluginRuntimeState = setPluginRuntimeState;
|
|
614
|
+
exports.start = start;
|
|
615
|
+
exports.version = version;
|
|
616
|
+
//# sourceMappingURL=index.cjs.map
|