@jsbot-tellus/core 2.4.0 → 2.5.1
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/README.md +8 -5
- package/dist/app/cli.d.ts +10 -0
- package/dist/app/cli.d.ts.map +1 -0
- package/dist/app/cli.js +37 -0
- package/dist/app/cli.js.map +1 -0
- package/dist/app/runtime.d.ts +121 -0
- package/dist/app/runtime.d.ts.map +1 -0
- package/dist/app/runtime.js +296 -0
- package/dist/app/runtime.js.map +1 -0
- package/dist/app.js +63 -273
- package/dist/app.js.map +1 -1
- package/dist/core/dispatch/command.d.ts.map +1 -1
- package/dist/core/dispatch/command.js +5 -1
- package/dist/core/dispatch/command.js.map +1 -1
- package/dist/core/dispatch/router.d.ts.map +1 -1
- package/dist/core/dispatch/router.js +1 -1
- package/dist/core/dispatch/router.js.map +1 -1
- package/dist/core/plugin/command-params.d.ts +3 -36
- package/dist/core/plugin/command-params.d.ts.map +1 -1
- package/dist/core/plugin/command-params.js +2 -461
- package/dist/core/plugin/command-params.js.map +1 -1
- package/dist/core/plugin/install/paths.d.ts +7 -0
- package/dist/core/plugin/install/paths.d.ts.map +1 -0
- package/dist/core/plugin/install/paths.js +49 -0
- package/dist/core/plugin/install/paths.js.map +1 -0
- package/dist/core/plugin/install/state.d.ts +23 -0
- package/dist/core/plugin/install/state.d.ts.map +1 -0
- package/dist/core/plugin/install/state.js +129 -0
- package/dist/core/plugin/install/state.js.map +1 -0
- package/dist/core/plugin/install/validate-script.d.ts +3 -0
- package/dist/core/plugin/install/validate-script.d.ts.map +1 -0
- package/dist/core/plugin/install/validate-script.js +154 -0
- package/dist/core/plugin/install/validate-script.js.map +1 -0
- package/dist/core/plugin/install-dir.d.ts +3 -28
- package/dist/core/plugin/install-dir.d.ts.map +1 -1
- package/dist/core/plugin/install-dir.js +3 -326
- package/dist/core/plugin/install-dir.js.map +1 -1
- package/dist/core/plugin/installer/checks.d.ts +6 -0
- package/dist/core/plugin/installer/checks.d.ts.map +1 -0
- package/dist/core/plugin/installer/checks.js +99 -0
- package/dist/core/plugin/installer/checks.js.map +1 -0
- package/dist/core/plugin/installer/support.d.ts +37 -0
- package/dist/core/plugin/installer/support.d.ts.map +1 -0
- package/dist/core/plugin/installer/support.js +67 -0
- package/dist/core/plugin/installer/support.js.map +1 -0
- package/dist/core/plugin/installer/warnings.d.ts +5 -0
- package/dist/core/plugin/installer/warnings.d.ts.map +1 -0
- package/dist/core/plugin/installer/warnings.js +28 -0
- package/dist/core/plugin/installer/warnings.js.map +1 -0
- package/dist/core/plugin/installer.d.ts +4 -30
- package/dist/core/plugin/installer.d.ts.map +1 -1
- package/dist/core/plugin/installer.js +21 -202
- package/dist/core/plugin/installer.js.map +1 -1
- package/dist/core/plugin/params/compile.d.ts +3 -0
- package/dist/core/plugin/params/compile.d.ts.map +1 -0
- package/dist/core/plugin/params/compile.js +23 -0
- package/dist/core/plugin/params/compile.js.map +1 -0
- package/dist/core/plugin/params/declaration.d.ts +9 -0
- package/dist/core/plugin/params/declaration.d.ts.map +1 -0
- package/dist/core/plugin/params/declaration.js +223 -0
- package/dist/core/plugin/params/declaration.js.map +1 -0
- package/dist/core/plugin/params/input.d.ts +5 -0
- package/dist/core/plugin/params/input.d.ts.map +1 -0
- package/dist/core/plugin/params/input.js +181 -0
- package/dist/core/plugin/params/input.js.map +1 -0
- package/dist/core/plugin/params/reply.d.ts +3 -0
- package/dist/core/plugin/params/reply.d.ts.map +1 -0
- package/dist/core/plugin/params/reply.js +49 -0
- package/dist/core/plugin/params/reply.js.map +1 -0
- package/dist/core/plugin/params/type-level.d.ts +23 -0
- package/dist/core/plugin/params/type-level.d.ts.map +1 -0
- package/dist/core/plugin/params/type-level.js +2 -0
- package/dist/core/plugin/params/type-level.js.map +1 -0
- package/dist/core/plugin/params/types.d.ts +28 -0
- package/dist/core/plugin/params/types.d.ts.map +1 -0
- package/dist/core/plugin/params/types.js +12 -0
- package/dist/core/plugin/params/types.js.map +1 -0
- package/dist/core/plugin/params/values.d.ts +6 -0
- package/dist/core/plugin/params/values.d.ts.map +1 -0
- package/dist/core/plugin/params/values.js +108 -0
- package/dist/core/plugin/params/values.js.map +1 -0
- package/dist/core/web/api.d.ts +2 -14
- package/dist/core/web/api.d.ts.map +1 -1
- package/dist/core/web/api.js +8 -500
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/routes/actions.d.ts +4 -0
- package/dist/core/web/routes/actions.d.ts.map +1 -0
- package/dist/core/web/routes/actions.js +33 -0
- package/dist/core/web/routes/actions.js.map +1 -0
- package/dist/core/web/routes/config.d.ts +5 -0
- package/dist/core/web/routes/config.d.ts.map +1 -0
- package/dist/core/web/routes/config.js +30 -0
- package/dist/core/web/routes/config.js.map +1 -0
- package/dist/core/web/routes/kv.d.ts +6 -0
- package/dist/core/web/routes/kv.d.ts.map +1 -0
- package/dist/core/web/routes/kv.js +80 -0
- package/dist/core/web/routes/kv.js.map +1 -0
- package/dist/core/web/routes/plugins.d.ts +5 -0
- package/dist/core/web/routes/plugins.d.ts.map +1 -0
- package/dist/core/web/routes/plugins.js +98 -0
- package/dist/core/web/routes/plugins.js.map +1 -0
- package/dist/core/web/routes/sessions.d.ts +8 -0
- package/dist/core/web/routes/sessions.d.ts.map +1 -0
- package/dist/core/web/routes/sessions.js +130 -0
- package/dist/core/web/routes/sessions.js.map +1 -0
- package/dist/core/web/routes/shared.d.ts +18 -0
- package/dist/core/web/routes/shared.d.ts.map +1 -0
- package/dist/core/web/routes/shared.js +19 -0
- package/dist/core/web/routes/shared.js.map +1 -0
- package/dist/core/web/routes/status.d.ts +6 -0
- package/dist/core/web/routes/status.d.ts.map +1 -0
- package/dist/core/web/routes/status.js +132 -0
- package/dist/core/web/routes/status.js.map +1 -0
- package/dist/core/web/server/auth.d.ts +17 -0
- package/dist/core/web/server/auth.d.ts.map +1 -0
- package/dist/core/web/server/auth.js +68 -0
- package/dist/core/web/server/auth.js.map +1 -0
- package/dist/core/web/server/csp.d.ts +11 -0
- package/dist/core/web/server/csp.d.ts.map +1 -0
- package/dist/core/web/server/csp.js +31 -0
- package/dist/core/web/server/csp.js.map +1 -0
- package/dist/core/web/server/request.d.ts +6 -0
- package/dist/core/web/server/request.d.ts.map +1 -0
- package/dist/core/web/server/request.js +33 -0
- package/dist/core/web/server/request.js.map +1 -0
- package/dist/core/web/server/security.d.ts +4 -0
- package/dist/core/web/server/security.d.ts.map +1 -0
- package/dist/core/web/server/security.js +12 -0
- package/dist/core/web/server/security.js.map +1 -0
- package/dist/core/web/server/sse.d.ts +18 -0
- package/dist/core/web/server/sse.d.ts.map +1 -0
- package/dist/core/web/server/sse.js +49 -0
- package/dist/core/web/server/sse.js.map +1 -0
- package/dist/core/web/server/static.d.ts +16 -0
- package/dist/core/web/server/static.d.ts.map +1 -0
- package/dist/core/web/server/static.js +80 -0
- package/dist/core/web/server/static.js.map +1 -0
- package/dist/core/web/server.d.ts +0 -7
- package/dist/core/web/server.d.ts.map +1 -1
- package/dist/core/web/server.js +41 -263
- package/dist/core/web/server.js.map +1 -1
- package/docs/architecture.md +28 -11
- package/docs/configuration.md +1 -1
- package/docs/migration.md +2 -2
- package/docs/plugin-api.md +3 -1
- package/docs/plugin-development.md +1 -1
- package/package.json +19 -17
- package/src/app/cli.ts +47 -0
- package/src/app/runtime.ts +522 -0
- package/src/app.ts +87 -395
- package/src/core/dispatch/command.ts +7 -1
- package/src/core/dispatch/router.ts +2 -1
- package/src/core/plugin/command-params.ts +8 -713
- package/src/core/plugin/install/paths.ts +72 -0
- package/src/core/plugin/install/state.ts +196 -0
- package/src/core/plugin/install/validate-script.ts +154 -0
- package/src/core/plugin/install-dir.ts +19 -427
- package/src/core/plugin/installer/checks.ts +137 -0
- package/src/core/plugin/installer/support.ts +120 -0
- package/src/core/plugin/installer/warnings.ts +52 -0
- package/src/core/plugin/installer.ts +52 -301
- package/src/core/plugin/params/compile.ts +41 -0
- package/src/core/plugin/params/declaration.ts +286 -0
- package/src/core/plugin/params/input.ts +254 -0
- package/src/core/plugin/params/reply.ts +59 -0
- package/src/core/plugin/params/type-level.ts +71 -0
- package/src/core/plugin/params/types.ts +49 -0
- package/src/core/plugin/params/values.ts +146 -0
- package/src/core/web/api.ts +33 -759
- package/src/core/web/routes/actions.ts +43 -0
- package/src/core/web/routes/config.ts +42 -0
- package/src/core/web/routes/kv.ts +122 -0
- package/src/core/web/routes/plugins.ts +151 -0
- package/src/core/web/routes/sessions.ts +188 -0
- package/src/core/web/routes/shared.ts +42 -0
- package/src/core/web/routes/status.ts +191 -0
- package/src/core/web/server/auth.ts +126 -0
- package/src/core/web/server/csp.ts +33 -0
- package/src/core/web/server/request.ts +38 -0
- package/src/core/web/server/security.ts +14 -0
- package/src/core/web/server/sse.ts +74 -0
- package/src/core/web/server/static.ts +112 -0
- package/src/core/web/server.ts +41 -383
package/src/app.ts
CHANGED
|
@@ -1,280 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
-
import { dirname, join, resolve } from 'node:path'
|
|
5
|
-
import { parseArgs } from 'node:util'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
6
3
|
import { fileURLToPath } from 'node:url'
|
|
7
|
-
import {
|
|
8
|
-
import type { AppConfig } from './core/config/schema.js'
|
|
4
|
+
import { parseCli, pick, USAGE } from './app/cli.js'
|
|
9
5
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
createAppLogging,
|
|
7
|
+
createExclusiveRunner,
|
|
8
|
+
createRestartRequester,
|
|
9
|
+
createRuntime,
|
|
10
|
+
createSerialRunner,
|
|
11
|
+
createShutdown,
|
|
12
|
+
createWebUiDeps,
|
|
13
|
+
credentialSignature,
|
|
14
|
+
describeError,
|
|
15
|
+
loadRuntimeSpec,
|
|
16
|
+
readVersion,
|
|
17
|
+
registerShutdownSignals,
|
|
18
|
+
resolveRuntimeSettings,
|
|
19
|
+
resolveStaticDir,
|
|
20
|
+
restartDrainTimeout,
|
|
21
|
+
startInitialRuntime,
|
|
22
|
+
type Runtime,
|
|
23
|
+
type StaticBinding,
|
|
24
|
+
} from './app/runtime.js'
|
|
25
|
+
import { ConfigError } from './core/config/loader.js'
|
|
26
|
+
import type { AppConfig } from './core/config/schema.js'
|
|
16
27
|
import { ConfigStore } from './core/web/config-store.js'
|
|
17
|
-
import { EventFeed } from './core/web/event-feed.js'
|
|
18
|
-
import { LogBuffer } from './core/logging/log-buffer.js'
|
|
19
|
-
import {
|
|
20
|
-
createLogger,
|
|
21
|
-
defaultLogSink,
|
|
22
|
-
parseLogLevel,
|
|
23
|
-
type LogSink,
|
|
24
|
-
type Logger,
|
|
25
|
-
} from './core/logging/logger.js'
|
|
26
|
-
import { NpmPluginInstaller } from './core/plugin/installer.js'
|
|
27
|
-
import { loadLocalPlugins } from './core/plugin/loader.js'
|
|
28
|
-
import type {
|
|
29
|
-
AnyPluginDefinition,
|
|
30
|
-
PluginInstaller,
|
|
31
|
-
} from './core/plugin/types.js'
|
|
32
|
-
import { openDatabase, type Database } from './core/storage/database.js'
|
|
33
28
|
import { WebUiServer } from './core/web/server.js'
|
|
34
|
-
import type { WebUiAddress
|
|
35
|
-
import { builtinPlugins } from './plugins/index.js'
|
|
36
|
-
|
|
37
|
-
const LOCAL_PLUGIN_DIR = 'plugins'
|
|
38
|
-
|
|
39
|
-
const USAGE = `Usage: jsbot [options]
|
|
40
|
-
|
|
41
|
-
Options:
|
|
42
|
-
--config <path> Config file path (env: BOT_CONFIG)
|
|
43
|
-
--data-dir <path> Data directory (env: BOT_DATA_DIR)
|
|
44
|
-
--log-level <level> debug, info, warn or error (env: BOT_LOG_LEVEL)
|
|
45
|
-
--help Show this help
|
|
46
|
-
|
|
47
|
-
Config precedence: CLI > environment > ./bot.config.json.
|
|
48
|
-
Data precedence: CLI > environment > ./data.
|
|
49
|
-
Local plugins: ./${LOCAL_PLUGIN_DIR}/<id>/dist (auto-built when missing).
|
|
50
|
-
`
|
|
51
|
-
|
|
52
|
-
function pick(value: unknown): string | undefined {
|
|
53
|
-
return typeof value === 'string' && value.trim().length > 0
|
|
54
|
-
? value
|
|
55
|
-
: undefined
|
|
56
|
-
}
|
|
29
|
+
import type { WebUiAddress } from './core/web/types.js'
|
|
57
30
|
|
|
58
|
-
|
|
59
|
-
label: string
|
|
60
|
-
plugins: AnyPluginDefinition[]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function mergePlugins(
|
|
64
|
-
sources: PluginSource[],
|
|
65
|
-
logger: Logger
|
|
66
|
-
): AnyPluginDefinition[] {
|
|
67
|
-
const seen = new Set<string>()
|
|
68
|
-
const plugins: AnyPluginDefinition[] = []
|
|
69
|
-
|
|
70
|
-
for (const source of sources) {
|
|
71
|
-
for (const plugin of source.plugins) {
|
|
72
|
-
const id = plugin.manifest.id
|
|
73
|
-
|
|
74
|
-
if (seen.has(id)) {
|
|
75
|
-
logger.warn(
|
|
76
|
-
`skipping ${source.label} plugin ${id}: plugin id already loaded`
|
|
77
|
-
)
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
seen.add(id)
|
|
82
|
-
plugins.push(plugin)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return plugins
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
interface RuntimeSettings {
|
|
90
|
-
logger: Logger
|
|
91
|
-
configPath: string
|
|
92
|
-
dataDir: string
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
interface RuntimeSpec {
|
|
96
|
-
config: AppConfig
|
|
97
|
-
dataDir: string
|
|
98
|
-
pluginDir: string
|
|
99
|
-
plugins: AnyPluginDefinition[]
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
interface Runtime {
|
|
103
|
-
spec: RuntimeSpec
|
|
104
|
-
kernel: Kernel
|
|
105
|
-
database: Database
|
|
106
|
-
installer: PluginInstaller
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
interface StaticBinding {
|
|
110
|
-
host: string
|
|
111
|
-
port: number
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function loadRuntimeSpec(
|
|
115
|
-
settings: RuntimeSettings
|
|
116
|
-
): Promise<RuntimeSpec> {
|
|
117
|
-
const { logger } = settings
|
|
118
|
-
|
|
119
|
-
logger.info('loading config', settings.configPath)
|
|
120
|
-
|
|
121
|
-
const config = await loadConfig(settings.configPath)
|
|
122
|
-
const pluginDir = resolve(LOCAL_PLUGIN_DIR)
|
|
123
|
-
|
|
124
|
-
logger.info('loading plugins from', pluginDir)
|
|
125
|
-
|
|
126
|
-
const localPlugins = await loadLocalPlugins(pluginDir, logger, {
|
|
127
|
-
buildTimeoutMs: config.installer.timeoutMs,
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
if (localPlugins.length > 0) {
|
|
131
|
-
logger.info(`loaded ${localPlugins.length} plugin(s) from ${pluginDir}`)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const plugins = mergePlugins(
|
|
135
|
-
[
|
|
136
|
-
{ label: 'built-in', plugins: builtinPlugins },
|
|
137
|
-
{ label: 'plugins', plugins: localPlugins },
|
|
138
|
-
],
|
|
139
|
-
logger
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
config,
|
|
144
|
-
dataDir: settings.dataDir,
|
|
145
|
-
pluginDir,
|
|
146
|
-
plugins,
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function createRuntime(
|
|
151
|
-
spec: RuntimeSpec,
|
|
152
|
-
settings: RuntimeSettings,
|
|
153
|
-
onRestart: () => void,
|
|
154
|
-
onEvent: (event: SnowLumaEvent) => void
|
|
155
|
-
): Runtime {
|
|
156
|
-
const { logger } = settings
|
|
157
|
-
const installer = new NpmPluginInstaller({
|
|
158
|
-
dir: spec.pluginDir,
|
|
159
|
-
config: spec.config.installer,
|
|
160
|
-
logger: logger.child('installer'),
|
|
161
|
-
reservedIds: () =>
|
|
162
|
-
new Set(spec.plugins.map((plugin) => plugin.manifest.id)),
|
|
163
|
-
configuredPluginIds: () => new Set(Object.keys(spec.config.plugins)),
|
|
164
|
-
})
|
|
165
|
-
const database = openDatabase({ dataDir: spec.dataDir })
|
|
166
|
-
|
|
167
|
-
const client = createBotClient({
|
|
168
|
-
url: spec.config.url,
|
|
169
|
-
...(spec.config.accessToken
|
|
170
|
-
? { accessToken: spec.config.accessToken }
|
|
171
|
-
: {}),
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
const kernel = new Kernel({
|
|
175
|
-
config: spec.config,
|
|
176
|
-
client,
|
|
177
|
-
logger,
|
|
178
|
-
database,
|
|
179
|
-
plugins: spec.plugins,
|
|
180
|
-
installer,
|
|
181
|
-
onRestart,
|
|
182
|
-
onEvent,
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
return { spec, kernel, database, installer }
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function describeError(error: unknown): string {
|
|
189
|
-
if (error instanceof Error) {
|
|
190
|
-
return error.stack ?? `${error.name}: ${error.message}`
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return String(error)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function credentialSignature(config: AppConfig): string {
|
|
197
|
-
const webui = config.webui
|
|
198
|
-
|
|
199
|
-
return [webui.username ?? '', webui.password ?? ''].join(':')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function resolveStaticDir(): string | null {
|
|
203
|
-
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
204
|
-
const candidates = [
|
|
205
|
-
join(scriptDir, 'webui'),
|
|
206
|
-
resolve(process.cwd(), 'dist/webui'),
|
|
207
|
-
]
|
|
208
|
-
|
|
209
|
-
for (const candidate of candidates) {
|
|
210
|
-
if (existsSync(join(candidate, 'index.html'))) return candidate
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return null
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function readVersion(): string {
|
|
217
|
-
try {
|
|
218
|
-
const pkgPath = new URL('../package.json', import.meta.url)
|
|
219
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as {
|
|
220
|
-
version?: string
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return pkg.version ?? 'unknown'
|
|
224
|
-
} catch {
|
|
225
|
-
return 'unknown'
|
|
226
|
-
}
|
|
227
|
-
}
|
|
31
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
|
|
228
32
|
|
|
229
33
|
async function main(): Promise<void> {
|
|
230
|
-
const
|
|
231
|
-
options: {
|
|
232
|
-
config: { type: 'string' },
|
|
233
|
-
'data-dir': { type: 'string' },
|
|
234
|
-
'log-level': { type: 'string' },
|
|
235
|
-
help: { type: 'boolean', short: 'h' },
|
|
236
|
-
},
|
|
237
|
-
allowPositionals: false,
|
|
238
|
-
})
|
|
34
|
+
const cli = parseCli()
|
|
239
35
|
|
|
240
|
-
if (
|
|
36
|
+
if (cli.help) {
|
|
241
37
|
process.stdout.write(USAGE)
|
|
242
38
|
return
|
|
243
39
|
}
|
|
244
40
|
|
|
245
|
-
const logs =
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
events.add(event)
|
|
249
|
-
}
|
|
250
|
-
const sink: LogSink = (record) => {
|
|
251
|
-
defaultLogSink(record)
|
|
252
|
-
logs.push(record)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const logger = createLogger({
|
|
256
|
-
level: parseLogLevel(
|
|
257
|
-
pick(values['log-level']) ?? process.env.BOT_LOG_LEVEL
|
|
258
|
-
),
|
|
259
|
-
scope: 'app',
|
|
260
|
-
sink,
|
|
261
|
-
})
|
|
41
|
+
const { logs, events, logger, tapEvents } = createAppLogging(
|
|
42
|
+
pick(cli.logLevel) ?? process.env.BOT_LOG_LEVEL
|
|
43
|
+
)
|
|
262
44
|
|
|
263
|
-
const
|
|
264
|
-
config: pick(
|
|
45
|
+
const settings = resolveRuntimeSettings(logger, {
|
|
46
|
+
config: pick(cli.config),
|
|
47
|
+
dataDir: pick(cli.dataDir),
|
|
265
48
|
env: process.env,
|
|
266
49
|
})
|
|
267
50
|
|
|
268
|
-
const dataDir =
|
|
269
|
-
dataDir: pick(values['data-dir']),
|
|
270
|
-
env: process.env,
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
const settings: RuntimeSettings = {
|
|
274
|
-
logger,
|
|
275
|
-
configPath,
|
|
276
|
-
dataDir,
|
|
277
|
-
}
|
|
51
|
+
const { configPath, dataDir } = settings
|
|
278
52
|
|
|
279
53
|
// Config file errors remain fatal: there is nothing the WebUI can restore
|
|
280
54
|
// from when the config cannot even be parsed.
|
|
@@ -287,19 +61,9 @@ async function main(): Promise<void> {
|
|
|
287
61
|
let shuttingDown = false
|
|
288
62
|
let restarting = false
|
|
289
63
|
let restartPending = false
|
|
290
|
-
let restartTimer: NodeJS.Timeout | null = null
|
|
291
|
-
let restartTail: Promise<boolean> = Promise.resolve(true)
|
|
292
|
-
let exclusiveTail: Promise<unknown> = Promise.resolve()
|
|
293
64
|
let boundWebUi: StaticBinding | null = null
|
|
294
65
|
let credentials = credentialSignature(initialSpec.config)
|
|
295
66
|
|
|
296
|
-
const restartDrainTimeout = (previous: Runtime | null): number => {
|
|
297
|
-
const installTimeout =
|
|
298
|
-
previous?.spec.config.installer.timeoutMs ?? 5_000
|
|
299
|
-
|
|
300
|
-
return Math.max(installTimeout, 5_000) * 2 + 5_000
|
|
301
|
-
}
|
|
302
|
-
|
|
303
67
|
const restartRuntimeOnce = async (): Promise<boolean> => {
|
|
304
68
|
if (shuttingDown) return false
|
|
305
69
|
|
|
@@ -374,32 +138,18 @@ async function main(): Promise<void> {
|
|
|
374
138
|
}
|
|
375
139
|
}
|
|
376
140
|
|
|
377
|
-
const restartRuntime = ()
|
|
378
|
-
const run = restartTail.then(restartRuntimeOnce, restartRuntimeOnce)
|
|
141
|
+
const restartRuntime = createSerialRunner(restartRuntimeOnce)
|
|
379
142
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
return run
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const requestRestart = (): void => {
|
|
389
|
-
if (shuttingDown) return
|
|
390
|
-
|
|
391
|
-
if (restarting) {
|
|
143
|
+
const restartRequester = createRestartRequester({
|
|
144
|
+
isShuttingDown: () => shuttingDown,
|
|
145
|
+
isRestarting: () => restarting,
|
|
146
|
+
markPending: () => {
|
|
392
147
|
restartPending = true
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (restartTimer !== null) return
|
|
148
|
+
},
|
|
149
|
+
restart: () => restartRuntime(),
|
|
150
|
+
})
|
|
397
151
|
|
|
398
|
-
|
|
399
|
-
restartTimer = null
|
|
400
|
-
void restartRuntime()
|
|
401
|
-
}, 250)
|
|
402
|
-
}
|
|
152
|
+
const requestRestart = restartRequester.request
|
|
403
153
|
|
|
404
154
|
const store = new ConfigStore({
|
|
405
155
|
configPath,
|
|
@@ -407,98 +157,48 @@ async function main(): Promise<void> {
|
|
|
407
157
|
restart: () => restartRuntime(),
|
|
408
158
|
})
|
|
409
159
|
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
version: readVersion(),
|
|
160
|
+
const runExclusive = createExclusiveRunner()
|
|
161
|
+
|
|
162
|
+
const deps = createWebUiDeps({
|
|
163
|
+
logger,
|
|
164
|
+
configPath,
|
|
165
|
+
dataDir,
|
|
166
|
+
pluginDir: initialSpec.pluginDir,
|
|
167
|
+
staticDir: resolveStaticDir(SCRIPT_DIR),
|
|
168
|
+
version: readVersion(SCRIPT_DIR),
|
|
419
169
|
logs,
|
|
420
170
|
events,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
171
|
+
store,
|
|
172
|
+
getCurrentConfig: () => currentConfig,
|
|
173
|
+
getRuntime: () => runtime,
|
|
174
|
+
getGeneration: () => generation,
|
|
425
175
|
restart: () => restartRuntime(),
|
|
426
176
|
requestRestart,
|
|
427
|
-
pause: () => runtime?.kernel.pause(),
|
|
428
|
-
resume: () => runtime?.kernel.resume(),
|
|
429
177
|
getListening: () => server.address,
|
|
430
178
|
invalidateSessions: () => server.invalidateSessions(),
|
|
431
|
-
runExclusive
|
|
432
|
-
const run = exclusiveTail.then(task, task)
|
|
433
|
-
|
|
434
|
-
exclusiveTail = run.then(
|
|
435
|
-
() => undefined,
|
|
436
|
-
() => undefined
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
return run
|
|
440
|
-
},
|
|
441
|
-
getRuntime: () =>
|
|
442
|
-
runtime === null
|
|
443
|
-
? null
|
|
444
|
-
: {
|
|
445
|
-
generation,
|
|
446
|
-
config: runtime.spec.config,
|
|
447
|
-
kernel: runtime.kernel,
|
|
448
|
-
api: runtime.kernel.api,
|
|
449
|
-
database: runtime.database,
|
|
450
|
-
installer: runtime.installer,
|
|
451
|
-
plugins: runtime.spec.plugins,
|
|
452
|
-
},
|
|
179
|
+
runExclusive,
|
|
453
180
|
isRestarting: () => restarting,
|
|
454
181
|
getStartupError: () => startupError,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (boundWebUi === null) return webui.enabled
|
|
459
|
-
|
|
460
|
-
if (!webui.enabled) return true
|
|
461
|
-
|
|
462
|
-
return (
|
|
463
|
-
webui.host !== boundWebUi.host || webui.port !== boundWebUi.port
|
|
464
|
-
)
|
|
465
|
-
},
|
|
466
|
-
}
|
|
182
|
+
getBoundWebUi: () => boundWebUi,
|
|
183
|
+
})
|
|
467
184
|
|
|
468
185
|
const server = new WebUiServer({
|
|
469
186
|
config: initialSpec.config.webui,
|
|
470
187
|
deps,
|
|
471
188
|
})
|
|
472
189
|
|
|
473
|
-
const shutdown =
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
logger.info(`received ${signal}, shutting down`)
|
|
484
|
-
|
|
485
|
-
try {
|
|
486
|
-
await runtime?.kernel.stop()
|
|
487
|
-
await server.stop()
|
|
488
|
-
process.exit(exitCode)
|
|
489
|
-
} catch (error) {
|
|
490
|
-
logger.error('shutdown failed', error)
|
|
491
|
-
process.exit(1)
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
process.once('SIGINT', () => {
|
|
496
|
-
void shutdown('SIGINT')
|
|
190
|
+
const shutdown = createShutdown({
|
|
191
|
+
logger,
|
|
192
|
+
isShuttingDown: () => shuttingDown,
|
|
193
|
+
onBegin: () => {
|
|
194
|
+
shuttingDown = true
|
|
195
|
+
restartRequester.clear()
|
|
196
|
+
},
|
|
197
|
+
getRuntime: () => runtime,
|
|
198
|
+
stopServer: () => server.stop(),
|
|
497
199
|
})
|
|
498
200
|
|
|
499
|
-
|
|
500
|
-
void shutdown('SIGTERM')
|
|
501
|
-
})
|
|
201
|
+
registerShutdownSignals(shutdown)
|
|
502
202
|
|
|
503
203
|
// Start the WebUI before the kernel so it stays online when startup fails.
|
|
504
204
|
if (initialSpec.config.webui.enabled) {
|
|
@@ -514,32 +214,24 @@ async function main(): Promise<void> {
|
|
|
514
214
|
}
|
|
515
215
|
}
|
|
516
216
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (boundWebUi === null) {
|
|
536
|
-
await shutdown('startup failure', 1)
|
|
537
|
-
} else {
|
|
538
|
-
logger.info(
|
|
539
|
-
'webui stays online; fix the configuration on the config page and restart the runtime'
|
|
540
|
-
)
|
|
541
|
-
}
|
|
542
|
-
}
|
|
217
|
+
const started = await startInitialRuntime({
|
|
218
|
+
spec: initialSpec,
|
|
219
|
+
settings,
|
|
220
|
+
requestRestart,
|
|
221
|
+
onEvent: tapEvents,
|
|
222
|
+
getCurrentConfig: () => currentConfig,
|
|
223
|
+
hasWebUi: boundWebUi !== null,
|
|
224
|
+
onFailure: () => shutdown('startup failure', 1),
|
|
225
|
+
setRuntime: (value) => {
|
|
226
|
+
runtime = value
|
|
227
|
+
},
|
|
228
|
+
generation,
|
|
229
|
+
credentials,
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
generation = started.generation
|
|
233
|
+
startupError = started.startupError
|
|
234
|
+
credentials = started.credentials
|
|
543
235
|
}
|
|
544
236
|
|
|
545
237
|
await main().catch((error: unknown) => {
|
|
@@ -89,6 +89,12 @@ function uniqueStrings(values: string[]): string[] {
|
|
|
89
89
|
return result
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
const LEADING_REPLY = /^(?:\[CQ:reply(?:,[^\]]*)?\]\s*)+/
|
|
93
|
+
|
|
94
|
+
function stripLeadingReply(raw: string): string {
|
|
95
|
+
return raw.replace(LEADING_REPLY, '')
|
|
96
|
+
}
|
|
97
|
+
|
|
92
98
|
function groupBases(name: string, aliases: string[]): string[] {
|
|
93
99
|
return uniqueStrings([
|
|
94
100
|
normalizeSegment('group name', name),
|
|
@@ -258,7 +264,7 @@ export class CommandRegistry {
|
|
|
258
264
|
}
|
|
259
265
|
|
|
260
266
|
match(rawMessage: string): RegisteredCommand | null {
|
|
261
|
-
const raw = rawMessage.trim()
|
|
267
|
+
const raw = stripLeadingReply(rawMessage.trim()).trim()
|
|
262
268
|
|
|
263
269
|
if (!raw) return null
|
|
264
270
|
|