@yunzai-ng/core 0.1.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/LICENSE +661 -0
- package/README.md +51 -0
- package/dist/adapter/accounts.d.ts +174 -0
- package/dist/adapter/accounts.d.ts.map +1 -0
- package/dist/adapter/accounts.js +653 -0
- package/dist/adapter/accounts.js.map +1 -0
- package/dist/adapter/bots.d.ts +142 -0
- package/dist/adapter/bots.d.ts.map +1 -0
- package/dist/adapter/bots.js +454 -0
- package/dist/adapter/bots.js.map +1 -0
- package/dist/adapter/host.d.ts +114 -0
- package/dist/adapter/host.d.ts.map +1 -0
- package/dist/adapter/host.js +90 -0
- package/dist/adapter/host.js.map +1 -0
- package/dist/adapter/login.d.ts +174 -0
- package/dist/adapter/login.d.ts.map +1 -0
- package/dist/adapter/login.js +345 -0
- package/dist/adapter/login.js.map +1 -0
- package/dist/adapter/registry.d.ts +102 -0
- package/dist/adapter/registry.d.ts.map +1 -0
- package/dist/adapter/registry.js +150 -0
- package/dist/adapter/registry.js.map +1 -0
- package/dist/config/core-config.d.ts +151 -0
- package/dist/config/core-config.d.ts.map +1 -0
- package/dist/config/core-config.js +338 -0
- package/dist/config/core-config.js.map +1 -0
- package/dist/config/schema.d.ts +449 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +871 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.d.ts +184 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +428 -0
- package/dist/config/store.js.map +1 -0
- package/dist/config/yaml.d.ts +23 -0
- package/dist/config/yaml.d.ts.map +1 -0
- package/dist/config/yaml.js +108 -0
- package/dist/config/yaml.js.map +1 -0
- package/dist/http/client.d.ts +82 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +658 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/app.d.ts +244 -0
- package/dist/kernel/app.d.ts.map +1 -0
- package/dist/kernel/app.js +654 -0
- package/dist/kernel/app.js.map +1 -0
- package/dist/kernel/kv-sink.d.ts +34 -0
- package/dist/kernel/kv-sink.d.ts.map +1 -0
- package/dist/kernel/kv-sink.js +25 -0
- package/dist/kernel/kv-sink.js.map +1 -0
- package/dist/kernel/policy.d.ts +77 -0
- package/dist/kernel/policy.d.ts.map +1 -0
- package/dist/kernel/policy.js +99 -0
- package/dist/kernel/policy.js.map +1 -0
- package/dist/kernel/runtime.d.ts +117 -0
- package/dist/kernel/runtime.d.ts.map +1 -0
- package/dist/kernel/runtime.js +197 -0
- package/dist/kernel/runtime.js.map +1 -0
- package/dist/kernel/sql-sink.d.ts +36 -0
- package/dist/kernel/sql-sink.d.ts.map +1 -0
- package/dist/kernel/sql-sink.js +114 -0
- package/dist/kernel/sql-sink.js.map +1 -0
- package/dist/logger/format.d.ts +67 -0
- package/dist/logger/format.d.ts.map +1 -0
- package/dist/logger/format.js +218 -0
- package/dist/logger/format.js.map +1 -0
- package/dist/logger/index.d.ts +97 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +363 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/rotate.d.ts +47 -0
- package/dist/logger/rotate.d.ts.map +1 -0
- package/dist/logger/rotate.js +242 -0
- package/dist/logger/rotate.js.map +1 -0
- package/dist/message/segment.d.ts +255 -0
- package/dist/message/segment.d.ts.map +1 -0
- package/dist/message/segment.js +510 -0
- package/dist/message/segment.js.map +1 -0
- package/dist/message/split.d.ts +34 -0
- package/dist/message/split.d.ts.map +1 -0
- package/dist/message/split.js +79 -0
- package/dist/message/split.js.map +1 -0
- package/dist/message/target.d.ts +29 -0
- package/dist/message/target.d.ts.map +1 -0
- package/dist/message/target.js +34 -0
- package/dist/message/target.js.map +1 -0
- package/dist/pipeline/cooldown.d.ts +84 -0
- package/dist/pipeline/cooldown.d.ts.map +1 -0
- package/dist/pipeline/cooldown.js +94 -0
- package/dist/pipeline/cooldown.js.map +1 -0
- package/dist/pipeline/dispatch.d.ts +98 -0
- package/dist/pipeline/dispatch.d.ts.map +1 -0
- package/dist/pipeline/dispatch.js +323 -0
- package/dist/pipeline/dispatch.js.map +1 -0
- package/dist/pipeline/event.d.ts +130 -0
- package/dist/pipeline/event.d.ts.map +1 -0
- package/dist/pipeline/event.js +538 -0
- package/dist/pipeline/event.js.map +1 -0
- package/dist/pipeline/middleware.d.ts +63 -0
- package/dist/pipeline/middleware.d.ts.map +1 -0
- package/dist/pipeline/middleware.js +174 -0
- package/dist/pipeline/middleware.js.map +1 -0
- package/dist/pipeline/prompt.d.ts +80 -0
- package/dist/pipeline/prompt.d.ts.map +1 -0
- package/dist/pipeline/prompt.js +155 -0
- package/dist/pipeline/prompt.js.map +1 -0
- package/dist/pipeline/router.d.ts +98 -0
- package/dist/pipeline/router.d.ts.map +1 -0
- package/dist/pipeline/router.js +489 -0
- package/dist/pipeline/router.js.map +1 -0
- package/dist/platform/detect.d.ts +24 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +178 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/paths.d.ts +38 -0
- package/dist/platform/paths.d.ts.map +1 -0
- package/dist/platform/paths.js +129 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/system.d.ts +71 -0
- package/dist/platform/system.d.ts.map +1 -0
- package/dist/platform/system.js +242 -0
- package/dist/platform/system.js.map +1 -0
- package/dist/plugin/context.d.ts +92 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +573 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +92 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +82 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/discover.d.ts +106 -0
- package/dist/plugin/discover.d.ts.map +1 -0
- package/dist/plugin/discover.js +254 -0
- package/dist/plugin/discover.js.map +1 -0
- package/dist/plugin/events.d.ts +109 -0
- package/dist/plugin/events.d.ts.map +1 -0
- package/dist/plugin/events.js +209 -0
- package/dist/plugin/events.js.map +1 -0
- package/dist/plugin/hooks.d.ts +266 -0
- package/dist/plugin/hooks.d.ts.map +1 -0
- package/dist/plugin/hooks.js +84 -0
- package/dist/plugin/hooks.js.map +1 -0
- package/dist/plugin/host.d.ts +160 -0
- package/dist/plugin/host.d.ts.map +1 -0
- package/dist/plugin/host.js +546 -0
- package/dist/plugin/host.js.map +1 -0
- package/dist/plugin/market.d.ts +264 -0
- package/dist/plugin/market.d.ts.map +1 -0
- package/dist/plugin/market.js +700 -0
- package/dist/plugin/market.js.map +1 -0
- package/dist/plugin/services.d.ts +116 -0
- package/dist/plugin/services.d.ts.map +1 -0
- package/dist/plugin/services.js +213 -0
- package/dist/plugin/services.js.map +1 -0
- package/dist/plugin/tar.d.ts +65 -0
- package/dist/plugin/tar.d.ts.map +1 -0
- package/dist/plugin/tar.js +277 -0
- package/dist/plugin/tar.js.map +1 -0
- package/dist/render/registry.d.ts +127 -0
- package/dist/render/registry.d.ts.map +1 -0
- package/dist/render/registry.js +306 -0
- package/dist/render/registry.js.map +1 -0
- package/dist/scheduler/index.d.ts +67 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +279 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/server/api.d.ts +179 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +602 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/auth.d.ts +83 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +178 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/browse.d.ts +60 -0
- package/dist/server/browse.d.ts.map +1 -0
- package/dist/server/browse.js +187 -0
- package/dist/server/browse.js.map +1 -0
- package/dist/server/files.d.ts +27 -0
- package/dist/server/files.d.ts.map +1 -0
- package/dist/server/files.js +76 -0
- package/dist/server/files.js.map +1 -0
- package/dist/server/index.d.ts +189 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1123 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/table.d.ts +105 -0
- package/dist/server/table.d.ts.map +1 -0
- package/dist/server/table.js +289 -0
- package/dist/server/table.js.map +1 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +120 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.d.ts +73 -0
- package/dist/store/json.d.ts.map +1 -0
- package/dist/store/json.js +174 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/kv.d.ts +126 -0
- package/dist/store/kv.d.ts.map +1 -0
- package/dist/store/kv.js +232 -0
- package/dist/store/kv.js.map +1 -0
- package/dist/store/level.d.ts +103 -0
- package/dist/store/level.d.ts.map +1 -0
- package/dist/store/level.js +119 -0
- package/dist/store/level.js.map +1 -0
- package/dist/store/memory.d.ts +61 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +94 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/sql.d.ts +142 -0
- package/dist/store/sql.d.ts.map +1 -0
- package/dist/store/sql.js +246 -0
- package/dist/store/sql.js.map +1 -0
- package/dist/testing/fake.d.ts +109 -0
- package/dist/testing/fake.d.ts.map +1 -0
- package/dist/testing/fake.js +196 -0
- package/dist/testing/fake.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-adapter.d.ts +205 -0
- package/dist/testing/mock-adapter.d.ts.map +1 -0
- package/dist/testing/mock-adapter.js +309 -0
- package/dist/testing/mock-adapter.js.map +1 -0
- package/dist/util/deep.d.ts +108 -0
- package/dist/util/deep.d.ts.map +1 -0
- package/dist/util/deep.js +275 -0
- package/dist/util/deep.js.map +1 -0
- package/dist/util/defer.d.ts +107 -0
- package/dist/util/defer.d.ts.map +1 -0
- package/dist/util/defer.js +163 -0
- package/dist/util/defer.js.map +1 -0
- package/dist/util/dispose.d.ts +88 -0
- package/dist/util/dispose.d.ts.map +1 -0
- package/dist/util/dispose.js +143 -0
- package/dist/util/dispose.js.map +1 -0
- package/dist/util/duration.d.ts +28 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +77 -0
- package/dist/util/duration.js.map +1 -0
- package/dist/util/fs.d.ts +110 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +269 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/id.d.ts +49 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +87 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/lru.d.ts +74 -0
- package/dist/util/lru.d.ts.map +1 -0
- package/dist/util/lru.js +156 -0
- package/dist/util/lru.js.map +1 -0
- package/dist/util/queue.d.ts +64 -0
- package/dist/util/queue.d.ts.map +1 -0
- package/dist/util/queue.js +147 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/text.d.ts +86 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +182 -0
- package/dist/util/text.js.map +1 -0
- package/package.json +44 -0
- package/src/adapter/accounts.ts +758 -0
- package/src/adapter/bots.ts +582 -0
- package/src/adapter/host.ts +224 -0
- package/src/adapter/login.ts +481 -0
- package/src/adapter/registry.ts +202 -0
- package/src/config/core-config.test.ts +60 -0
- package/src/config/core-config.ts +401 -0
- package/src/config/schema.test.ts +179 -0
- package/src/config/schema.ts +1036 -0
- package/src/config/store.test.ts +245 -0
- package/src/config/store.ts +506 -0
- package/src/config/yaml.ts +119 -0
- package/src/http/client.test.ts +568 -0
- package/src/http/client.ts +777 -0
- package/src/index.ts +106 -0
- package/src/kernel/app.test.ts +267 -0
- package/src/kernel/app.ts +843 -0
- package/src/kernel/kv-sink.ts +58 -0
- package/src/kernel/policy.ts +120 -0
- package/src/kernel/runtime.test.ts +542 -0
- package/src/kernel/runtime.ts +307 -0
- package/src/kernel/sql-sink.ts +154 -0
- package/src/logger/format.ts +259 -0
- package/src/logger/index.ts +427 -0
- package/src/logger/rotate.ts +265 -0
- package/src/message/segment.test.ts +335 -0
- package/src/message/segment.ts +569 -0
- package/src/message/split.ts +97 -0
- package/src/message/target.ts +48 -0
- package/src/pipeline/cooldown.ts +136 -0
- package/src/pipeline/dispatch.ts +419 -0
- package/src/pipeline/event.ts +735 -0
- package/src/pipeline/middleware.test.ts +426 -0
- package/src/pipeline/middleware.ts +199 -0
- package/src/pipeline/prompt.ts +212 -0
- package/src/pipeline/router.test.ts +432 -0
- package/src/pipeline/router.ts +575 -0
- package/src/platform/detect.ts +178 -0
- package/src/platform/paths.ts +147 -0
- package/src/platform/system.test.ts +102 -0
- package/src/platform/system.ts +289 -0
- package/src/plugin/context.test.ts +459 -0
- package/src/plugin/context.ts +757 -0
- package/src/plugin/define.test.ts +120 -0
- package/src/plugin/define.ts +142 -0
- package/src/plugin/discover.test.ts +253 -0
- package/src/plugin/discover.ts +353 -0
- package/src/plugin/events.test.ts +162 -0
- package/src/plugin/events.ts +266 -0
- package/src/plugin/hooks.ts +373 -0
- package/src/plugin/host.test.ts +696 -0
- package/src/plugin/host.ts +738 -0
- package/src/plugin/market.test.ts +781 -0
- package/src/plugin/market.ts +870 -0
- package/src/plugin/services.test.ts +135 -0
- package/src/plugin/services.ts +261 -0
- package/src/plugin/tar.test.ts +158 -0
- package/src/plugin/tar.ts +309 -0
- package/src/render/registry.test.ts +127 -0
- package/src/render/registry.ts +439 -0
- package/src/scheduler/index.ts +357 -0
- package/src/server/api.test.ts +802 -0
- package/src/server/api.ts +869 -0
- package/src/server/auth.ts +201 -0
- package/src/server/browse.test.ts +193 -0
- package/src/server/browse.ts +240 -0
- package/src/server/files.ts +97 -0
- package/src/server/index.test.ts +566 -0
- package/src/server/index.ts +1386 -0
- package/src/server/table.ts +351 -0
- package/src/store/index.ts +156 -0
- package/src/store/json.ts +198 -0
- package/src/store/kv.test.ts +307 -0
- package/src/store/kv.ts +256 -0
- package/src/store/level.ts +167 -0
- package/src/store/memory.ts +109 -0
- package/src/store/sql.test.ts +204 -0
- package/src/store/sql.ts +324 -0
- package/src/testing/fake.ts +297 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/mock-adapter.ts +588 -0
- package/src/util/deep.ts +272 -0
- package/src/util/defer.ts +211 -0
- package/src/util/dispose.ts +169 -0
- package/src/util/duration.ts +82 -0
- package/src/util/fs.ts +279 -0
- package/src/util/id.ts +94 -0
- package/src/util/lru.ts +185 -0
- package/src/util/queue.ts +167 -0
- package/src/util/text.ts +189 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:应用生命周期 `create → start → stop`,装配全部内核子系统
|
|
3
|
+
* 依赖方向:依赖内核各模块(config / logger / store / http / plugin / platform);
|
|
4
|
+
* **不依赖任何插件**,这是本次重写的核心不变量,由 scripts/check-layering.mjs 守着
|
|
5
|
+
* 生命周期:一个进程一个实例;`stop()` 是终态,重启请新建实例
|
|
6
|
+
* 注意事项:装配顺序不是任意排列,以下七项是强制约束 ——
|
|
7
|
+
*
|
|
8
|
+
* **日志最先就绪,早于配置。** 否则「读配置时出错」无处记录,使用者只看到一个没有任何
|
|
9
|
+
* 输出的进程。代价是轮转参数只能取 schema 默认值,配置里的改动次轮启动才生效(会记日志)。
|
|
10
|
+
*
|
|
11
|
+
* **存储早于插件。** 插件加载失败要写状态、要记日志,两者都以 KV 为前提;反过来插件注册的
|
|
12
|
+
* KV 驱动本轮接管不了存储,见 kernel/kv-sink.ts。
|
|
13
|
+
*
|
|
14
|
+
* **子系统接缝可后填。** `hooks` 与 `subsystems` 是可变对象,装配完直接替换字段即可,
|
|
15
|
+
* 已加载的插件立即取得新实现(上下文是即时读取)。`server.enable` 为 false 时刻意不建实例,
|
|
16
|
+
* 让 `hooks.server` 保持 `unavailableServer()` —— 插件调 `ctx.route()` 会得到一条说明原因的
|
|
17
|
+
* 错误,而不是一次静默不生效的注册。
|
|
18
|
+
*
|
|
19
|
+
* **`listen()` 排在插件加载之后、`app/ready` 之前。** 前者保证路由已齐、没有「面板部分可用」
|
|
20
|
+
* 的窗口;后者是因为账号在 `app/ready` 里开始连接,反向 WebSocket 的适配器要求端点已可接入。
|
|
21
|
+
*
|
|
22
|
+
* **AppView 全部用 getter。** 它被插件长期持有;若是值快照,替换 bots 注册表后插件手里
|
|
23
|
+
* 那一份永远是空的。
|
|
24
|
+
*
|
|
25
|
+
* **面板 API 在 `new App(...)` 之后挂载。** 它要读 `app.status` 与 `app.startedAt`。方向仍
|
|
26
|
+
* 单向:`server/api.ts` 不认识 `App`,它收的是一组窄依赖加两个取值函数。
|
|
27
|
+
*
|
|
28
|
+
* **内核不提供面板前端,只检测有没有插件提供。** 内核侧不留兜底实现 —— 兜底会使「替换面板」
|
|
29
|
+
* 退化成必须改内核。
|
|
30
|
+
*/
|
|
31
|
+
import { createRequire } from "node:module";
|
|
32
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
33
|
+
import { CORE_CONFIG_NAME, applyLogLevel, coreConfigSchema, defineCoreConfig, loggerSettingsOf, serverSecurityWarning } from "../config/core-config.js";
|
|
34
|
+
import { createConfigStore } from "../config/store.js";
|
|
35
|
+
import { createManagedServer } from "../server/index.js";
|
|
36
|
+
import { registerApi, API_SCOPE } from "../server/api.js";
|
|
37
|
+
import { createHttpClient } from "../http/client.js";
|
|
38
|
+
import { createLoggerHub } from "../logger/index.js";
|
|
39
|
+
import { detectPlatform, sampleUsage } from "../platform/detect.js";
|
|
40
|
+
import { sampleSystem } from "../platform/system.js";
|
|
41
|
+
import { ensurePaths, resolvePaths } from "../platform/paths.js";
|
|
42
|
+
import { createEventBus } from "../plugin/events.js";
|
|
43
|
+
import { unavailableHooks } from "../plugin/hooks.js";
|
|
44
|
+
import { PluginHost, createPluginsView } from "../plugin/host.js";
|
|
45
|
+
import { PluginMarket } from "../plugin/market.js";
|
|
46
|
+
import { createServiceRegistry } from "../plugin/services.js";
|
|
47
|
+
import { openKv } from "../store/index.js";
|
|
48
|
+
import { withTimeout } from "../util/defer.js";
|
|
49
|
+
import { parseDuration } from "../util/duration.js";
|
|
50
|
+
import { createKvDriverSink } from "./kv-sink.js";
|
|
51
|
+
import { createPolicy } from "./policy.js";
|
|
52
|
+
import { installRuntime } from "./runtime.js";
|
|
53
|
+
import { createSqlSink } from "./sql-sink.js";
|
|
54
|
+
/** 读不到 package.json 时用的版本号 */
|
|
55
|
+
const FALLBACK_VERSION = "0.0.0-unknown";
|
|
56
|
+
/** 日志文件名前缀 */
|
|
57
|
+
const LOG_BASENAME = "yunzai";
|
|
58
|
+
/** `app/stopping` 的默认等待上限毫秒 */
|
|
59
|
+
const DEFAULT_STOP_TIMEOUT = 5000;
|
|
60
|
+
/** 用于读自身 package.json(ESM 里没有 __dirname,也不能 import 到 rootDir 之外) */
|
|
61
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
62
|
+
/**
|
|
63
|
+
* 读内核版本
|
|
64
|
+
*
|
|
65
|
+
* `src/kernel/app.ts` 与 `dist/kernel/app.js` 到 package.json 都是两级,
|
|
66
|
+
* 所以同一个相对路径在源码运行(vitest / tsx)与编译产物下都成立。
|
|
67
|
+
* @returns 版本号;读不到时返回占位值而不是抛错 —— 版本号读不到不该让机器人起不来
|
|
68
|
+
*/
|
|
69
|
+
function readVersion() {
|
|
70
|
+
try {
|
|
71
|
+
const pkg = requireFromHere("../../package.json");
|
|
72
|
+
return typeof pkg.version === "string" ? pkg.version : FALLBACK_VERSION;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return FALLBACK_VERSION;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 把配置里可能是相对路径的目录解析成绝对路径
|
|
80
|
+
* @param home 应用主目录
|
|
81
|
+
* @param value 配置值
|
|
82
|
+
* @returns 绝对路径;配置为空时 undefined
|
|
83
|
+
*/
|
|
84
|
+
function resolveUnder(home, value) {
|
|
85
|
+
if (!value)
|
|
86
|
+
return undefined;
|
|
87
|
+
return isAbsolute(value) ? value : resolve(home, value);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 构造一组空的子系统视图
|
|
91
|
+
* @returns 子系统视图
|
|
92
|
+
*/
|
|
93
|
+
function emptySubsystems() {
|
|
94
|
+
return {
|
|
95
|
+
adapters: { list: () => [], get: () => undefined },
|
|
96
|
+
bots: { get: () => undefined, bySelfId: () => undefined, online: () => [], size: 0 },
|
|
97
|
+
accounts: { list: () => [], get: () => undefined },
|
|
98
|
+
registries: { commands: () => [], tasks: () => [], middlewares: () => [] }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 从 Bot 注册表里挑一个账号
|
|
103
|
+
*
|
|
104
|
+
* `hooks.bots.pick` 的实现放在内核而不是账号管理器里,是为了让阶段三只需要
|
|
105
|
+
* 提供 `BotRegistryView`(一个纯查询接口),不必再实现一遍"省略 id 时取第一个"
|
|
106
|
+
* 这种约定。
|
|
107
|
+
* @param bots Bot 注册表
|
|
108
|
+
* @param id 账号记录 id 或平台 selfId;省略时取第一个在线账号
|
|
109
|
+
* @returns Bot;无可用账号时 undefined
|
|
110
|
+
*/
|
|
111
|
+
function pickBot(bots, id) {
|
|
112
|
+
if (id === undefined || id === "")
|
|
113
|
+
return bots.online()[0];
|
|
114
|
+
return bots.get(id) ?? bots.bySelfId(id);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 应用实例
|
|
118
|
+
*
|
|
119
|
+
* 由 `createApp()` 创建。持有全部内核子系统,并负责按正确顺序把它们关掉 —— 没有这一层,
|
|
120
|
+
* `Ctrl+C` 之后存储连接、浏览器进程与文件监听会各自漂着,要手动 kill。
|
|
121
|
+
*/
|
|
122
|
+
export class App {
|
|
123
|
+
/** 内核版本 */
|
|
124
|
+
version;
|
|
125
|
+
/** 目录布局 */
|
|
126
|
+
paths;
|
|
127
|
+
/** 运行环境信息 */
|
|
128
|
+
platform;
|
|
129
|
+
/** 日志枢纽(WebUI 的日志回看与实时推送用它) */
|
|
130
|
+
loggerHub;
|
|
131
|
+
/** 内核自己的日志器 */
|
|
132
|
+
logger;
|
|
133
|
+
/** 配置仓库(全部配置文件的登记处) */
|
|
134
|
+
configStore;
|
|
135
|
+
/** 内核配置句柄 */
|
|
136
|
+
config;
|
|
137
|
+
/** KV 存储 */
|
|
138
|
+
kv;
|
|
139
|
+
/** SQL 接缝 */
|
|
140
|
+
sql;
|
|
141
|
+
/** HTTP 客户端 */
|
|
142
|
+
http;
|
|
143
|
+
/** 服务注册表(`ctx.provide` / `ctx.inject`) */
|
|
144
|
+
services;
|
|
145
|
+
/** 内核事件总线 */
|
|
146
|
+
events;
|
|
147
|
+
/** 策略(主人、前缀、维护模式) */
|
|
148
|
+
policy;
|
|
149
|
+
/**
|
|
150
|
+
* 子系统接缝
|
|
151
|
+
*
|
|
152
|
+
* **可变**:管线层、调度器、服务器、渲染器装好后替换对应字段即可,
|
|
153
|
+
* 已加载的插件立刻生效。见文件头第 3 条。
|
|
154
|
+
*/
|
|
155
|
+
hooks;
|
|
156
|
+
/** 插件宿主 */
|
|
157
|
+
plugins;
|
|
158
|
+
/**
|
|
159
|
+
* 可后填的子系统视图
|
|
160
|
+
*
|
|
161
|
+
* **可变**,同 `hooks`。阶段三装好适配器与账号管理后替换这里的字段。
|
|
162
|
+
*/
|
|
163
|
+
subsystems;
|
|
164
|
+
/**
|
|
165
|
+
* 运行期子系统的具体实现
|
|
166
|
+
*
|
|
167
|
+
* 与 `subsystems` 的区别:那边是交给插件的**只读窄视图**(列表、查询),
|
|
168
|
+
* 这边是完整实现。WebUI 要做的事(增删账号、驱动登录会话、看渲染器状态、
|
|
169
|
+
* 读分发器积压)都在窄视图之外,所以内核自己留一份引用。
|
|
170
|
+
*
|
|
171
|
+
* 插件拿不到它 —— `PluginContext` 只暴露 `ctx.app`(即 `view`)。
|
|
172
|
+
*/
|
|
173
|
+
runtime;
|
|
174
|
+
/** 只读应用视图(交给插件的那一份) */
|
|
175
|
+
view;
|
|
176
|
+
/** 插件视图 */
|
|
177
|
+
#pluginsView;
|
|
178
|
+
/** 共享服务器;`server.enable` 为 false 时没有 */
|
|
179
|
+
#server;
|
|
180
|
+
/** 停机上限毫秒 */
|
|
181
|
+
#stopTimeout;
|
|
182
|
+
/** 需在 `stop()` 中回收的句柄;装配期与 `start()` 期登记的均在其中 */
|
|
183
|
+
#disposers;
|
|
184
|
+
/** 当前状态 */
|
|
185
|
+
#status = "created";
|
|
186
|
+
/** 启动时间戳;未启动时为创建时间 */
|
|
187
|
+
#startedAt = Date.now();
|
|
188
|
+
/**
|
|
189
|
+
* 内部构造函数,请用 `createApp()`
|
|
190
|
+
*
|
|
191
|
+
* 没有标 `private`:那样 `createApp()` 这个模块级函数自己也调不了。
|
|
192
|
+
* 封装靠 `AppParts` **不导出** —— 包外拿不到那些内部类型,也就拼不出参数。
|
|
193
|
+
* @param parts 已装配好的部件
|
|
194
|
+
*/
|
|
195
|
+
constructor(parts) {
|
|
196
|
+
this.version = parts.version;
|
|
197
|
+
this.paths = parts.paths;
|
|
198
|
+
this.platform = parts.platform;
|
|
199
|
+
this.loggerHub = parts.loggerHub;
|
|
200
|
+
this.logger = parts.loggerHub.root.child({ scope: "kernel" });
|
|
201
|
+
this.configStore = parts.configStore;
|
|
202
|
+
this.config = parts.config;
|
|
203
|
+
this.kv = parts.kv;
|
|
204
|
+
this.sql = parts.sql;
|
|
205
|
+
this.http = parts.http;
|
|
206
|
+
this.services = parts.services;
|
|
207
|
+
this.events = parts.events;
|
|
208
|
+
this.policy = parts.policy;
|
|
209
|
+
this.hooks = parts.hooks;
|
|
210
|
+
this.plugins = parts.host;
|
|
211
|
+
this.subsystems = parts.subsystems;
|
|
212
|
+
this.runtime = parts.runtime;
|
|
213
|
+
this.#pluginsView = parts.pluginsView;
|
|
214
|
+
this.#server = parts.server;
|
|
215
|
+
this.#stopTimeout = parts.stopTimeout;
|
|
216
|
+
// 复制而不是持有同一个数组:装配期那份数组不该在 App 建好之后还能被外部追加
|
|
217
|
+
this.#disposers = [...parts.disposers];
|
|
218
|
+
// 全部字段都是 getter:见文件头第 4 条
|
|
219
|
+
const self = this;
|
|
220
|
+
this.view = {
|
|
221
|
+
version: parts.version,
|
|
222
|
+
paths: parts.paths,
|
|
223
|
+
platform: parts.platform,
|
|
224
|
+
policy: parts.policy,
|
|
225
|
+
get startedAt() {
|
|
226
|
+
return self.#startedAt;
|
|
227
|
+
},
|
|
228
|
+
get adapters() {
|
|
229
|
+
return self.subsystems.adapters;
|
|
230
|
+
},
|
|
231
|
+
get bots() {
|
|
232
|
+
return self.subsystems.bots;
|
|
233
|
+
},
|
|
234
|
+
get accounts() {
|
|
235
|
+
return self.subsystems.accounts;
|
|
236
|
+
},
|
|
237
|
+
get plugins() {
|
|
238
|
+
return self.#pluginsView;
|
|
239
|
+
},
|
|
240
|
+
get server() {
|
|
241
|
+
// 单一来源:服务器信息只存在于接缝里,避免"改了监听端口但视图还是旧的"
|
|
242
|
+
return self.hooks.server.info;
|
|
243
|
+
},
|
|
244
|
+
usage: () => sampleUsage()
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/** 当前状态 */
|
|
248
|
+
get status() {
|
|
249
|
+
return this.#status;
|
|
250
|
+
}
|
|
251
|
+
/** 启动时间戳(毫秒) */
|
|
252
|
+
get startedAt() {
|
|
253
|
+
return this.#startedAt;
|
|
254
|
+
}
|
|
255
|
+
/** 当前配置快照 */
|
|
256
|
+
get settings() {
|
|
257
|
+
return this.config.get();
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 启动:加载插件、开启配置热加载、广播 `app/ready`
|
|
261
|
+
*
|
|
262
|
+
* 插件加载失败不会导致启动失败:单个插件写错一个正则,不该让整个实例起不来。
|
|
263
|
+
* 失败清单在返回值里,同时记一条 warn。
|
|
264
|
+
* @returns 插件加载汇总
|
|
265
|
+
* @throws 已经启动过或已停机时;此时应新建实例而不是重启
|
|
266
|
+
*/
|
|
267
|
+
async start() {
|
|
268
|
+
if (this.#status !== "created") {
|
|
269
|
+
throw new Error(`应用当前状态为 ${this.#status},不能再次 start()。stop() 是终态(连接池与日志文件都已关闭),重启请新建实例`);
|
|
270
|
+
}
|
|
271
|
+
this.#status = "starting";
|
|
272
|
+
this.#startedAt = Date.now();
|
|
273
|
+
// 配置热加载:目录监听 + 变更回调。两者均登记进 #disposers,
|
|
274
|
+
// 否则 stop() 之后 watcher 仍在运行,进程无法退出
|
|
275
|
+
this.own(this.configStore.startWatching());
|
|
276
|
+
this.own(this.config.onChange(change => {
|
|
277
|
+
applyLogLevel(this.loggerHub, change.next);
|
|
278
|
+
// emitDetached:配置回调可能来自 fs 事件,没人在等它的返回值;
|
|
279
|
+
// 用 emit 会把插件监听器的耗时算进文件监听的回调里
|
|
280
|
+
this.events.emitDetached("config/changed", CORE_CONFIG_NAME, change.paths);
|
|
281
|
+
}));
|
|
282
|
+
// 令牌要在安全警告之前落定:否则"未设置访问令牌"的警告会和紧随其后的
|
|
283
|
+
// "已自动生成令牌"自相矛盾,用户不知道该信哪句
|
|
284
|
+
if (this.#server)
|
|
285
|
+
await this.#server.ensureToken();
|
|
286
|
+
const warning = serverSecurityWarning(this.settings);
|
|
287
|
+
if (warning)
|
|
288
|
+
this.logger.warn(warning);
|
|
289
|
+
const report = await this.plugins.loadAll();
|
|
290
|
+
if (report.failed.length > 0) {
|
|
291
|
+
this.logger.warn(`${report.failed.length} 个插件未加载:${report.failed.map(f => f.name).join("、")}(详情见上文,或在面板的插件页查看)`);
|
|
292
|
+
}
|
|
293
|
+
this.logger.info(`插件就绪:成功 ${report.loaded.length} 个,失败 ${report.failed.length} 个,耗时 ${report.cost}ms`);
|
|
294
|
+
// 监听要卡在这两步中间:
|
|
295
|
+
// - 在 loadAll() 之后,插件的路由才齐,不会出现"面板半截可用"的窗口期;
|
|
296
|
+
// - 在 app/ready 之前,因为账号是在那个事件里开始连接的(见 kernel/runtime.ts),
|
|
297
|
+
// 而反向 WebSocket 的适配器需要端点已经能接入
|
|
298
|
+
if (this.#server) {
|
|
299
|
+
this.#reportPanel(this.#server);
|
|
300
|
+
try {
|
|
301
|
+
await this.#server.listen();
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
// 端口被占用不该让机器人整台起不来:消息收发和面板是两件独立的事
|
|
305
|
+
this.logger.error(`共享服务器监听失败,面板与插件路由不可用:${err instanceof Error ? err.message : String(err)}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
await this.events.emit("app/ready");
|
|
309
|
+
this.#status = "running";
|
|
310
|
+
this.logger.mark(`Yunzai NG ${this.version} 已启动`);
|
|
311
|
+
return report;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 检测面板由谁提供,并在无人提供时给出可照做的一条指令
|
|
315
|
+
*
|
|
316
|
+
* **内核不提供面板前端,此处只做检测。** 面板由插件调用 `ctx.panel()` 接管根路径,
|
|
317
|
+
* 因此检测必须排在 `plugins.loadAll()` 之后。内核侧不设兜底实现:兜底实现要求
|
|
318
|
+
* 内核知晓前端产物的目录约定,而那正是"替换面板必须改内核"的由来。
|
|
319
|
+
*
|
|
320
|
+
* 无人提供不是错误,只记一条 info:机器人收发消息与面板是两件独立的事,
|
|
321
|
+
* 精简部署(不装面板插件)是受支持的形态。
|
|
322
|
+
* @param server 共享服务器
|
|
323
|
+
*/
|
|
324
|
+
#reportPanel(server) {
|
|
325
|
+
const claimant = server.claimant("/");
|
|
326
|
+
if (claimant !== undefined) {
|
|
327
|
+
this.logger.debug(`站点根路径由 ${claimant} 提供`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this.logger.info(`未检测到提供面板的插件,当前仅 ${API_SCOPE} 可用。可在插件市场安装 webui,或将其置入 ${this.paths.plugins}`);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 停机:广播 `app/stopping`、卸载插件、按依赖倒序关闭内核资源
|
|
334
|
+
*
|
|
335
|
+
* 幂等,可重复调用。任何一步出错均只记日志并继续执行下一步 ——
|
|
336
|
+
* 停机路径上"某个资源无法关闭"绝不能连带导致其后的资源全部泄漏。
|
|
337
|
+
* @returns 全部关闭后兑现
|
|
338
|
+
*/
|
|
339
|
+
async stop() {
|
|
340
|
+
if (this.#status === "stopping" || this.#status === "stopped")
|
|
341
|
+
return;
|
|
342
|
+
this.#status = "stopping";
|
|
343
|
+
this.logger.info("正在停机…");
|
|
344
|
+
// 1) 先让插件保存状态。超时就不等了:一个插件卡在这里不能让整个进程停不下来
|
|
345
|
+
try {
|
|
346
|
+
await withTimeout(this.events.emit("app/stopping"), this.#stopTimeout, `插件处理 app/stopping 超过 ${this.#stopTimeout}ms,不再等待(可能有插件在停机回调里发网络请求)`);
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
this.logger.warn(err instanceof Error ? err.message : String(err));
|
|
350
|
+
}
|
|
351
|
+
// 2) 卸载插件:宿主会按逆加载顺序回收每个插件登记的资源
|
|
352
|
+
await this.#safely("插件", async () => {
|
|
353
|
+
const count = await this.plugins.dispose();
|
|
354
|
+
this.logger.debug(`已卸载 ${count} 个插件`);
|
|
355
|
+
});
|
|
356
|
+
// 3) 关闭服务器。必须在插件卸载之后:插件的 Disposer 需摘除自身注册的路由,
|
|
357
|
+
// 若先关闭服务器,那些 Disposer 面对的是一张已清空的表(幂等,但为无效执行),
|
|
358
|
+
// 且停机过程中面板应当能响应至最后一刻
|
|
359
|
+
if (this.#server)
|
|
360
|
+
await this.#safely("共享服务器", () => this.#server?.close() ?? Promise.resolve());
|
|
361
|
+
// 4) 装配期与 start() 期登记的回收动作,后进先出
|
|
362
|
+
for (const dispose of this.#disposers.reverse()) {
|
|
363
|
+
await this.#safely("运行期资源", dispose);
|
|
364
|
+
}
|
|
365
|
+
this.#disposers.length = 0;
|
|
366
|
+
// 5) 内核资源。顺序 = 依赖倒序:先断掉还会产生事件的,再关存储,最后关日志
|
|
367
|
+
await this.#safely("事件总线", () => this.events.close());
|
|
368
|
+
await this.#safely("SQL 连接", () => this.sql.closeAll());
|
|
369
|
+
await this.#safely("KV 存储", () => this.kv.close());
|
|
370
|
+
await this.#safely("HTTP 连接池", () => this.http.close());
|
|
371
|
+
await this.#safely("配置监听", () => this.configStore.dispose());
|
|
372
|
+
this.#status = "stopped";
|
|
373
|
+
this.logger.info("已停机");
|
|
374
|
+
// 日志放在最后:上面每一步都还要往里写
|
|
375
|
+
this.loggerHub.flush();
|
|
376
|
+
this.loggerHub.close();
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* 接管 `SIGINT` / `SIGTERM`,收到信号时优雅停机后退出进程
|
|
380
|
+
*
|
|
381
|
+
* 刻意做成**显式调用**而不是 `createApp()` 里自动装上:往 `process` 上挂
|
|
382
|
+
* 全局监听器是有副作用的,嵌进别人程序里的内核不该私自决定进程什么时候退出。
|
|
383
|
+
* CLI 的 `yzng start` 调它,单元测试不调。
|
|
384
|
+
* @returns 取消接管
|
|
385
|
+
*/
|
|
386
|
+
handleSignals() {
|
|
387
|
+
/** 是否已经在停机 */
|
|
388
|
+
let leaving = false;
|
|
389
|
+
/**
|
|
390
|
+
* 信号处理
|
|
391
|
+
* @param signal 信号名
|
|
392
|
+
*/
|
|
393
|
+
const onSignal = (signal) => {
|
|
394
|
+
if (leaving) {
|
|
395
|
+
// 用户连按两次 Ctrl+C 的意思很明确:别等了。这时强退比"优雅"重要
|
|
396
|
+
this.logger.warn(`再次收到 ${signal},放弃优雅停机直接退出`);
|
|
397
|
+
this.loggerHub.flush();
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
leaving = true;
|
|
401
|
+
this.logger.info(`收到 ${signal},开始停机(再按一次可强制退出)`);
|
|
402
|
+
void this.stop().then(() => process.exit(0), () => process.exit(1));
|
|
403
|
+
};
|
|
404
|
+
process.on("SIGINT", onSignal);
|
|
405
|
+
process.on("SIGTERM", onSignal);
|
|
406
|
+
return () => {
|
|
407
|
+
process.off("SIGINT", onSignal);
|
|
408
|
+
process.off("SIGTERM", onSignal);
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* 登记一个需在 `stop()` 中回收的句柄
|
|
413
|
+
*
|
|
414
|
+
* 回收按登记的**逆序**执行,位置是 `stop()` 的第 3 步 —— 插件已卸载完毕
|
|
415
|
+
*(不再有向子系统注册的行为),而存储、事件总线、日志仍处于开启状态(尚可写出
|
|
416
|
+
* 最后一次账号状态、尚可记录最后一行日志)。
|
|
417
|
+
*
|
|
418
|
+
* 公开而非私有:`start()` 用它登记配置监听,阶段四的服务器会用它登记
|
|
419
|
+
* 关闭监听端口。装配期(`new App` 之前)的回收动作走
|
|
420
|
+
* `AppParts.disposers`,因为那时还没有实例可调。
|
|
421
|
+
* @param dispose 回收函数
|
|
422
|
+
*/
|
|
423
|
+
own(dispose) {
|
|
424
|
+
this.#disposers.push(dispose);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* 执行一步关闭动作,失败只记日志
|
|
428
|
+
* @param what 关闭对象的名字,用于日志
|
|
429
|
+
* @param fn 关闭动作
|
|
430
|
+
*/
|
|
431
|
+
async #safely(what, fn) {
|
|
432
|
+
try {
|
|
433
|
+
await fn();
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
this.logger.warn(`关闭${what}时出错,继续停机`, err);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* 创建应用
|
|
442
|
+
*
|
|
443
|
+
* 只装配,不启动:返回时全部子系统已就绪但没有任何账号在连、没有任何插件被
|
|
444
|
+
* 加载。调用方可以在此期间往 `hooks` / `subsystems` 里塞测试替身,再调
|
|
445
|
+
* `start()`。CLI 与测试都走这条路。
|
|
446
|
+
* @param opts 参数
|
|
447
|
+
* @returns 应用实例
|
|
448
|
+
* @throws 目录创建失败、或全部 KV 驱动都打不开时
|
|
449
|
+
*/
|
|
450
|
+
export async function createApp(opts = {}) {
|
|
451
|
+
const version = opts.version ?? readVersion();
|
|
452
|
+
const platform = detectPlatform();
|
|
453
|
+
const paths = await ensurePaths(resolvePaths({ home: opts.home, runtime: opts.runtime }));
|
|
454
|
+
// ── 1. 日志 ───────────────────────────────────────────────────────────
|
|
455
|
+
// 轮转参数取 schema 默认值而不是另写一套魔数:默认值只有一个来源
|
|
456
|
+
const logDefaults = coreConfigSchema.defaults().log;
|
|
457
|
+
const loggerHub = createLoggerHub({
|
|
458
|
+
level: opts.logLevel ?? logDefaults.level,
|
|
459
|
+
dir: paths.logs,
|
|
460
|
+
basename: LOG_BASENAME,
|
|
461
|
+
console: opts.console !== false,
|
|
462
|
+
color: logDefaults.color,
|
|
463
|
+
maxSize: logDefaults.maxSize * 1024 * 1024,
|
|
464
|
+
keepDays: logDefaults.keepDays,
|
|
465
|
+
maxFiles: logDefaults.maxFiles
|
|
466
|
+
});
|
|
467
|
+
const root = loggerHub.root;
|
|
468
|
+
const logger = root.child({ scope: "kernel" });
|
|
469
|
+
logger.info(`Yunzai NG ${version} 装配中:${platform.os}/${platform.arch},Node ${platform.nodeVersion}`);
|
|
470
|
+
logger.debug(`主目录 ${paths.home}${platform.isTermux ? "(Termux)" : ""}${platform.lowMemory ? ",低内存模式" : ""}`);
|
|
471
|
+
// ── 2. 配置 ───────────────────────────────────────────────────────────
|
|
472
|
+
const configStore = createConfigStore({ dir: paths.config, logger: root, watch: opts.watchConfig !== false });
|
|
473
|
+
const config = await defineCoreConfig(configStore);
|
|
474
|
+
const settings = config.get();
|
|
475
|
+
applyLogLevel(loggerHub, settings);
|
|
476
|
+
// 日志写入器已按默认值建立,轮转参数无法再变更;此处明确说明而非静默忽略
|
|
477
|
+
const wanted = loggerSettingsOf(settings);
|
|
478
|
+
if (wanted.maxSize !== logDefaults.maxSize * 1024 * 1024 ||
|
|
479
|
+
wanted.keepDays !== logDefaults.keepDays ||
|
|
480
|
+
wanted.maxFiles !== logDefaults.maxFiles) {
|
|
481
|
+
logger.info("日志的单文件上限 / 保留天数 / 文件数上限将在下次启动生效:日志必须早于配置就绪,见 kernel/app.ts 文件头");
|
|
482
|
+
}
|
|
483
|
+
// ── 3. 存储 ───────────────────────────────────────────────────────────
|
|
484
|
+
const kv = await openKv({
|
|
485
|
+
dir: resolveUnder(paths.home, settings.store.dir) ?? join(paths.data, "store"),
|
|
486
|
+
driver: settings.store.driver,
|
|
487
|
+
logger: root
|
|
488
|
+
});
|
|
489
|
+
const sql = createSqlSink({ dir: join(paths.data, "sql"), logger: root, enabled: settings.store.sqlite });
|
|
490
|
+
// ── 4. 网络 ───────────────────────────────────────────────────────────
|
|
491
|
+
// 超时在这里就折算成毫秒:配置里是 "20s" 这类人写的形式,
|
|
492
|
+
// 往下传字符串的话每一层都得再解析一次,也各有一次解析失败的机会
|
|
493
|
+
const http = createHttpClient({
|
|
494
|
+
logger: root,
|
|
495
|
+
timeout: parseDuration(settings.net.timeout, 20_000),
|
|
496
|
+
retry: settings.net.retry,
|
|
497
|
+
proxy: settings.net.proxy,
|
|
498
|
+
userAgent: settings.net.userAgent ?? `Yunzai-NG/${version}`
|
|
499
|
+
});
|
|
500
|
+
// ── 5. 注册表与事件 ───────────────────────────────────────────────────
|
|
501
|
+
const services = createServiceRegistry();
|
|
502
|
+
const events = createEventBus({ logger: root });
|
|
503
|
+
const policy = createPolicy(config);
|
|
504
|
+
// ── 6. 子系统接缝:能填的填真的,填不了的留 unavailable ────────────────
|
|
505
|
+
const subsystems = emptySubsystems();
|
|
506
|
+
const hooks = unavailableHooks();
|
|
507
|
+
hooks.sql = sql;
|
|
508
|
+
hooks.kvDrivers = createKvDriverSink({
|
|
509
|
+
logger: root,
|
|
510
|
+
requested: () => config.get().store.driver,
|
|
511
|
+
active: kv.driver
|
|
512
|
+
});
|
|
513
|
+
hooks.bots = { pick: (id) => pickBot(subsystems.bots, id) };
|
|
514
|
+
// 共享服务器:关掉时刻意**不建实例**,`hooks.server` 保持 unavailable 占位。
|
|
515
|
+
// 建一个"不监听的服务器"会让插件的 `ctx.route()` 注册成功却永远收不到请求,
|
|
516
|
+
// 那比当场报错难查得多
|
|
517
|
+
const server = settings.server.enable
|
|
518
|
+
? await createManagedServer({ config, logger: root.child({ scope: "server" }) })
|
|
519
|
+
: undefined;
|
|
520
|
+
if (server)
|
|
521
|
+
hooks.server = server;
|
|
522
|
+
// ── 7. 插件宿主 ───────────────────────────────────────────────────────
|
|
523
|
+
// 插件目录:配置里的相对路径按主目录解析,命令行传的追加在后面
|
|
524
|
+
const extraDirs = [
|
|
525
|
+
...settings.plugins.dirs.map(dir => resolveUnder(paths.home, dir)).filter((dir) => dir !== undefined),
|
|
526
|
+
...(opts.extraDirs ?? [])
|
|
527
|
+
];
|
|
528
|
+
/**
|
|
529
|
+
* 装配期的自引用槽
|
|
530
|
+
*
|
|
531
|
+
* 宿主要 `AppView`,而 `AppView.plugins` 正是宿主提供的 —— 死结只能靠
|
|
532
|
+
* "先建宿主、事后回填"解开。用对象槽而不是 `let`,是为了让"这里有意存在
|
|
533
|
+
* 一个装配期的空窗"这件事在类型上就写明白(`app?: App`)。
|
|
534
|
+
*/
|
|
535
|
+
const selfRef = {};
|
|
536
|
+
const host = new PluginHost({
|
|
537
|
+
paths,
|
|
538
|
+
logger: root,
|
|
539
|
+
config: configStore,
|
|
540
|
+
kv,
|
|
541
|
+
http,
|
|
542
|
+
services,
|
|
543
|
+
events,
|
|
544
|
+
hooks,
|
|
545
|
+
app: () => {
|
|
546
|
+
if (!selfRef.app)
|
|
547
|
+
throw new Error("内部错误:应用尚未装配完成就有人取用 AppView");
|
|
548
|
+
return selfRef.app.view;
|
|
549
|
+
},
|
|
550
|
+
extraDirs,
|
|
551
|
+
builtinDirs: opts.builtinDirs,
|
|
552
|
+
// 注意:禁用清单在这里固定下来。面板上"禁用某插件"应当同时 patch 配置
|
|
553
|
+
// 并调用 host.unload(),这样无需重启也能立刻生效
|
|
554
|
+
disabled: settings.plugins.disabled,
|
|
555
|
+
setupTimeout: parseDuration(settings.plugins.loadTimeout, 30_000)
|
|
556
|
+
});
|
|
557
|
+
const pluginsView = createPluginsView(host, {
|
|
558
|
+
commands: () => subsystems.registries.commands(),
|
|
559
|
+
tasks: () => subsystems.registries.tasks(),
|
|
560
|
+
middlewares: () => subsystems.registries.middlewares()
|
|
561
|
+
});
|
|
562
|
+
// 插件市场:只负责取索引与读写插件目录,不参与加载。安装完成后由面板决定
|
|
563
|
+
// 何时调用 host.loadAll(),因此它与宿主之间没有直接依赖
|
|
564
|
+
const market = new PluginMarket({
|
|
565
|
+
http,
|
|
566
|
+
logger: root.child({ scope: "market" }),
|
|
567
|
+
pluginsDir: paths.plugins,
|
|
568
|
+
tempDir: join(paths.temp, "market"),
|
|
569
|
+
cacheFile: join(paths.cache, "market-index.json"),
|
|
570
|
+
coreVersion: version,
|
|
571
|
+
settings: () => {
|
|
572
|
+
const current = config.get().market;
|
|
573
|
+
return {
|
|
574
|
+
sources: current.sources,
|
|
575
|
+
mirror: current.mirror,
|
|
576
|
+
cacheTtl: parseDuration(current.cacheTtl, 3_600_000),
|
|
577
|
+
timeout: parseDuration(current.timeout, 15_000)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
// ── 8. 运行期子系统 ───────────────────────────────────────────────────
|
|
582
|
+
// 必须在 new App 之前:否则 AppView 会存在一段"子系统仍为空占位"的窗口期,
|
|
583
|
+
// 见 kernel/runtime.ts 文件头第 2 条。回收动作先行累积,交由 App 接手
|
|
584
|
+
const disposers = [];
|
|
585
|
+
const runtime = installRuntime({
|
|
586
|
+
logger: root,
|
|
587
|
+
config,
|
|
588
|
+
kv,
|
|
589
|
+
events,
|
|
590
|
+
policy,
|
|
591
|
+
hooks,
|
|
592
|
+
subsystems,
|
|
593
|
+
plugins: name => host.runtime(name),
|
|
594
|
+
own: dispose => void disposers.push(dispose)
|
|
595
|
+
});
|
|
596
|
+
const app = new App({
|
|
597
|
+
version,
|
|
598
|
+
paths,
|
|
599
|
+
platform,
|
|
600
|
+
loggerHub,
|
|
601
|
+
configStore,
|
|
602
|
+
config,
|
|
603
|
+
kv,
|
|
604
|
+
sql,
|
|
605
|
+
http,
|
|
606
|
+
services,
|
|
607
|
+
events,
|
|
608
|
+
policy,
|
|
609
|
+
hooks,
|
|
610
|
+
host,
|
|
611
|
+
pluginsView,
|
|
612
|
+
subsystems,
|
|
613
|
+
runtime,
|
|
614
|
+
disposers,
|
|
615
|
+
server,
|
|
616
|
+
stopTimeout: opts.stopTimeout ?? DEFAULT_STOP_TIMEOUT
|
|
617
|
+
});
|
|
618
|
+
selfRef.app = app;
|
|
619
|
+
// ── 9. 面板 ───────────────────────────────────────────────────────────
|
|
620
|
+
// 必须在 new App 之后:API 要读 `app.status` 与 `app.startedAt`,而这两个只有
|
|
621
|
+
// 实例才有。反过来 api.ts 不认识 App —— 它收的是一组窄依赖与两个取值函数,
|
|
622
|
+
// 于是 app → api 是单向的(见 server/api.ts 文件头第 1 条)
|
|
623
|
+
if (server) {
|
|
624
|
+
app.own(registerApi(server, {
|
|
625
|
+
version,
|
|
626
|
+
paths,
|
|
627
|
+
platform,
|
|
628
|
+
logger: root.child({ scope: "api" }),
|
|
629
|
+
config,
|
|
630
|
+
configStore,
|
|
631
|
+
loggerHub,
|
|
632
|
+
plugins: host,
|
|
633
|
+
market,
|
|
634
|
+
registries: {
|
|
635
|
+
commands: () => subsystems.registries.commands(),
|
|
636
|
+
tasks: () => subsystems.registries.tasks(),
|
|
637
|
+
middlewares: () => subsystems.registries.middlewares()
|
|
638
|
+
},
|
|
639
|
+
adapters: runtime.adapters,
|
|
640
|
+
accounts: runtime.accounts,
|
|
641
|
+
logins: runtime.logins,
|
|
642
|
+
bots: runtime.bots,
|
|
643
|
+
renderers: runtime.renderers,
|
|
644
|
+
dispatcher: runtime.dispatcher,
|
|
645
|
+
server,
|
|
646
|
+
status: () => app.status,
|
|
647
|
+
startedAt: () => app.startedAt,
|
|
648
|
+
usage: () => sampleUsage(),
|
|
649
|
+
system: () => sampleSystem()
|
|
650
|
+
}));
|
|
651
|
+
}
|
|
652
|
+
return app;
|
|
653
|
+
}
|
|
654
|
+
//# sourceMappingURL=app.js.map
|