@aalis/plugin-webui-server 0.14.1 → 0.15.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.
Files changed (95) hide show
  1. package/README.md +5 -1
  2. package/dist/admin-channel.d.ts +10 -0
  3. package/dist/admin-channel.d.ts.map +1 -0
  4. package/dist/admin-channel.js +57 -0
  5. package/dist/admin-channel.js.map +1 -0
  6. package/dist/attention.d.ts +36 -0
  7. package/dist/attention.d.ts.map +1 -0
  8. package/dist/attention.js +73 -0
  9. package/dist/attention.js.map +1 -0
  10. package/dist/auth.d.ts +22 -3
  11. package/dist/auth.d.ts.map +1 -1
  12. package/dist/auth.js +192 -68
  13. package/dist/auth.js.map +1 -1
  14. package/dist/client-discovery.d.ts +7 -3
  15. package/dist/client-discovery.d.ts.map +1 -1
  16. package/dist/client-discovery.js +9 -3
  17. package/dist/client-discovery.js.map +1 -1
  18. package/dist/client-switch-page.d.ts +2 -0
  19. package/dist/client-switch-page.d.ts.map +1 -1
  20. package/dist/client-switch-page.js +37 -36
  21. package/dist/client-switch-page.js.map +1 -1
  22. package/dist/gate.d.ts +3 -3
  23. package/dist/gate.js +3 -3
  24. package/dist/index.d.ts +21 -26
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +561 -1015
  27. package/dist/index.js.map +1 -1
  28. package/dist/layout.d.ts +18 -0
  29. package/dist/layout.d.ts.map +1 -0
  30. package/dist/layout.js +198 -0
  31. package/dist/layout.js.map +1 -0
  32. package/dist/logs.d.ts +10 -0
  33. package/dist/logs.d.ts.map +1 -0
  34. package/dist/logs.js +50 -0
  35. package/dist/logs.js.map +1 -0
  36. package/dist/page-check.d.ts +56 -0
  37. package/dist/page-check.d.ts.map +1 -0
  38. package/dist/page-check.js +369 -0
  39. package/dist/page-check.js.map +1 -0
  40. package/dist/page-files.d.ts +13 -0
  41. package/dist/page-files.d.ts.map +1 -0
  42. package/dist/page-files.js +39 -0
  43. package/dist/page-files.js.map +1 -0
  44. package/dist/pages.d.ts +60 -0
  45. package/dist/pages.d.ts.map +1 -0
  46. package/dist/pages.js +470 -0
  47. package/dist/pages.js.map +1 -0
  48. package/dist/route.d.ts +11 -0
  49. package/dist/route.d.ts.map +1 -0
  50. package/dist/route.js +13 -0
  51. package/dist/route.js.map +1 -0
  52. package/dist/routes/files.d.ts +8 -9
  53. package/dist/routes/files.d.ts.map +1 -1
  54. package/dist/routes/files.js +32 -119
  55. package/dist/routes/files.js.map +1 -1
  56. package/dist/routes/frames.d.ts +4 -0
  57. package/dist/routes/frames.d.ts.map +1 -0
  58. package/dist/routes/frames.js +103 -0
  59. package/dist/routes/frames.js.map +1 -0
  60. package/dist/routes/marketplace.d.ts +46 -129
  61. package/dist/routes/marketplace.d.ts.map +1 -1
  62. package/dist/routes/marketplace.js +138 -86
  63. package/dist/routes/marketplace.js.map +1 -1
  64. package/dist/routes/media.d.ts +7 -0
  65. package/dist/routes/media.d.ts.map +1 -0
  66. package/dist/routes/media.js +63 -0
  67. package/dist/routes/media.js.map +1 -0
  68. package/dist/routes/pages.d.ts +20 -0
  69. package/dist/routes/pages.d.ts.map +1 -0
  70. package/dist/routes/pages.js +86 -0
  71. package/dist/routes/pages.js.map +1 -0
  72. package/dist/routes/pipe-to-response.d.ts +9 -0
  73. package/dist/routes/pipe-to-response.d.ts.map +1 -0
  74. package/dist/routes/pipe-to-response.js +16 -0
  75. package/dist/routes/pipe-to-response.js.map +1 -0
  76. package/dist/routes/plugins.d.ts +22 -24
  77. package/dist/routes/plugins.d.ts.map +1 -1
  78. package/dist/routes/plugins.js +285 -275
  79. package/dist/routes/plugins.js.map +1 -1
  80. package/dist/routes/proxy.d.ts.map +1 -1
  81. package/dist/routes/proxy.js +2 -1
  82. package/dist/routes/proxy.js.map +1 -1
  83. package/dist/session-transport.d.ts +6 -0
  84. package/dist/session-transport.d.ts.map +1 -0
  85. package/dist/session-transport.js +32 -0
  86. package/dist/session-transport.js.map +1 -0
  87. package/package.json +34 -31
  88. package/dist/protocol.d.ts +0 -113
  89. package/dist/protocol.d.ts.map +0 -1
  90. package/dist/protocol.js +0 -59
  91. package/dist/protocol.js.map +0 -1
  92. package/dist/routes/uploaded-files.d.ts +0 -20
  93. package/dist/routes/uploaded-files.d.ts.map +0 -1
  94. package/dist/routes/uploaded-files.js +0 -161
  95. package/dist/routes/uploaded-files.js.map +0 -1
