@jsbot-tellus/core 2.0.3 → 2.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/README.md +13 -17
- package/dist/app.js +12 -40
- package/dist/app.js.map +1 -1
- package/dist/core/config/schema.d.ts +0 -2
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +0 -1
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/dispatch/command.d.ts +24 -2
- package/dist/core/dispatch/command.d.ts.map +1 -1
- package/dist/core/dispatch/command.js +297 -60
- package/dist/core/dispatch/command.js.map +1 -1
- package/dist/core/dispatch/router.d.ts +17 -3
- package/dist/core/dispatch/router.d.ts.map +1 -1
- package/dist/core/dispatch/router.js +38 -2
- package/dist/core/dispatch/router.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/plugin/command-params.d.ts +15 -0
- package/dist/core/plugin/command-params.d.ts.map +1 -0
- package/dist/core/plugin/command-params.js +427 -0
- package/dist/core/plugin/command-params.js.map +1 -0
- package/dist/core/plugin/context.d.ts.map +1 -1
- package/dist/core/plugin/context.js +27 -0
- package/dist/core/plugin/context.js.map +1 -1
- package/dist/core/plugin/install-dir.d.ts +6 -9
- package/dist/core/plugin/install-dir.d.ts.map +1 -1
- package/dist/core/plugin/install-dir.js +99 -79
- package/dist/core/plugin/install-dir.js.map +1 -1
- package/dist/core/plugin/install-validate.d.ts +2 -1
- package/dist/core/plugin/install-validate.d.ts.map +1 -1
- package/dist/core/plugin/install-validate.js +25 -17
- package/dist/core/plugin/install-validate.js.map +1 -1
- package/dist/core/plugin/installer.d.ts +3 -3
- package/dist/core/plugin/installer.d.ts.map +1 -1
- package/dist/core/plugin/installer.js +143 -140
- package/dist/core/plugin/installer.js.map +1 -1
- package/dist/core/plugin/loader.d.ts +1 -0
- package/dist/core/plugin/loader.d.ts.map +1 -1
- package/dist/core/plugin/loader.js +24 -2
- package/dist/core/plugin/loader.js.map +1 -1
- package/dist/core/plugin/types.d.ts +11 -3
- package/dist/core/plugin/types.d.ts.map +1 -1
- package/dist/core/plugin/types.js.map +1 -1
- package/dist/plugins/admin/index.d.ts.map +1 -1
- package/dist/plugins/admin/index.js +119 -86
- package/dist/plugins/admin/index.js.map +1 -1
- package/dist/plugins/core/index.d.ts.map +1 -1
- package/dist/plugins/core/index.js +4 -1
- package/dist/plugins/core/index.js.map +1 -1
- package/docs/architecture.md +22 -24
- package/docs/configuration.md +19 -23
- package/docs/feature-parity.md +4 -4
- package/docs/migration.md +13 -13
- package/docs/plugin-api.md +156 -26
- package/docs/plugin-development.md +390 -0
- package/docs/release.md +2 -2
- package/examples/plugins/hello/README.md +10 -5
- package/examples/plugins/hello/index.js +3 -6
- package/package.json +2 -1
- package/src/app.ts +16 -55
- package/src/core/config/schema.ts +0 -1
- package/src/core/dispatch/command.ts +474 -70
- package/src/core/dispatch/router.ts +77 -3
- package/src/core/index.ts +2 -0
- package/src/core/plugin/command-params.ts +595 -0
- package/src/core/plugin/context.ts +47 -2
- package/src/core/plugin/install-dir.ts +117 -111
- package/src/core/plugin/install-validate.ts +36 -34
- package/src/core/plugin/installer.ts +228 -184
- package/src/core/plugin/loader.ts +36 -2
- package/src/core/plugin/types.ts +23 -3
- package/src/plugins/admin/index.ts +302 -233
- package/src/plugins/core/index.ts +5 -1
- package/dist/core/plugin/installed.d.ts +0 -9
- package/dist/core/plugin/installed.d.ts.map +0 -1
- package/dist/core/plugin/installed.js +0 -113
- package/dist/core/plugin/installed.js.map +0 -1
- package/src/core/plugin/installed.ts +0 -165
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# 插件开发指南
|
|
2
|
+
|
|
3
|
+
本文面向插件开发者,说明插件应该放在哪里、如何创建和构建、如何加载调试,以及发布到 npm 前需要检查什么。
|
|
4
|
+
|
|
5
|
+
API 细节(`PluginContext`、命令、事件、中间件、KV、Session 等)见[插件 API](plugin-api.md)。
|
|
6
|
+
|
|
7
|
+
## 先选插件类型
|
|
8
|
+
|
|
9
|
+
| 类型 | 推荐位置 | 是否进入本仓库 | 加载方式 | 适用场景 |
|
|
10
|
+
| -------- | ------------------- | -------------- | ---------------------------------- | ---------------------------- |
|
|
11
|
+
| 内置插件 | `src/plugins/<id>/` | 是 | 注册到 `src/plugins/index.ts` | 随内核发布的官方功能 |
|
|
12
|
+
| 本地插件 | `plugins/<id>/` | 否 | 启动时自动扫描 `plugins/<id>/dist` | 个人使用、内部功能、开发调试 |
|
|
13
|
+
| npm 插件 | 独立仓库或独立包 | 独立管理 | `/plugin install <包名>[@版本]` | 发布给其他用户安装 |
|
|
14
|
+
|
|
15
|
+
位置约定:
|
|
16
|
+
|
|
17
|
+
- `plugins/` 已被 `.gitignore` 忽略,适合放不准备提交到本仓库的本地插件。
|
|
18
|
+
- 只有准备随内核一起发布的官方插件才放进 `src/plugins/`;本地插件不要直接写进 `src/`。
|
|
19
|
+
- 目录名建议与 `manifest.id` 一致,使用 kebab-case,例如 `plugins/my-plugin/` 对应 `id: 'my-plugin'`。
|
|
20
|
+
- 发布到 npm 的插件,`manifest.id` 必须匹配 `[a-z0-9][a-z0-9._-]*`;本地插件也建议使用相同的 kebab-case 格式。无论哪种插件,ID 都必须在所有已加载插件中唯一。
|
|
21
|
+
- 插件 ID 重复时按“内置插件 → `plugins/` 目录(本地插件和在线安装的插件)”的顺序保留先加载的插件,后面的会被跳过并记录警告。
|
|
22
|
+
|
|
23
|
+
## 目录结构
|
|
24
|
+
|
|
25
|
+
### 本地插件
|
|
26
|
+
|
|
27
|
+
推荐结构:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
plugins/my-plugin/
|
|
31
|
+
package.json
|
|
32
|
+
tsconfig.json
|
|
33
|
+
src/
|
|
34
|
+
index.ts
|
|
35
|
+
dist/
|
|
36
|
+
index.js
|
|
37
|
+
index.d.ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `src/index.ts` 是开发入口。
|
|
41
|
+
- `dist/index.js` 是运行时入口。
|
|
42
|
+
- 构建产物固定放在 `plugins/<id>/dist`,启动时自动扫描;不要指向或加载 `src/`。
|
|
43
|
+
- 每个本地插件都是独立包,有自己的 `package.json`、lockfile 和 `node_modules`。
|
|
44
|
+
|
|
45
|
+
### 内置插件
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
src/plugins/my-plugin/
|
|
49
|
+
index.ts
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
新增内置插件后需要在 `src/plugins/index.ts` 中导入并加入 `builtinPlugins`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import type { AnyPluginDefinition } from '../core/index.js'
|
|
57
|
+
import { adminPlugin } from './admin/index.js'
|
|
58
|
+
import { corePlugin } from './core/index.js'
|
|
59
|
+
import { myPlugin } from './my-plugin/index.js'
|
|
60
|
+
|
|
61
|
+
export const builtinPlugins: AnyPluginDefinition[] = [
|
|
62
|
+
corePlugin,
|
|
63
|
+
adminPlugin,
|
|
64
|
+
myPlugin,
|
|
65
|
+
]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
内置插件和内核共用根目录的 `tsconfig.build.json`,执行 `npm run build` 后会输出到 `dist/plugins/`。
|
|
69
|
+
|
|
70
|
+
### npm 插件
|
|
71
|
+
|
|
72
|
+
npm 插件通常在独立仓库中开发,发布构建后的 `dist/`。安装器会把它直接放到 `plugins/<插件id>/`,和本地插件一样从 `plugins/<插件id>/dist` 加载。首次安装时,一个包导出多个插件会使用包名作为目录(例如 `@scope/bundle` 对应 `plugins/@scope__bundle/`)。
|
|
73
|
+
|
|
74
|
+
## 从零创建本地插件
|
|
75
|
+
|
|
76
|
+
### 快速生成
|
|
77
|
+
|
|
78
|
+
在仓库根目录执行:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm run plugin:gen -- my-plugin
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
也可以不带参数运行 `npm run plugin:gen`,在终端中交互输入插件 id。
|
|
85
|
+
|
|
86
|
+
生成结果是 `plugins/my-plugin/`,包含:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
plugins/my-plugin/
|
|
90
|
+
package.json
|
|
91
|
+
tsconfig.json
|
|
92
|
+
README.md
|
|
93
|
+
src/index.ts
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
可选参数:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run plugin:gen -- my-plugin --name @yourscope/plugin-my-plugin
|
|
100
|
+
npm run plugin:gen -- my-plugin --description "My plugin"
|
|
101
|
+
npm run plugin:gen -- my-plugin --no-install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
生成器默认会执行 `npm install`。如果使用 `--no-install` 或安装失败,可以手动补装:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm install --prefix plugins/my-plugin
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
然后构建并启动:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm run build --prefix plugins/my-plugin
|
|
114
|
+
node dist/app.js
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
如果目标目录已经存在,命令会拒绝覆盖。
|
|
118
|
+
|
|
119
|
+
下面以手动创建 `plugins/my-plugin/` 为例。
|
|
120
|
+
|
|
121
|
+
### 1. 创建 package.json
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"name": "@yourscope/plugin-my-plugin",
|
|
126
|
+
"version": "0.1.0",
|
|
127
|
+
"private": true,
|
|
128
|
+
"type": "module",
|
|
129
|
+
"main": "./dist/index.js",
|
|
130
|
+
"types": "./dist/index.d.ts",
|
|
131
|
+
"scripts": {
|
|
132
|
+
"build": "tsc -p tsconfig.json",
|
|
133
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
134
|
+
"watch": "tsc -p tsconfig.json --watch"
|
|
135
|
+
},
|
|
136
|
+
"devDependencies": {
|
|
137
|
+
"@jsbot-tellus/core": "^2.0.3",
|
|
138
|
+
"@types/node": "^24.6.4",
|
|
139
|
+
"typescript": "^7.0.2"
|
|
140
|
+
},
|
|
141
|
+
"dependencies": {
|
|
142
|
+
"zod": "^4.6.7"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
依赖约定:
|
|
148
|
+
|
|
149
|
+
- `@jsbot-tellus/core` 只用于类型和 `definePlugin`,放在 `devDependencies`。
|
|
150
|
+
- 插件自己的第三方运行时依赖放在 `dependencies`。
|
|
151
|
+
- 不要把 `@jsbot-tellus/core` 写进 `dependencies` 或 `peerDependencies`。
|
|
152
|
+
- 通过 `/plugin install` 安装的包,宿主会在 `plugins/<目录>/node_modules/@jsbot-tellus/core` 创建链接;手动放在 `plugins/` 的本地插件需要自己安装 core。正常执行 `npm install --prefix plugins/<id>` 会安装 `devDependencies`,已经满足这个条件。
|
|
153
|
+
- 在本仓库内联调本地内核时,可以把 core 改成 `"file:../.."`,但需要先执行根目录的 `npm run build`,并且发布前要改回版本号。
|
|
154
|
+
- 仅在本机使用的插件可以保留 `"private": true`;准备发布到 npm 时删除它。
|
|
155
|
+
|
|
156
|
+
### 2. 创建 tsconfig.json
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"extends": "../../tsconfig.base.json",
|
|
161
|
+
"compilerOptions": {
|
|
162
|
+
"noEmit": false,
|
|
163
|
+
"outDir": "dist",
|
|
164
|
+
"rootDir": "src",
|
|
165
|
+
"declaration": true,
|
|
166
|
+
"sourceMap": true
|
|
167
|
+
},
|
|
168
|
+
"include": ["src"]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
如果插件不在本仓库内,把 `extends` 换成自己的基础配置即可。使用 NodeNext 时,TypeScript 源码里的相对导入要带 `.js` 扩展名,例如:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { helper } from './helper.js'
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 3. 编写入口
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { definePlugin } from '@jsbot-tellus/core'
|
|
182
|
+
import { z } from 'zod'
|
|
183
|
+
|
|
184
|
+
export default definePlugin({
|
|
185
|
+
manifest: {
|
|
186
|
+
id: 'my-plugin',
|
|
187
|
+
version: '0.1.0',
|
|
188
|
+
description: 'My local plugin',
|
|
189
|
+
defaultEnabled: true,
|
|
190
|
+
configSchema: z
|
|
191
|
+
.object({
|
|
192
|
+
reply: z.string().default('hello from my plugin'),
|
|
193
|
+
})
|
|
194
|
+
.strict(),
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
setup(ctx) {
|
|
198
|
+
ctx.commands.register(
|
|
199
|
+
{
|
|
200
|
+
name: 'hello',
|
|
201
|
+
permission: 'User',
|
|
202
|
+
description: '发送一条问候',
|
|
203
|
+
params: '[name:string="世界"]',
|
|
204
|
+
},
|
|
205
|
+
async (_event, context, _match, name: string) => {
|
|
206
|
+
await context.send(`${name}:${ctx.config.reply}`)
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
需要注意:
|
|
214
|
+
|
|
215
|
+
- `definePlugin` 只提供类型推导,不会产生全局副作用。
|
|
216
|
+
- `setup()` 只注册命令、事件和中间件;定时器、连接等运行时资源放在 `start()`,并在 `stop()` 中清理。
|
|
217
|
+
- 命令可以用 `params` 声明位置参数;解析结果从 handler 第四个参数开始传入,用法会自动出现在 `/help` 和指令组提示中。详见[插件 API 的参数章节](plugin-api.md#参数)。
|
|
218
|
+
- 多级命令(例如 `/plugin install`)使用 `ctx.commands.group()`,不要直接注册带空格的命令名。详见[插件 API 的命令章节](plugin-api.md#命令)。
|
|
219
|
+
- 修改命令、事件或配置后,完整重启进程最可靠;自动重启不会清空 ESM 模块缓存。
|
|
220
|
+
|
|
221
|
+
### 4. 安装依赖并构建
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npm install --prefix plugins/my-plugin
|
|
225
|
+
npm run build
|
|
226
|
+
npm run build --prefix plugins/my-plugin
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
根目录的 `npm run build:local` / `npm run check:local` 目前只覆盖 `plugins/poke` 和 `plugins/llm`。新增插件请使用 `npm run build --prefix plugins/<id>` 和 `npm run check --prefix plugins/<id>`,或者把新目录加入根 `package.json` 的对应脚本。
|
|
230
|
+
|
|
231
|
+
开发时可以单独开一个终端监听:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npm run watch --prefix plugins/my-plugin
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 5. 启动并加载
|
|
238
|
+
|
|
239
|
+
本地插件目录固定为 `plugins/`,相对当前工作目录解析。应用启动时会自动扫描:
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
plugins/
|
|
243
|
+
my-plugin/
|
|
244
|
+
dist/
|
|
245
|
+
index.js
|
|
246
|
+
another-plugin/
|
|
247
|
+
dist/
|
|
248
|
+
index.js
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
node dist/app.js
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
开发模式:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npm run dev
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
不需要额外参数;多个本地插件按 `plugins/<id>/dist` 并存。没有 `dist` 的目录会被跳过并记录警告。
|
|
262
|
+
|
|
263
|
+
### 6. 配置插件
|
|
264
|
+
|
|
265
|
+
在 `bot.config.json` 中按插件 ID 配置:
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"plugins": {
|
|
270
|
+
"my-plugin": {
|
|
271
|
+
"enabled": true,
|
|
272
|
+
"reply": "hello from config"
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
- `enabled`、`private`、`groups` 是内核处理的信封字段,不会传给 `configSchema`。
|
|
279
|
+
- 其它字段由插件的 `configSchema` 校验;未知字段会报错,错误信息会带 `plugins.my-plugin.xxx` 这样的路径。
|
|
280
|
+
- 如果 `manifest.defaultEnabled` 为 `true`,可以省略 `enabled`。
|
|
281
|
+
- 配置里出现未加载的插件 ID 会导致启动失败;删除插件后记得同步删除对应配置。
|
|
282
|
+
|
|
283
|
+
## 加载规则
|
|
284
|
+
|
|
285
|
+
- 加载器会递归扫描每个 `plugins/<id>/dist` 中的 `.js`、`.mjs`、`.cjs`、`.ts`、`.mts`、`.cts` 文件,忽略 `node_modules` 和 `.d.ts`。
|
|
286
|
+
- 只识别以下导出形式:
|
|
287
|
+
- `export default plugin`
|
|
288
|
+
- `export const plugin = ...`
|
|
289
|
+
- `export const plugins = [...]`
|
|
290
|
+
- 没有导出插件定义的 helper 模块会被跳过。
|
|
291
|
+
- 生产环境应加载编译后的 JavaScript;TypeScript 源码必须先构建,或者在使用 `tsx` 的开发环境中运行。
|
|
292
|
+
- 空目录不会报错;如果目录中存在无法导入或无法识别的文件,并且最终没有加载到任何有效插件,启动失败。只要至少有一个有效插件,其他无法识别的文件会记录警告并跳过。
|
|
293
|
+
- 本地插件和在线安装的插件都从 `plugins/<目录>/dist` 自动加载,没有单独的安装目录。
|
|
294
|
+
- 同一个 ID 同时来自内置插件和 `plugins/` 中的多个目录时,只有最先加载的那个生效。
|
|
295
|
+
|
|
296
|
+
## 调试与常见问题
|
|
297
|
+
|
|
298
|
+
| 现象 | 可能原因 | 处理 |
|
|
299
|
+
| ---------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
300
|
+
| `skipping local plugin ... dist directory not found` | 插件目录没有构建产物 | 执行 `npm run build --prefix plugins/<id>` |
|
|
301
|
+
| `failed to import ...` | TypeScript 未编译、入口有运行时错误 | 先构建;查看日志中的具体导入错误 |
|
|
302
|
+
| `dist` 目录没有有效插件 | 没有导出 `default` / `plugin` / `plugins` | 检查入口导出和 `dist` 中的构建输出 |
|
|
303
|
+
| `skipping ... plugin id already loaded` | 插件 ID 重复 | 修改 `manifest.id`,或移除重复的插件目录 |
|
|
304
|
+
| `unknown plugin id` | 配置里写了未加载的插件 ID | 启用插件、修正 ID,或删除 `plugins.<id>` 配置 |
|
|
305
|
+
| 命令没有响应 | 插件未启用、权限不足、群组 gating、命令前缀不匹配 | 检查 `plugins.<id>.enabled`、角色、群组配置和命令前缀 |
|
|
306
|
+
| 修改代码后行为没变 | ESM 模块缓存、自动重启未刷新本地插件 | 完整退出并重新启动进程 |
|
|
307
|
+
| `Cannot find module '@jsbot-tellus/core'` | 没有安装 devDependency,或 `file:` 依赖尚未构建 | 在插件目录执行 `npm install`;使用 `file:` 时先构建根项目 |
|
|
308
|
+
| `/plugin install` 拒绝包 | 使用了路径、`file:`、git/url、范围版本或未启用安装器 | 改用 npm 包名 + 精确版本或 dist-tag;确认 `installer.enabled` 和 allowlist |
|
|
309
|
+
| 安装完成但提示网络错误 | 安装后的通知发送失败,不代表安装失败 | 用 `/plugin list` 确认;必要时手动重启进程 |
|
|
310
|
+
|
|
311
|
+
注意:安装器在安装或更新成功后会请求重建运行时,但安装后的通知发送失败不会回滚安装。遇到“网络连接异常”时优先检查 `/plugin list` 和 `plugins/.jsbot-installed.json`。
|
|
312
|
+
|
|
313
|
+
## 发布到 npm
|
|
314
|
+
|
|
315
|
+
发布前确认包满足以下要求:
|
|
316
|
+
|
|
317
|
+
- 使用 ESM:`"type": "module"`。
|
|
318
|
+
- 发布 `dist/` 中的 JavaScript 和 `.d.ts`,不要依赖安装后的 `postinstall` 构建;运行时加载器会扫描 `dist/`。
|
|
319
|
+
- 建议通过 `main` 或 `exports["."]` 指向 `dist/index.js`,方便类型和工具解析。
|
|
320
|
+
- `dist/` 中导出的插件定义会被加载:`export default plugin`、`export const plugin = ...` 或 `export const plugins = [...]`。
|
|
321
|
+
- `manifest.id` 全局唯一,不能与内置插件、`plugins/` 本地插件或其它已安装插件冲突。
|
|
322
|
+
- 插件之间的依赖写在 `manifest.dependencies`,依赖的插件必须由同一个包导出,或已经由其它已安装包提供。
|
|
323
|
+
- 第三方运行时依赖写入 `dependencies`。
|
|
324
|
+
- 不要依赖源码仓库或相对的 `file:` 依赖。
|
|
325
|
+
- 不要把 `@jsbot-tellus/core` 写进 `dependencies` 或 `peerDependencies`;宿主会在 `plugins/<目录>/node_modules/@jsbot-tellus/core` 创建链接,运行时直接解析到当前内核。
|
|
326
|
+
- 安装器默认使用 `--ignore-scripts`;只有配置 `installer.allowScripts=true` 才会执行 npm 脚本。
|
|
327
|
+
|
|
328
|
+
一个可发布的 `package.json` 示例:
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"name": "@yourscope/plugin-demo",
|
|
333
|
+
"version": "1.0.0",
|
|
334
|
+
"type": "module",
|
|
335
|
+
"main": "./dist/index.js",
|
|
336
|
+
"types": "./dist/index.d.ts",
|
|
337
|
+
"exports": {
|
|
338
|
+
".": {
|
|
339
|
+
"types": "./dist/index.d.ts",
|
|
340
|
+
"import": "./dist/index.js"
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"files": ["dist"],
|
|
344
|
+
"scripts": {
|
|
345
|
+
"build": "tsc -p tsconfig.json",
|
|
346
|
+
"prepack": "npm run build"
|
|
347
|
+
},
|
|
348
|
+
"devDependencies": {
|
|
349
|
+
"@jsbot-tellus/core": "^2.0.3",
|
|
350
|
+
"@types/node": "^24.6.4",
|
|
351
|
+
"typescript": "^7.0.2"
|
|
352
|
+
},
|
|
353
|
+
"dependencies": {
|
|
354
|
+
"zod": "^4.6.7"
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
发布后在聊天中安装:
|
|
360
|
+
|
|
361
|
+
```text
|
|
362
|
+
/plugin install @yourscope/plugin-demo@1.0.0
|
|
363
|
+
/plugin install @yourscope/plugin-demo@latest
|
|
364
|
+
/plugin list
|
|
365
|
+
/plugin update @yourscope/plugin-demo
|
|
366
|
+
/plugin remove @yourscope/plugin-demo
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
安装器限制:
|
|
370
|
+
|
|
371
|
+
- 只接受 npm registry 包,不接受本地路径、`file:`、`workspace:`、`link:`、git/github 地址或 `http(s)` URL。
|
|
372
|
+
- 版本只接受精确版本或 dist-tag,不接受 `^`、`~`、`1.x` 等范围。
|
|
373
|
+
- `/plugin` 的所有子命令都要求 SuperAdmin,安装器默认关闭。
|
|
374
|
+
- 安装后会直接落在 `plugins/<插件id>/`(多插件包使用包名目录),运行时依赖在该目录的 `node_modules`。`plugins/.jsbot-installed.json` 由内核维护,不要手动修改。目标目录已被手动放置的本地插件占用时,安装会被拒绝。
|
|
375
|
+
- 安装、更新、卸载成功后会自动重建运行时;进程本身不会退出。
|
|
376
|
+
|
|
377
|
+
## 安全边界
|
|
378
|
+
|
|
379
|
+
插件和内核运行在同一个 Node.js 进程中,不是安全沙箱。插件可以访问文件系统、网络和 `process.env`,也可以执行任意代码。只加载和安装完全信任的插件;生产环境建议配置 `installer.allowedPackages`,并保持 `installer.allowScripts=false`。
|
|
380
|
+
|
|
381
|
+
## 开发检查清单
|
|
382
|
+
|
|
383
|
+
- 插件目录位置符合约定:本地插件在 `plugins/<id>/`,内置插件在 `src/plugins/<id>/`。
|
|
384
|
+
- `manifest.id` 唯一;发布到 npm 时符合 `[a-z0-9][a-z0-9._-]*` 格式。
|
|
385
|
+
- `configSchema` 使用 `.strict()`,默认值完整。
|
|
386
|
+
- TypeScript 构建通过,运行时加载的是 `dist`。
|
|
387
|
+
- 命令名是单个路径段;多级命令使用 `ctx.commands.group()`。
|
|
388
|
+
- 命令权限和群组 gating 符合预期。
|
|
389
|
+
- 重启进程后插件仍能正常加载。
|
|
390
|
+
- 发布包不包含本地绝对路径、`file:` 依赖或对 `@jsbot-tellus/core` 的运行时依赖。
|
package/docs/release.md
CHANGED
|
@@ -46,9 +46,9 @@ git commit -m "Release v$(node -p "require('./package.json').version")"
|
|
|
46
46
|
git push
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
将 `patch` 换成 `minor` 或 `major`
|
|
49
|
+
将 `patch` 换成 `minor` 或 `major` 可以发布对应版本。提交并通过 Pull Request 合并到 `main` 后,CI 会先运行;CI 成功后 CD 自动发布该版本。`main` 受分支保护,不能直接推送。
|
|
50
50
|
|
|
51
|
-
如果 `main` 上的版本已经在 npm 存在,CD
|
|
51
|
+
如果 `main` 上的版本已经在 npm 存在,CD 只会输出提示并跳过,不会失败。因此版本提交重复合并不影响已发布版本。
|
|
52
52
|
|
|
53
53
|
## 手动运行
|
|
54
54
|
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
# Hello
|
|
1
|
+
# Hello local plugin
|
|
2
2
|
|
|
3
|
-
This is a minimal
|
|
4
|
-
|
|
3
|
+
This is a minimal local plugin. It registers `/hello` and `/hey`, and reads its
|
|
4
|
+
own `reply` option from `plugins.hello`.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Build the project first, then copy the example into the fixed local plugin
|
|
7
|
+
directory:
|
|
7
8
|
|
|
8
9
|
```bash
|
|
9
10
|
npm run build
|
|
10
|
-
|
|
11
|
+
mkdir -p plugins/hello/dist
|
|
12
|
+
cp examples/plugins/hello/index.js plugins/hello/dist/index.js
|
|
13
|
+
node dist/app.js
|
|
11
14
|
```
|
|
12
15
|
|
|
16
|
+
The app automatically loads `plugins/<id>/dist`.
|
|
17
|
+
|
|
13
18
|
Enable and configure it in `bot.config.json`:
|
|
14
19
|
|
|
15
20
|
```json
|
|
@@ -4,15 +4,12 @@ export default {
|
|
|
4
4
|
manifest: {
|
|
5
5
|
id: 'hello',
|
|
6
6
|
version: '1.0.0',
|
|
7
|
-
description: '
|
|
7
|
+
description: 'Local hello command example',
|
|
8
8
|
priority: 10,
|
|
9
9
|
defaultEnabled: true,
|
|
10
10
|
configSchema: z
|
|
11
11
|
.object({
|
|
12
|
-
reply: z
|
|
13
|
-
.string()
|
|
14
|
-
.min(1)
|
|
15
|
-
.default('hello from an external plugin'),
|
|
12
|
+
reply: z.string().min(1).default('hello from a local plugin'),
|
|
16
13
|
})
|
|
17
14
|
.strict(),
|
|
18
15
|
},
|
|
@@ -23,7 +20,7 @@ export default {
|
|
|
23
20
|
name: 'hello',
|
|
24
21
|
aliases: ['hey'],
|
|
25
22
|
permission: 'User',
|
|
26
|
-
description: '
|
|
23
|
+
description: 'Local plugin demo',
|
|
27
24
|
},
|
|
28
25
|
async (_event, context) => {
|
|
29
26
|
await context.send(ctx.config.reply)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsbot-tellus/core",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": "^24.22.1 || >=26.12.1"
|
|
6
6
|
},
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"prepublishOnly": "npm run check && npm test",
|
|
53
53
|
"build:local": "npm run build --prefix plugins/poke && npm run build --prefix plugins/llm",
|
|
54
54
|
"check:local": "npm run check --prefix plugins/poke && npm run check --prefix plugins/llm",
|
|
55
|
+
"plugin:gen": "tsx scripts/plugin-gen.ts",
|
|
55
56
|
"start": "node dist/app.js",
|
|
56
57
|
"start:build": "npm run build && node dist/app.js",
|
|
57
58
|
"start:source": "tsx src/app.ts",
|
package/src/app.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from 'node:path'
|
|
2
3
|
import { parseArgs } from 'node:util'
|
|
3
4
|
import { createBotClient } from './core/client.js'
|
|
4
5
|
import type { AppConfig } from './core/config/schema.js'
|
|
@@ -14,25 +15,25 @@ import {
|
|
|
14
15
|
parseLogLevel,
|
|
15
16
|
type Logger,
|
|
16
17
|
} from './core/logging/logger.js'
|
|
17
|
-
import { resolveInstallDir } from './core/plugin/install-dir.js'
|
|
18
18
|
import { NpmPluginInstaller } from './core/plugin/installer.js'
|
|
19
|
-
import {
|
|
20
|
-
import { loadPluginsFromDirectory } from './core/plugin/loader.js'
|
|
19
|
+
import { loadLocalPlugins } from './core/plugin/loader.js'
|
|
21
20
|
import type { AnyPluginDefinition } from './core/plugin/types.js'
|
|
22
21
|
import { openDatabase } from './core/storage/database.js'
|
|
23
22
|
import { builtinPlugins } from './plugins/index.js'
|
|
24
23
|
|
|
24
|
+
const LOCAL_PLUGIN_DIR = 'plugins'
|
|
25
|
+
|
|
25
26
|
const USAGE = `Usage: jsbot [options]
|
|
26
27
|
|
|
27
28
|
Options:
|
|
28
29
|
--config <path> Config file path (env: BOT_CONFIG)
|
|
29
30
|
--data-dir <path> Data directory (env: BOT_DATA_DIR)
|
|
30
31
|
--log-level <level> debug, info, warn or error (env: BOT_LOG_LEVEL)
|
|
31
|
-
--plugin-dir <path> External plugin directory, repeatable (env: BOT_PLUGIN_DIR)
|
|
32
32
|
--help Show this help
|
|
33
33
|
|
|
34
34
|
Config precedence: CLI > environment > ./bot.config.json.
|
|
35
35
|
Data precedence: CLI > environment > ./data.
|
|
36
|
+
Local plugins: ./${LOCAL_PLUGIN_DIR}/<id>/dist.
|
|
36
37
|
`
|
|
37
38
|
|
|
38
39
|
function pick(value: unknown): string | undefined {
|
|
@@ -41,22 +42,6 @@ function pick(value: unknown): string | undefined {
|
|
|
41
42
|
: undefined
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
function pickDirs(value: unknown): string[] {
|
|
45
|
-
if (Array.isArray(value)) {
|
|
46
|
-
return value
|
|
47
|
-
.filter((item): item is string => typeof item === 'string')
|
|
48
|
-
.map((item) => item.trim())
|
|
49
|
-
.filter((item) => item.length > 0)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (typeof value !== 'string') return []
|
|
53
|
-
|
|
54
|
-
return value
|
|
55
|
-
.split(',')
|
|
56
|
-
.map((item) => item.trim())
|
|
57
|
-
.filter((item) => item.length > 0)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
45
|
interface PluginSource {
|
|
61
46
|
label: string
|
|
62
47
|
plugins: AnyPluginDefinition[]
|
|
@@ -88,31 +73,16 @@ function mergePlugins(
|
|
|
88
73
|
return plugins
|
|
89
74
|
}
|
|
90
75
|
|
|
91
|
-
async function loadExternalPlugins(
|
|
92
|
-
directories: string[],
|
|
93
|
-
logger: Logger
|
|
94
|
-
): Promise<AnyPluginDefinition[]> {
|
|
95
|
-
const plugins: AnyPluginDefinition[] = []
|
|
96
|
-
|
|
97
|
-
for (const directory of directories) {
|
|
98
|
-
logger.info('loading external plugins', directory)
|
|
99
|
-
plugins.push(...(await loadPluginsFromDirectory(directory, logger)))
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return plugins
|
|
103
|
-
}
|
|
104
|
-
|
|
105
76
|
interface RuntimeSettings {
|
|
106
77
|
logger: Logger
|
|
107
78
|
configPath: string
|
|
108
79
|
dataDir: string
|
|
109
|
-
pluginDirs: string[]
|
|
110
80
|
}
|
|
111
81
|
|
|
112
82
|
interface RuntimeSpec {
|
|
113
83
|
config: AppConfig
|
|
114
84
|
dataDir: string
|
|
115
|
-
|
|
85
|
+
pluginDir: string
|
|
116
86
|
plugins: AnyPluginDefinition[]
|
|
117
87
|
}
|
|
118
88
|
|
|
@@ -129,24 +99,20 @@ async function loadRuntimeSpec(
|
|
|
129
99
|
logger.info('loading config', settings.configPath)
|
|
130
100
|
|
|
131
101
|
const config = await loadConfig(settings.configPath)
|
|
132
|
-
const
|
|
133
|
-
settings.pluginDirs,
|
|
134
|
-
logger
|
|
135
|
-
)
|
|
136
|
-
const installDir = resolveInstallDir(config.installer, settings.dataDir)
|
|
137
|
-
const installed = await loadInstalledPlugins(installDir, logger)
|
|
102
|
+
const pluginDir = resolve(LOCAL_PLUGIN_DIR)
|
|
138
103
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
104
|
+
logger.info('loading plugins from', pluginDir)
|
|
105
|
+
|
|
106
|
+
const localPlugins = await loadLocalPlugins(pluginDir, logger)
|
|
107
|
+
|
|
108
|
+
if (localPlugins.length > 0) {
|
|
109
|
+
logger.info(`loaded ${localPlugins.length} plugin(s) from ${pluginDir}`)
|
|
143
110
|
}
|
|
144
111
|
|
|
145
112
|
const plugins = mergePlugins(
|
|
146
113
|
[
|
|
147
114
|
{ label: 'built-in', plugins: builtinPlugins },
|
|
148
|
-
{ label: '
|
|
149
|
-
{ label: 'installed', plugins: installed.plugins },
|
|
115
|
+
{ label: 'plugins', plugins: localPlugins },
|
|
150
116
|
],
|
|
151
117
|
logger
|
|
152
118
|
)
|
|
@@ -154,7 +120,7 @@ async function loadRuntimeSpec(
|
|
|
154
120
|
return {
|
|
155
121
|
config,
|
|
156
122
|
dataDir: settings.dataDir,
|
|
157
|
-
|
|
123
|
+
pluginDir,
|
|
158
124
|
plugins,
|
|
159
125
|
}
|
|
160
126
|
}
|
|
@@ -166,7 +132,7 @@ function createRuntime(
|
|
|
166
132
|
): Runtime {
|
|
167
133
|
const { logger } = settings
|
|
168
134
|
const installer = new NpmPluginInstaller({
|
|
169
|
-
dir: spec.
|
|
135
|
+
dir: spec.pluginDir,
|
|
170
136
|
config: spec.config.installer,
|
|
171
137
|
logger: logger.child('installer'),
|
|
172
138
|
reservedIds: () =>
|
|
@@ -201,7 +167,6 @@ async function main(): Promise<void> {
|
|
|
201
167
|
config: { type: 'string' },
|
|
202
168
|
'data-dir': { type: 'string' },
|
|
203
169
|
'log-level': { type: 'string' },
|
|
204
|
-
'plugin-dir': { type: 'string', multiple: true },
|
|
205
170
|
help: { type: 'boolean', short: 'h' },
|
|
206
171
|
},
|
|
207
172
|
allowPositionals: false,
|
|
@@ -229,14 +194,10 @@ async function main(): Promise<void> {
|
|
|
229
194
|
env: process.env,
|
|
230
195
|
})
|
|
231
196
|
|
|
232
|
-
const cliPluginDirs = pickDirs(values['plugin-dir'])
|
|
233
|
-
const envPluginDirs = pickDirs(process.env.BOT_PLUGIN_DIR)
|
|
234
|
-
const pluginDirs = cliPluginDirs.length > 0 ? cliPluginDirs : envPluginDirs
|
|
235
197
|
const settings: RuntimeSettings = {
|
|
236
198
|
logger,
|
|
237
199
|
configPath,
|
|
238
200
|
dataDir,
|
|
239
|
-
pluginDirs,
|
|
240
201
|
}
|
|
241
202
|
|
|
242
203
|
let runtime: Runtime | null = null
|
|
@@ -48,7 +48,6 @@ export const rolesConfigSchema = z
|
|
|
48
48
|
export const installerConfigSchema = z
|
|
49
49
|
.object({
|
|
50
50
|
enabled: z.boolean().default(false),
|
|
51
|
-
dir: z.string().min(1).optional(),
|
|
52
51
|
registry: z.url().default('https://registry.npmjs.org/'),
|
|
53
52
|
allowedPackages: z.array(z.string().min(1)).default([]),
|
|
54
53
|
allowScripts: z.boolean().default(false),
|