mioku 0.9.2 → 0.9.3

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