@@ -0,0 +1,63 @@
1
+ // ============================================================
2
+ // routes/media.ts — 聊天里的图片、视频与音频按存储地址取
3
+ //
4
+ // GET /api/media?uri=<storage URI>(如 data:/images/x.png)。聊天消息与会话历史里的附件多半是存储
5
+ // 地址,浏览器拿它当不了图片地址;客户端把它换成这条路由。只有 owner(cookie 闸)能取;按文件头判定类型,
6
+ // 只放行浏览器能直接显示或播放的图片、视频与音频,其余(含 HTML、SVG、JSON 账本)一律 415,不在 WebUI 源里内联。
7
+ // 响应带 nosniff 与 sandbox CSP,类型取判定结果、不取扩展名。文件不存在回 404,其余读不出的(含存储拒绝的越界路径)
8
+ // 回 500 并记告警。
9
+ // ============================================================
10
+ import { isStorageNotFound } from '@aalis/api-storage';
11
+ import { detectMediaFormat, MEDIA_HEAD_BYTES } from '@aalis/util-media-signature';
12
+ import { route } from '../route.js';
13
+ import { pipeToResponse } from './pipe-to-response.js';
14
+ /** 判定出的格式 → 响应类型;不在表里的不提供 */
15
+ const MEDIA_TYPES = {
16
+ PNG: 'image/png',
17
+ JPEG: 'image/jpeg',
18
+ GIF: 'image/gif',
19
+ WebP: 'image/webp',
20
+ MP4: 'video/mp4',
21
+ WebM: 'video/webm',
22
+ MP3: 'audio/mpeg',
23
+ WAV: 'audio/wav',
24
+ OGG: 'audio/ogg',
25
+ M4A: 'audio/mp4',
26
+ };
27
+ /** 存储地址:<根名>:/<路径> */
28
+ const STORAGE_URI = /^[A-Za-z][\w-]*:\/[^\0]*$/;
29
+ /** 传入的是 storage 的业务句柄:区间读恒有定义,提供者不支持时调用即抛错 */
30
+ export function registerMediaRoutes(expressApp, storage, logger, gate) {
31
+ route(expressApp, 'GET /api/media', gate(), async (req, res) => {
32
+ const uri = String(req.query.uri ?? '');
33
+ if (!STORAGE_URI.test(uri) || /^https?:/i.test(uri)) {
34
+ res.status(400).json({ error: '不是存储地址' });
35
+ return;
36
+ }
37
+ try {
38
+ const head = await storage.readFileRange(uri, 0, MEDIA_HEAD_BYTES);
39
+ const type = MEDIA_TYPES[detectMediaFormat(new Uint8Array(head))?.format ?? ''];
40
+ if (!type) {
41
+ res.status(415).json({ error: '只提供图片、视频与音频' });
42
+ return;
43
+ }
44
+ const { stream, stat } = await storage.createReadStream(uri);
45
+ res.setHeader('Content-Type', type);
46
+ res.setHeader('Content-Length', stat.size);
47
+ res.setHeader('Content-Disposition', 'inline');
48
+ res.setHeader('X-Content-Type-Options', 'nosniff');
49
+ res.setHeader('Content-Security-Policy', "sandbox; default-src 'none'");
50
+ res.setHeader('Cache-Control', 'private, max-age=3600');
51
+ pipeToResponse(stream, res, logger, `媒体文件 ${uri}`);
52
+ }
53
+ catch (err) {
54
+ if (isStorageNotFound(err)) {
55
+ res.status(404).json({ error: '文件不存在' });
56
+ return;
57
+ }
58
+ logger.warn(`读取媒体文件 ${uri} 失败:`, err);
59
+ res.status(500).json({ error: '读取文件失败' });
60
+ }
61
+ });
62
+ }
63
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/routes/media.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { UserIdentity } from '@aalis/api-authority';
2
+ import type { WebuiAdminEvent } from '@aalis/api-webui';
3
+ import type { Logger } from '@aalis/core';
4
+ import type express from 'express';
5
+ import type { RouteGate } from '../gate.js';
6
+ import { type LayoutStore } from '../layout.js';
7
+ import type { PageRegistry } from '../pages.js';
8
+ export declare function registerPageRoutes(expressApp: express.Express, deps: {
9
+ registry: PageRegistry;
10
+ layout: LayoutStore;
11
+ /** 存储服务在不在:布局记录读写要它 */
12
+ storageReady(): boolean;
13
+ send(event: WebuiAdminEvent): void;
14
+ logger: Logger;
15
+ }, identify: (req: {
16
+ headers: {
17
+ cookie?: string;
18
+ };
19
+ }) => UserIdentity | undefined, gate: RouteGate): void;
20
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/routes/pages.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAc,KAAK,WAAW,EAAwB,MAAM,cAAc,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAS,MAAM,aAAa,CAAC;AAGvD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,CAAC,OAAO,EAC3B,IAAI,EAAE;IACJ,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,uBAAuB;IACvB,YAAY,IAAI,OAAO,CAAC;IACxB,IAAI,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB,EACD,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAK,YAAY,GAAG,SAAS,EAC7E,IAAI,EAAE,SAAS,GACd,IAAI,CA+EN"}
@@ -0,0 +1,86 @@
1
+ // ============================================================
2
+ // routes/pages.ts — 声明式页面、会话面板与布局记录的 REST 路由
3
+ //
4
+ // 路径与返回形状见 api-webui 的 page.ts。页面的数据源与动作要把调用者交给插件,这两条路由自己解析身份(与 gate 同一判定);
5
+ // 其余用 gate。闸门与校验在 pages.ts,这里只把请求交过去、把结果写回。布局记录存在插件数据里,存储服务不在时回 503。
6
+ // ============================================================
7
+ import { LayoutFull, parseLayoutOperation } from '../layout.js';
8
+ import { route } from '../route.js';
9
+ export function registerPageRoutes(expressApp, deps, identify, gate) {
10
+ const { registry, layout } = deps;
11
+ const reply = (res, { status, body }) => res.status(status).json(body);
12
+ const forbidden = (res) => res.status(403).json({ error: '需要 owner 权限' });
13
+ route(expressApp, 'GET /api/pages', gate(), (_req, res) => {
14
+ res.json({ pages: registry.views() });
15
+ });
16
+ route(expressApp, 'GET /api/session-pages', gate(), async (req, res) => {
17
+ reply(res, await registry.panelPages(req.query.sessionId));
18
+ });
19
+ route(expressApp, 'GET /api/pages/:instance/:pageKey/data/:componentId', async (req, res) => {
20
+ const caller = identify(req);
21
+ if (!caller)
22
+ return forbidden(res);
23
+ const { instance, pageKey, componentId } = req.params;
24
+ reply(res, await registry.data(instance, pageKey, componentId, req.query, caller));
25
+ });
26
+ route(expressApp, 'POST /api/pages/:instance/:pageKey/actions/:action', async (req, res) => {
27
+ const caller = identify(req);
28
+ if (!caller)
29
+ return forbidden(res);
30
+ const { instance, pageKey, action } = req.params;
31
+ reply(res, await registry.act(instance, pageKey, action, req.body, caller, req.socket.remoteAddress ?? '未知'));
32
+ });
33
+ const storageMissing = (res) => res.status(503).json({ error: '存储服务不在,布局记录读写不了' });
34
+ route(expressApp, 'GET /api/layout', gate(), async (_req, res) => {
35
+ if (!deps.storageReady())
36
+ return storageMissing(res);
37
+ try {
38
+ res.json(await layout.read());
39
+ }
40
+ catch (err) {
41
+ deps.logger.warn('读取布局记录失败:', err);
42
+ res.status(500).json({ error: '读取布局记录失败' });
43
+ }
44
+ });
45
+ route(expressApp, 'POST /api/layout', gate(), async (req, res) => {
46
+ if (!deps.storageReady())
47
+ return storageMissing(res);
48
+ const operation = parseLayoutOperation(req.body);
49
+ if (!operation) {
50
+ return res.status(400).json({
51
+ error: '操作要是 { op, page } 或 { op, tab }:pin、unpin、move-up、move-down 带 page({ instance, pageKey }),' +
52
+ 'open-tab、close-tab、tab-up、tab-down 带 tab({ host } 或 { instance, pageKey }),' +
53
+ 'remove-tab 只带插件标签 tab({ instance, pageKey }),宿主标签只能关闭、不能移出',
54
+ });
55
+ }
56
+ // 固定与打开插件标签只收此刻登记着、位置对的页面;其余只动记录,所属插件停用时也能改
57
+ const wanted = operation.op === 'pin'
58
+ ? { page: operation.page, placement: 'plugin', refusal: '只有位置为插件详情的页面能固定' }
59
+ : operation.op === 'open-tab' && !('host' in operation.tab)
60
+ ? { page: operation.tab, placement: 'session', refusal: '只有会话面板的页面能在面板里打开' }
61
+ : undefined;
62
+ if (wanted) {
63
+ const placement = registry.placementOf(wanted.page);
64
+ if (!placement) {
65
+ return res.status(404).json({ error: `页面 ${wanted.page.pageKey}(${wanted.page.instance})不存在` });
66
+ }
67
+ if (placement !== wanted.placement)
68
+ return res.status(400).json({ error: wanted.refusal });
69
+ }
70
+ try {
71
+ const { records, changed } = await layout.apply(operation);
72
+ if (changed)
73
+ deps.send({ type: 'layout.changed' });
74
+ res.json(records);
75
+ }
76
+ catch (err) {
77
+ if (err instanceof LayoutFull) {
78
+ res.status(409).json({ error: err.message });
79
+ return;
80
+ }
81
+ deps.logger.warn('保存布局记录失败:', err);
82
+ res.status(500).json({ error: '保存布局记录失败,记录没有改动' });
83
+ }
84
+ });
85
+ }
86
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/routes/pages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import type { Logger } from '@aalis/core';
3
+ /**
4
+ * 把读流写进响应。读流出错(打开前文件被删、读到一半出错)或客户端中途断开时,pipeline 把两端都销毁、断开这次
5
+ * 响应,响应头可能已经发出,不再改回错误码。裸 pipe 不处理源流的错误,没有监听的 'error' 会让进程退出;客户端断开
6
+ * 时也不销毁源流,fd 不回收。中途断开是常事,只记读流出错;label 是日志里这次发送的说明
7
+ */
8
+ export declare function pipeToResponse(stream: Readable, res: Writable, logger: Logger, label: string): void;
9
+ //# sourceMappingURL=pipe-to-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipe-to-response.d.ts","sourceRoot":"","sources":["../../src/routes/pipe-to-response.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAInG"}
@@ -0,0 +1,16 @@
1
+ // ============================================================
2
+ // routes/pipe-to-response.ts — 把存储读流写进 HTTP 响应
3
+ // ============================================================
4
+ import { pipeline } from 'node:stream/promises';
5
+ /**
6
+ * 把读流写进响应。读流出错(打开前文件被删、读到一半出错)或客户端中途断开时,pipeline 把两端都销毁、断开这次
7
+ * 响应,响应头可能已经发出,不再改回错误码。裸 pipe 不处理源流的错误,没有监听的 'error' 会让进程退出;客户端断开
8
+ * 时也不销毁源流,fd 不回收。中途断开是常事,只记读流出错;label 是日志里这次发送的说明
9
+ */
10
+ export function pipeToResponse(stream, res, logger, label) {
11
+ pipeline(stream, res).catch((err) => {
12
+ if (err.code !== 'ERR_STREAM_PREMATURE_CLOSE')
13
+ logger.warn(`${label} 读取出错: ${err.message}`);
14
+ });
15
+ }
16
+ //# sourceMappingURL=pipe-to-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipe-to-response.js","sourceRoot":"","sources":["../../src/routes/pipe-to-response.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+CAA+C;AAC/C,+DAA+D;AAG/D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAgB,EAAE,GAAa,EAAE,MAAc,EAAE,KAAa;IAC3F,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAA0B,EAAE,EAAE;QACzD,IAAI,GAAG,CAAC,IAAI,KAAK,4BAA4B;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,19 +1,18 @@
1
- import type { UserIdentity } from '@aalis/api-authority';
2
1
  import type { CommandService } from '@aalis/api-commands';
3
2
  import { type HostConfig } from '@aalis/api-host-config';
4
3
  import type { PluginSourceService } from '@aalis/api-plugin-source';
5
4
  import type { ToolService } from '@aalis/api-tools';
6
- import type { WebUIService, WebuiActionHandler } from '@aalis/api-webui';
7
- import type { AppService, Logger, PluginManagerService, ServiceRef } from '@aalis/core';
5
+ import type { ErrorResponse } from '@aalis/api-webui';
6
+ import type { AppService, Logger, PluginManagerService, ServiceSelection, Services } from '@aalis/core';
8
7
  import type express from 'express';
9
8
  import type { RouteGate } from '../gate.js';
10
9
  /**
11
- * 落盘被拒后运行态怎样与文件对齐:插件配置与新建的实例随配置文件热重载(文件里没有配置段的后缀实例会被卸载);
12
- * 启停与服务偏好只在重启时按文件登记
10
+ * 落盘被拒后运行态怎样与文件对齐:插件配置、新建的实例与服务偏好随配置文件热重载(文件里没有配置段的后缀实例会被卸载);
11
+ * 启停只在重启时按文件登记。被拒的可能是配置文件,也可能是密钥文件,拒写原因里点了名,这里不指具体哪个文件
13
12
  */
14
13
  declare const RECONCILE: {
15
- readonly reload: "修好配置文件后会按文件内容重载";
16
- readonly restart: "修好配置文件后,重启时以文件内容为准";
14
+ readonly reload: "修好该文件后会按文件内容重载";
15
+ readonly restart: "修好该文件后,重启时以文件内容为准";
17
16
  };
18
17
  /**
19
18
  * 管理动作改完运行态之后落盘;失败时回 `applied: true` 并返回 false:改动已在运行态生效、只是没写进文件,
@@ -22,30 +21,29 @@ declare const RECONCILE: {
22
21
  * `outcome` 是动作之后插件没有落在预期状态的说明(如激活失败转 error、仍在激活、保持禁用):落盘也失败时放在回执开头,
23
22
  * 两件事一并说明。
24
23
  */
25
- export declare function saveAfterApply(doc: Pick<HostConfig, 'save'>, res: express.Response, reconcile: keyof typeof RECONCILE, outcome?: string): Promise<boolean>;
24
+ export declare function saveAfterApply(doc: Pick<HostConfig, 'save'>, res: express.Response<ErrorResponse>, reconcile: keyof typeof RECONCILE, outcome?: string): Promise<boolean>;
26
25
  /** 插件管理 + 全局配置路由用到的能力 */
27
26
  interface PluginRoutesCaps {
28
- app: ServiceRef<AppService>;
29
- plugins: ServiceRef<PluginManagerService>;
27
+ app: Pick<ServiceSelection<AppService>, 'present' | 'use'>;
28
+ plugins: Pick<ServiceSelection<PluginManagerService>, 'present' | 'use'>;
30
29
  /** 宿主的插件来源;打包宿主不提供,扫描路由随之报不可用 */
31
- source: Pick<ServiceRef<PluginSourceService>, 'current'>;
30
+ source: Pick<ServiceSelection<PluginSourceService>, 'present' | 'use'>;
32
31
  /** 配置文档的读写与落盘(宿主提供;缺席时读写文档的路由返回 503) */
33
- hostConfig: Pick<ServiceRef<HostConfig>, 'current'>;
34
- tools: Pick<ServiceRef<ToolService>, 'current'>;
35
- commands: Pick<ServiceRef<CommandService>, 'current'>;
36
- /**
37
- * 取当前 webui-server 提供者(页面登记表的读取面)。本插件自己提供这个服务,
38
- * 写死引用就绕过了解析——第三方若以更高优先级接管 webui-server,页面列表该跟着走。
39
- */
40
- webui(): WebUIService | undefined;
32
+ hostConfig: Pick<ServiceSelection<HostConfig>, 'present' | 'use'>;
33
+ tools: Pick<ServiceSelection<ToolService>, 'present' | 'use'>;
34
+ commands: Pick<ServiceSelection<CommandService>, 'present' | 'use'>;
35
+ /** 当前有提供者的服务名:列表与回执据它判缺与在等;列表另据它读各插件的本插件偏好与此刻的选择 */
36
+ services: Pick<Services, 'names' | 'preferred' | 'inspect'>;
37
+ /** 提供这个界面的插件实例:经这些路由停用不了它,列表给它标 self */
38
+ self: string;
39
+ /** 家目录(宿主路径的 home):宿主配置一并给出;宿主不提供路径时没有 */
40
+ home?: string;
41
+ /** 本地已装包的版本,每取一次列表现查一遍(装卸与更新会改它) */
42
+ versions(): (name: string) => string | undefined;
41
43
  /** 裁剪 schema 外字段时点名 warn,保存全局配置后重启失败时记 error;测试可不传 */
42
44
  logger?: Logger;
43
45
  }
44
46
  /** 注册插件管理 + 全局配置相关 REST 路由 */
45
- export declare function registerPluginRoutes(expressApp: express.Express, caps: PluginRoutesCaps, identify: (req: {
46
- headers: {
47
- cookie?: string;
48
- };
49
- }) => UserIdentity | undefined, gate: RouteGate, getAction: (plugin: string, method: string) => WebuiActionHandler | undefined): void;
47
+ export declare function registerPluginRoutes(expressApp: express.Express, caps: PluginRoutesCaps, gate: RouteGate): void;
50
48
  export {};
51
49
  //# sourceMappingURL=plugins.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/routes/plugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,KAAK,UAAU,EAAuB,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAa,MAAM,kBAAkB,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAe,UAAU,EAAE,MAAM,aAAa,CAAC;AAWrG,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C;;;GAGG;AACH,QAAA,MAAM,SAAS;;;CAGL,CAAC;AAEX;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAC7B,GAAG,EAAE,OAAO,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,OAAO,SAAS,EACjC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAmBlB;AA8BD,yBAAyB;AACzB,UAAU,gBAAgB;IACxB,GAAG,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,OAAO,EAAE,UAAU,CAAC,oBAAoB,CAAC,CAAC;IAC1C,iCAAiC;IACjC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC,CAAC;IACzD,wCAAwC;IACxC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC;IACpD,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC,CAAC;IAChD,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC;IACtD;;;OAGG;IACH,KAAK,IAAI,YAAY,GAAG,SAAS,CAAC;IAClC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,8BAA8B;AAC9B,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,OAAO,CAAC,OAAO,EAC3B,IAAI,EAAE,gBAAgB,EACtB,QAAQ,EAAE,CAAC,GAAG,EAAE;IAAE,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAK,YAAY,GAAG,SAAS,EAC7E,IAAI,EAAE,SAAS,EACf,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,GAC5E,IAAI,CA8nBN"}
1
+ {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/routes/plugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAgC,KAAK,UAAU,EAAuB,MAAM,wBAAwB,CAAC;AAC5G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAe,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAarH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAO5C;;;GAGG;AACH,QAAA,MAAM,SAAS;;;CAGL,CAAC;AAEX;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAC7B,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EACpC,SAAS,EAAE,MAAM,OAAO,SAAS,EACjC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAQlB;AAgFD,yBAAyB;AACzB,UAAU,gBAAgB;IACxB,GAAG,EAAE,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IAC3D,OAAO,EAAE,IAAI,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IACzE,iCAAiC;IACjC,MAAM,EAAE,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IACvE,wCAAwC;IACxC,UAAU,EAAE,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IAClE,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IAC9D,QAAQ,EAAE,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IACpE,oDAAoD;IACpD,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAAC,CAAC;IAC5D,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,IAAI,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACjD,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,8BAA8B;AAC9B,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAmnB/G"}