@aalis/plugin-webui-server 0.12.0 → 0.13.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/dist/auth.d.ts +3 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +6 -3
- package/dist/auth.js.map +1 -1
- package/dist/client-discovery.d.ts +2 -1
- package/dist/client-discovery.d.ts.map +1 -1
- package/dist/client-discovery.js +4 -3
- package/dist/client-discovery.js.map +1 -1
- package/dist/index.d.ts +15 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -86
- package/dist/index.js.map +1 -1
- package/dist/routes/marketplace.d.ts +27 -24
- package/dist/routes/marketplace.d.ts.map +1 -1
- package/dist/routes/marketplace.js +114 -82
- package/dist/routes/marketplace.js.map +1 -1
- package/dist/routes/plugins.d.ts +22 -4
- package/dist/routes/plugins.d.ts.map +1 -1
- package/dist/routes/plugins.js +225 -85
- package/dist/routes/plugins.js.map +1 -1
- package/dist/routes/uploaded-files.d.ts.map +1 -1
- package/dist/routes/uploaded-files.js +19 -26
- package/dist/routes/uploaded-files.js.map +1 -1
- package/package.json +28 -25
package/dist/auth.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http';
|
|
2
2
|
import type { UserIdentity } from '@aalis/api-authority';
|
|
3
3
|
import type { ProcessService } from '@aalis/api-process';
|
|
4
|
-
import type { Logger } from '@aalis/core';
|
|
5
4
|
import type { RequestHandler } from 'express';
|
|
6
5
|
export interface AuthSystem {
|
|
7
6
|
/** Express 中间件:处理 ?token= 重定向、登录端点、未认证拦截、登录页 */
|
|
@@ -14,11 +13,12 @@ export interface AuthSystem {
|
|
|
14
13
|
/**
|
|
15
14
|
* 创建单 token 认证系统(单 owner)。
|
|
16
15
|
*
|
|
17
|
-
* - token
|
|
16
|
+
* - token 由调用方经 getToken 提供,每次校验现取:persist 模式下 storage 晚上线时,调用方会把
|
|
17
|
+
* 启动时的临时 token 换成读回的持久化 token(见 index.ts 的 token 解析)
|
|
18
18
|
* - HttpOnly + SameSite=Strict cookie;`?token=` 一键登录;同源 POST /api/auth/login 提交
|
|
19
19
|
* - 身份恒为 `webui:console`(owner 级,单人模式语义)
|
|
20
20
|
*/
|
|
21
|
-
export declare function createAuthSystem(
|
|
21
|
+
export declare function createAuthSystem(getToken: () => string): AuthSystem;
|
|
22
22
|
/**
|
|
23
23
|
* 跨平台打开默认浏览器到指定 URL(无外部依赖)
|
|
24
24
|
* 失败静默;此为便利功能,不影响服务启动。
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA0E9C,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,UAAU,EAAE,cAAc,CAAC;IAC3B,wCAAwC;IACxC,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;IAC9C,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,SAAS,CAAC;CAC3E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,MAAM,GAAG,UAAU,CAiFnE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAwBnE"}
|
package/dist/auth.js
CHANGED
|
@@ -70,15 +70,16 @@ function escapeHtml(s) {
|
|
|
70
70
|
/**
|
|
71
71
|
* 创建单 token 认证系统(单 owner)。
|
|
72
72
|
*
|
|
73
|
-
* - token
|
|
73
|
+
* - token 由调用方经 getToken 提供,每次校验现取:persist 模式下 storage 晚上线时,调用方会把
|
|
74
|
+
* 启动时的临时 token 换成读回的持久化 token(见 index.ts 的 token 解析)
|
|
74
75
|
* - HttpOnly + SameSite=Strict cookie;`?token=` 一键登录;同源 POST /api/auth/login 提交
|
|
75
76
|
* - 身份恒为 `webui:console`(owner 级,单人模式语义)
|
|
76
77
|
*/
|
|
77
|
-
export function createAuthSystem(
|
|
78
|
+
export function createAuthSystem(getToken) {
|
|
78
79
|
const cookieMaxAge = 30 * 24 * 3600; // 30d;进程重启 token 轮换后无效
|
|
79
80
|
function tokenAuthed(req) {
|
|
80
81
|
const cookieToken = parseCookieValue(req.headers.cookie, TOKEN_COOKIE);
|
|
81
|
-
return !!cookieToken && cookieToken ===
|
|
82
|
+
return !!cookieToken && cookieToken === getToken();
|
|
82
83
|
}
|
|
83
84
|
function identify(req) {
|
|
84
85
|
return tokenAuthed(req) ? { platform: 'webui', userId: 'console' } : undefined;
|
|
@@ -88,6 +89,7 @@ export function createAuthSystem(token, _logger) {
|
|
|
88
89
|
const qToken = typeof req.query?.token === 'string' ? req.query.token : undefined;
|
|
89
90
|
// 1. ?token= 命中 → 设置 cookie → 302 到不带 query 的同路径
|
|
90
91
|
if (req.method === 'GET' && qToken) {
|
|
92
|
+
const token = getToken();
|
|
91
93
|
if (qToken === token) {
|
|
92
94
|
res.setHeader('Set-Cookie', buildSetCookie(TOKEN_COOKIE, token, cookieMaxAge));
|
|
93
95
|
res.redirect(302, url);
|
|
@@ -99,6 +101,7 @@ export function createAuthSystem(token, _logger) {
|
|
|
99
101
|
// 2. 登录端点:{token}
|
|
100
102
|
if (url === '/api/auth/login' && req.method === 'POST') {
|
|
101
103
|
const body = req.body ?? {};
|
|
104
|
+
const token = getToken();
|
|
102
105
|
if (typeof body.token === 'string' && body.token === token) {
|
|
103
106
|
res.setHeader('Set-Cookie', buildSetCookie(TOKEN_COOKIE, token, cookieMaxAge));
|
|
104
107
|
res.json({ ok: true, identity: { platform: 'webui', userId: 'console' } });
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,8 @@ export interface DiscoveryEnv {
|
|
|
17
17
|
/**
|
|
18
18
|
* 发现所有前端包:在 scanDirs 各目录下逐子目录、以及 depIds(项目根 deps)里,收录 package.json
|
|
19
19
|
* 标了 `aalis.client:true` 且其 `dist/index.html` 存在的包。按包名去重,再按 id 排序(确定性输出)。
|
|
20
|
-
*
|
|
20
|
+
* 活跃前端由 webui-client 服务解析决定(偏好 > 优先级 > 注册顺序),本函数只负责发现、不做偏好判断、
|
|
21
|
+
* 不认任何具体名字。
|
|
21
22
|
*/
|
|
22
23
|
export declare function discoverClients(scanDirs: string[], depIds: string[], env: DiscoveryEnv): ClientCandidate[];
|
|
23
24
|
/** 本地扫到的一个包:它自身的元数据 + 它依赖了谁。 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-discovery.d.ts","sourceRoot":"","sources":["../src/client-discovery.ts"],"names":[],"mappings":"AAMA,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,qBAAqB;IACrB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACrC,oCAAoC;IACpC,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,mDAAmD;IACnD,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACpD;AASD
|
|
1
|
+
{"version":3,"file":"client-discovery.d.ts","sourceRoot":"","sources":["../src/client-discovery.ts"],"names":[],"mappings":"AAMA,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,qBAAqB;IACrB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACrC,oCAAoC;IACpC,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,mDAAmD;IACnD,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACpD;AASD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,YAAY,GAAG,eAAe,EAAE,CA2B1G;AAED,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAAE,EAClB,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,aAAa,GAAG,UAAU,GAAG,MAAM,CAAC,GAC1E,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAiC7B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAGjH"}
|
package/dist/client-discovery.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// 全动态前端发现(纯逻辑,注入 fs/path 便于单测)。
|
|
2
2
|
//
|
|
3
|
-
// 按 `aalis.client:true` 标记 + 存在 `dist/index.html`
|
|
4
|
-
//
|
|
3
|
+
// 按 `aalis.client:true` 标记 + 存在 `dist/index.html` 收录前端候选——**不硬编码任何前端包名**
|
|
4
|
+
// (忒修斯之船:任意第三方前端,带标记+dist 即被发现)。
|
|
5
5
|
// 覆盖三种拓扑:monorepo(扫 packages 同级目录)/ 独立项目(扫 node_modules/@aalis 作用域 + 根 deps)。
|
|
6
6
|
/**
|
|
7
7
|
* 发现所有前端包:在 scanDirs 各目录下逐子目录、以及 depIds(项目根 deps)里,收录 package.json
|
|
8
8
|
* 标了 `aalis.client:true` 且其 `dist/index.html` 存在的包。按包名去重,再按 id 排序(确定性输出)。
|
|
9
|
-
*
|
|
9
|
+
* 活跃前端由 webui-client 服务解析决定(偏好 > 优先级 > 注册顺序),本函数只负责发现、不做偏好判断、
|
|
10
|
+
* 不认任何具体名字。
|
|
10
11
|
*/
|
|
11
12
|
export function discoverClients(scanDirs, depIds, env) {
|
|
12
13
|
const out = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-discovery.js","sourceRoot":"","sources":["../src/client-discovery.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"client-discovery.js","sourceRoot":"","sources":["../src/client-discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type PlatformAdapter } from '@aalis/api-platform';
|
|
2
2
|
import type { StorageService } from '@aalis/api-storage';
|
|
3
|
-
export { type WSIncoming, WSIncomingSchema } from './protocol.js';
|
|
4
3
|
/**
|
|
5
4
|
* 会话归属的平台名:取 sessionId 首个 `:` 前的前缀,命中**已注册平台名**才采信,否则 `'webui'`。
|
|
6
5
|
*
|
|
@@ -19,20 +18,21 @@ declare const _default: import("@aalis/core").PluginDefinition<{
|
|
|
19
18
|
config: import("@aalis/core").ServiceDescriptor<(identity: symbol) => Readonly<Record<string, unknown>>, Readonly<Record<string, unknown>>>;
|
|
20
19
|
provide: import("@aalis/core").ServiceDescriptor<(identity: symbol) => import("@aalis/core").Provide, import("@aalis/core").Provide>;
|
|
21
20
|
services: import("@aalis/core").ServiceDescriptor<(identity: symbol) => import("@aalis/core").Services, import("@aalis/core").Services>;
|
|
22
|
-
hostConfig: import("@aalis/core
|
|
23
|
-
app: import("@aalis/core
|
|
24
|
-
plugins: import("@aalis/core
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
21
|
+
hostConfig: import("@aalis/core").OptionalUse<import("@aalis/api-host-config").HostConfig, import("@aalis/core").ServiceRef<import("@aalis/api-host-config").HostConfig>>;
|
|
22
|
+
app: import("@aalis/core").OptionalUse<import("@aalis/core").AppService, import("@aalis/core").ServiceRef<import("@aalis/core").AppService>>;
|
|
23
|
+
plugins: import("@aalis/core").OptionalUse<import("@aalis/core").PluginManagerService, import("@aalis/core").ServiceRef<import("@aalis/core").PluginManagerService>>;
|
|
24
|
+
source: import("@aalis/core").OptionalUse<import("@aalis/api-plugin-source").PluginSourceService, import("@aalis/core").ServiceRef<import("@aalis/api-plugin-source").PluginSourceService>>;
|
|
25
|
+
packageManager: import("@aalis/core").OptionalUse<import("@aalis/api-package-manager").PackageManagerService, import("@aalis/core").ServiceRef<import("@aalis/api-package-manager").PackageManagerService>>;
|
|
26
|
+
storage: import("@aalis/core").OptionalUse<StorageService, import("@aalis/core").ServiceRef<StorageService>>;
|
|
27
|
+
authority: import("@aalis/core").OptionalUse<import("@aalis/api-authority").AuthorityService, import("@aalis/core").ServiceRef<import("@aalis/api-authority").AuthorityService>>;
|
|
28
|
+
commands: import("@aalis/core").OptionalUse<import("@aalis/api-commands").CommandService, import("@aalis/api-commands").BoundCommands>;
|
|
29
|
+
platform: import("@aalis/core").OptionalUse<PlatformAdapter, import("@aalis/core").ServiceRef<PlatformAdapter>>;
|
|
30
|
+
process: import("@aalis/core").OptionalUse<import("@aalis/api-process").ProcessService, import("@aalis/core").ServiceRef<import("@aalis/api-process").ProcessService>>;
|
|
31
|
+
sessionConfirm: import("@aalis/core").OptionalUse<import("@aalis/api-session-confirm").SessionConfirmService, import("@aalis/core").ServiceRef<import("@aalis/api-session-confirm").SessionConfirmService>>;
|
|
32
|
+
tools: import("@aalis/core").OptionalUse<import("@aalis/api-tools").ToolService, import("@aalis/api-tools").BoundTools>;
|
|
33
|
+
llm: import("@aalis/core").OptionalUse<import("@aalis/api-llm").LLMModel, import("@aalis/core").ServiceRef<import("@aalis/api-llm").LLMModel>>;
|
|
34
|
+
persona: import("@aalis/core").OptionalUse<import("@aalis/api-persona").PersonaService, import("@aalis/core").ServiceRef<import("@aalis/api-persona").PersonaService>>;
|
|
35
|
+
agent: import("@aalis/core").OptionalUse<import("@aalis/api-agent").AgentService, import("@aalis/api-agent").BoundAgent>;
|
|
36
36
|
}>;
|
|
37
37
|
export default _default;
|
|
38
38
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,EAIL,KAAK,eAAe,EAGrB,MAAM,qBAAqB,CAAC;AAK7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA+QzD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAK5F;;;;;;;;;;;;;;;;;;;;;;;;AA+BD,wBAQG"}
|
package/dist/index.js
CHANGED
|
@@ -9,16 +9,18 @@ import { pathToFileURL } from 'node:url';
|
|
|
9
9
|
import { agent } from '@aalis/api-agent';
|
|
10
10
|
import { authority } from '@aalis/api-authority';
|
|
11
11
|
import { commands } from '@aalis/api-commands';
|
|
12
|
+
import { hostConfig } from '@aalis/api-host-config';
|
|
12
13
|
import { listLLMModels, llm } from '@aalis/api-llm';
|
|
13
|
-
import {
|
|
14
|
+
import { packageManager } from '@aalis/api-package-manager';
|
|
14
15
|
import { persona } from '@aalis/api-persona';
|
|
15
16
|
import { aggregatePlatformDetails, getPlatformAdapters, getPlatformNames, platform, } from '@aalis/api-platform';
|
|
17
|
+
import { pluginSource } from '@aalis/api-plugin-source';
|
|
16
18
|
import { createProcessGateway, processService } from '@aalis/api-process';
|
|
17
19
|
import { sessionConfirm } from '@aalis/api-session-confirm';
|
|
18
20
|
import { createStorageGateway, readTailLines, storage } from '@aalis/api-storage';
|
|
19
21
|
import { tools } from '@aalis/api-tools';
|
|
20
22
|
import { DEFAULT_SUBSYSTEM_METADATA, webuiClient, webuiServer } from '@aalis/api-webui';
|
|
21
|
-
import { appService, config, definePlugin, events,
|
|
23
|
+
import { appService, config, definePlugin, events, LogHub, lifecycle, logger, optional, pluginsService, provide, services, } from '@aalis/core';
|
|
22
24
|
import { parseLogLine } from '@aalis/schema-log';
|
|
23
25
|
import express from 'express';
|
|
24
26
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
@@ -28,7 +30,7 @@ import { renderClientSwitchPage } from './client-switch-page.js';
|
|
|
28
30
|
import { createRouteGate } from './gate.js';
|
|
29
31
|
import { registerFileRoutes } from './routes/files.js';
|
|
30
32
|
import { registerMarketplaceRoutes } from './routes/marketplace.js';
|
|
31
|
-
import { registerPluginRoutes } from './routes/plugins.js';
|
|
33
|
+
import { registerPluginRoutes, saveAfterApply } from './routes/plugins.js';
|
|
32
34
|
import { registerProxyRoutes } from './routes/proxy.js';
|
|
33
35
|
import { registerUploadedFilesRoutes } from './routes/uploaded-files.js';
|
|
34
36
|
import { createWsHeartbeat } from './ws-heartbeat.js';
|
|
@@ -94,7 +96,7 @@ const configSchema = {
|
|
|
94
96
|
type: 'string',
|
|
95
97
|
label: '插件市场 npm 源',
|
|
96
98
|
default: 'https://registry.npmjs.org',
|
|
97
|
-
description: '插件市场检索用的 npm registry 基址。注意 npm 的 search API 并非所有镜像都支持(淘宝等国内源不支持),默认官方源;国内可填支持 search 的镜像或代理。安装走 package-manager(遵循本机 npm
|
|
99
|
+
description: '插件市场检索用的 npm registry 基址。注意 npm 的 search API 并非所有镜像都支持(淘宝等国内源不支持),默认官方源;国内可填支持 search 的镜像或代理。安装走 package-manager(遵循本机 npm 配置),卡片上的最新版与可更新也按安装使用的源查。',
|
|
98
100
|
},
|
|
99
101
|
// 活跃前端不在此配置:前端即 `webui-client` 服务的多 provider,活跃者由「服务偏好」
|
|
100
102
|
// (servicePreferences['webui-client'])决定——在 WebUI「服务」页的下拉框切换。
|
|
@@ -102,7 +104,6 @@ const configSchema = {
|
|
|
102
104
|
// ===== WebSocket 消息协议 =====
|
|
103
105
|
// 入站消息类型 + 校验 schema 见 ./protocol.ts(zod 强校验)
|
|
104
106
|
import { WSIncomingSchema } from './protocol.js';
|
|
105
|
-
export { WSIncomingSchema } from './protocol.js';
|
|
106
107
|
// ===== 日志文件读取(与 @aalis/runtime 的 file-logger 格式对偶)=====
|
|
107
108
|
// 行格式契约(format ↔ parse)由 @aalis/schema-log 的 parseLogLine 唯一持有,此处只负责
|
|
108
109
|
// 读取「持久化日志单一数据源」并按 cursor 尾读分页。
|
|
@@ -172,6 +173,8 @@ const uses = {
|
|
|
172
173
|
hostConfig: optional(hostConfig),
|
|
173
174
|
app: optional(appService),
|
|
174
175
|
plugins: optional(pluginsService),
|
|
176
|
+
source: optional(pluginSource),
|
|
177
|
+
packageManager: optional(packageManager),
|
|
175
178
|
storage: optional(storage),
|
|
176
179
|
authority: optional(authority),
|
|
177
180
|
commands: optional(commands),
|
|
@@ -183,7 +186,6 @@ const uses = {
|
|
|
183
186
|
llm: optional(llm),
|
|
184
187
|
persona: optional(persona),
|
|
185
188
|
agent: optional(agent),
|
|
186
|
-
memory: optional(memory),
|
|
187
189
|
};
|
|
188
190
|
export default definePlugin({
|
|
189
191
|
name,
|
|
@@ -206,10 +208,6 @@ async function startWebuiServer(caps) {
|
|
|
206
208
|
? config.tokenMode
|
|
207
209
|
: 'persist',
|
|
208
210
|
fixedToken: config.fixedToken ?? '',
|
|
209
|
-
relationGraphDefaultSpacing: (() => {
|
|
210
|
-
const v = Number(config.relationGraphDefaultSpacing);
|
|
211
|
-
return Number.isFinite(v) && v > 0 ? v : 120;
|
|
212
|
-
})(),
|
|
213
211
|
marketplaceRegistry: config.marketplaceRegistry?.trim() || 'https://registry.npmjs.org',
|
|
214
212
|
};
|
|
215
213
|
// 创建 storage gateway;所有文件读写(token、access、文件管理)都走这里
|
|
@@ -221,32 +219,10 @@ async function startWebuiServer(caps) {
|
|
|
221
219
|
// 所有模式都会写出 data:/webui/access.txt 便于查找访问 URL
|
|
222
220
|
const tokenFileUri = 'data:/webui/token';
|
|
223
221
|
const accessFileUri = 'data:/webui/access.txt';
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (uiConfig.tokenMode === 'persist' || uiConfig.tokenMode === 'fixed') {
|
|
229
|
-
try {
|
|
230
|
-
const raw = await storage.readFile(tokenFileUri, 'utf-8');
|
|
231
|
-
const existing = (typeof raw === 'string' ? raw : raw.toString('utf-8')).trim();
|
|
232
|
-
if (existing)
|
|
233
|
-
return existing;
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
/* not exists or unreadable */
|
|
237
|
-
}
|
|
238
|
-
const fresh = Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('hex');
|
|
239
|
-
try {
|
|
240
|
-
await storage.writeFile(tokenFileUri, fresh);
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
logger.warn(`持久化 token 失败,本次仍可使用但重启会再生成: ${err.message}`);
|
|
244
|
-
}
|
|
245
|
-
return fresh;
|
|
246
|
-
}
|
|
247
|
-
// ephemeral
|
|
248
|
-
return Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('hex');
|
|
249
|
-
}
|
|
222
|
+
const fixedToken = uiConfig.tokenMode === 'fixed' ? uiConfig.fixedToken.trim() : '';
|
|
223
|
+
let authToken = fixedToken || Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('hex');
|
|
224
|
+
/** 监听成功后的访问 URL;持久化 token 晚于监听读回时,据此重写 access.txt */
|
|
225
|
+
let listeningUrl;
|
|
250
226
|
/** 返回是否真的写成了:调用方据此决定能不能宣称「凭据已写入」 */
|
|
251
227
|
async function writeAccessFile(url, token) {
|
|
252
228
|
const lines = [
|
|
@@ -269,8 +245,46 @@ async function startWebuiServer(caps) {
|
|
|
269
245
|
return false;
|
|
270
246
|
}
|
|
271
247
|
}
|
|
272
|
-
|
|
273
|
-
|
|
248
|
+
// persist 的读回跟随 storage:storage 是 optional、不参与激活拓扑,可能晚于本插件上线
|
|
249
|
+
// (加载顺序、启动时配置出错后修好、运行时才启用)。只在 apply 里读一次会读空:内存里是新随机
|
|
250
|
+
// token,持久化的那份不生效,已登录的浏览器全部被登出;没有 token 文件时也永远建不起来。
|
|
251
|
+
// follow 对已在线的 storage 同步首挂,晚上线时补读。持久化的那份优先(浏览器里 30 天的 cookie
|
|
252
|
+
// 就是它);只认第一次成功,之后 storage 换人不再换 token。
|
|
253
|
+
let tokenSettled = uiConfig.tokenMode === 'ephemeral' || fixedToken !== '';
|
|
254
|
+
let tokenLoading;
|
|
255
|
+
caps.storage.follow(() => {
|
|
256
|
+
if (tokenSettled)
|
|
257
|
+
return;
|
|
258
|
+
tokenLoading = (async () => {
|
|
259
|
+
try {
|
|
260
|
+
const raw = await storage.readFile(tokenFileUri, 'utf-8');
|
|
261
|
+
const existing = (typeof raw === 'string' ? raw : raw.toString('utf-8')).trim();
|
|
262
|
+
if (existing) {
|
|
263
|
+
tokenSettled = true;
|
|
264
|
+
if (existing !== authToken) {
|
|
265
|
+
authToken = existing;
|
|
266
|
+
if (listeningUrl)
|
|
267
|
+
void writeAccessFile(listeningUrl, authToken);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
/* 不存在或读不了 */
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
await storage.writeFile(tokenFileUri, authToken);
|
|
277
|
+
tokenSettled = true;
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
logger.warn(`持久化 token 失败,本次仍可使用但重启会再生成: ${err.message}`);
|
|
281
|
+
}
|
|
282
|
+
})();
|
|
283
|
+
});
|
|
284
|
+
// storage 已在线时 follow 是同步首挂:等读回完成再继续,认证从第一个请求起就用持久化的 token
|
|
285
|
+
if (tokenLoading)
|
|
286
|
+
await tokenLoading;
|
|
287
|
+
const auth = createAuthSystem(() => authToken);
|
|
274
288
|
const expressApp = express();
|
|
275
289
|
expressApp.use(express.json({ limit: '10mb' }));
|
|
276
290
|
expressApp.use(auth.middleware);
|
|
@@ -367,21 +381,13 @@ async function startWebuiServer(caps) {
|
|
|
367
381
|
// 获取系统状态
|
|
368
382
|
expressApp.get('/api/status', gate(), (_req, res) => {
|
|
369
383
|
const personaSvc = caps.persona.current;
|
|
370
|
-
// 判断上传能力。media / file-reader
|
|
384
|
+
// 判断上传能力。media / file-reader 在本插件里只做在场探测,为一个布尔值反向依赖
|
|
371
385
|
// 它们的包不值当(file-reader 还是插件包),故按名动态查——动态查到的不算声明依赖。
|
|
372
386
|
const hasMedia = services.get('media') !== undefined;
|
|
373
387
|
const llmHasVision = listLLMModels(caps.llm).some(e => e.instance.capabilities.includes('vision'));
|
|
374
388
|
const hasFileReader = services.get('file-reader') !== undefined;
|
|
375
389
|
res.json({
|
|
376
|
-
name: personaSvc?.getPersonaName() ?? caps.hostConfig.
|
|
377
|
-
services: {
|
|
378
|
-
'webui-server': services.get(webuiServer) !== undefined,
|
|
379
|
-
cli: services.get('cli') !== undefined,
|
|
380
|
-
llm: caps.llm.current !== undefined,
|
|
381
|
-
agent: caps.agent.current !== undefined,
|
|
382
|
-
memory: caps.memory.current !== undefined,
|
|
383
|
-
persona: personaSvc !== undefined,
|
|
384
|
-
},
|
|
390
|
+
name: personaSvc?.getPersonaName() ?? caps.hostConfig.current?.get('name') ?? 'Aalis',
|
|
385
391
|
/** 上传能力:客户端据此决定显示哪些上传按钮 */
|
|
386
392
|
uploadCapabilities: {
|
|
387
393
|
/** 是否支持图片上传(media 可用 或 LLM 声明了 vision) */
|
|
@@ -424,6 +430,7 @@ async function startWebuiServer(caps) {
|
|
|
424
430
|
registerPluginRoutes(expressApp, {
|
|
425
431
|
app: caps.app,
|
|
426
432
|
plugins: caps.plugins,
|
|
433
|
+
source: caps.source,
|
|
427
434
|
hostConfig: caps.hostConfig,
|
|
428
435
|
tools: caps.tools,
|
|
429
436
|
commands: caps.commands,
|
|
@@ -437,20 +444,10 @@ async function startWebuiServer(caps) {
|
|
|
437
444
|
logger,
|
|
438
445
|
plugins: caps.plugins,
|
|
439
446
|
services,
|
|
440
|
-
|
|
441
|
-
packageManager: () => services.get('package-manager'),
|
|
447
|
+
packageManager: caps.packageManager,
|
|
442
448
|
}, gate, uiConfig.marketplaceRegistry, () => collectLocalPackageDeps(localScanDirs, fsScanEnv), () => discoverAndProvideClients());
|
|
443
|
-
//
|
|
449
|
+
// 历史日志从 data/latest.log 读(lazy load)。
|
|
444
450
|
// 单进程内 LogHub 不再缓存 buffer——历史以文件为单一数据源。
|
|
445
|
-
expressApp.get('/api/logs', gate(), async (_req, res) => {
|
|
446
|
-
try {
|
|
447
|
-
const entries = await readLogFileTail(storage, 200);
|
|
448
|
-
res.json(entries);
|
|
449
|
-
}
|
|
450
|
-
catch (err) {
|
|
451
|
-
res.status(500).json({ error: err.message });
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
451
|
// 尾部 N 条(首屏 / 显式刷新)
|
|
455
452
|
expressApp.get('/api/logs/tail', gate(), async (req, res) => {
|
|
456
453
|
const limit = Math.min(Math.max(Number(req.query.limit) || 200, 1), 5000);
|
|
@@ -487,8 +484,9 @@ async function startWebuiServer(caps) {
|
|
|
487
484
|
displayNameMap.set(p.instanceId, p.displayName);
|
|
488
485
|
}
|
|
489
486
|
}
|
|
490
|
-
//
|
|
491
|
-
|
|
487
|
+
// 根激活归宿主:core 的内置服务与宿主登记的 host-config / plugin-source 都在这里(与市场依赖图同一标签)。
|
|
488
|
+
// 别把内部 id 'root' 裸露给用户。
|
|
489
|
+
displayNameMap.set('root', '宿主');
|
|
492
490
|
const serviceNames = services.names();
|
|
493
491
|
const detail = {};
|
|
494
492
|
for (const svcName of serviceNames) {
|
|
@@ -524,7 +522,12 @@ async function startWebuiServer(caps) {
|
|
|
524
522
|
res.status(404).json({ ok: false, error: `service "${svcName}" has no provider with contextId "${contextId}"` });
|
|
525
523
|
return;
|
|
526
524
|
}
|
|
527
|
-
|
|
525
|
+
// 偏好要跨重启保留就得写进文档:宿主没提供文档时不动运行态
|
|
526
|
+
const host = caps.hostConfig.current;
|
|
527
|
+
if (!host) {
|
|
528
|
+
res.status(503).json({ ok: false, error: '宿主未提供配置文档(host-config),无法保存偏好' });
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
528
531
|
if (!services.prefer(svcName, contextId)) {
|
|
529
532
|
res.status(409).json({ ok: false, error: `service "${svcName}" rejected provider preference "${contextId}"` });
|
|
530
533
|
return;
|
|
@@ -534,18 +537,24 @@ async function startWebuiServer(caps) {
|
|
|
534
537
|
// 重挂与偏好同属内存态,必须在等落盘之前一起生效:save 拒绝时才不会留下「解析选 B、静态挂 A」。
|
|
535
538
|
if (svcName === 'webui-client')
|
|
536
539
|
remountActiveClient();
|
|
537
|
-
await
|
|
540
|
+
if (!(await saveAfterApply(host, res, 'restart')))
|
|
541
|
+
return;
|
|
538
542
|
res.json({ ok: true });
|
|
539
543
|
});
|
|
540
544
|
/** 清除服务偏好 */
|
|
541
545
|
expressApp.delete('/api/services/:name/prefer', gate(), async (req, res) => {
|
|
542
546
|
const svcName = String(req.params.name);
|
|
543
|
-
const host = caps.hostConfig.
|
|
547
|
+
const host = caps.hostConfig.current;
|
|
548
|
+
if (!host) {
|
|
549
|
+
res.status(503).json({ ok: false, error: '宿主未提供配置文档(host-config),无法保存偏好' });
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
544
552
|
services.unprefer(svcName);
|
|
545
553
|
host.removeServicePreference(svcName);
|
|
546
554
|
if (svcName === 'webui-client')
|
|
547
555
|
remountActiveClient();
|
|
548
|
-
await
|
|
556
|
+
if (!(await saveAfterApply(host, res, 'restart')))
|
|
557
|
+
return;
|
|
549
558
|
res.json({ ok: true });
|
|
550
559
|
});
|
|
551
560
|
// 前端切换已统一到「服务偏好」:前端是 webui-client 服务的多 provider,切换走
|
|
@@ -682,10 +691,6 @@ async function startWebuiServer(caps) {
|
|
|
682
691
|
// 同 provider 下所有 model entries 共享同一份 refresh 闭包,调任一个 entry 即可。
|
|
683
692
|
expressApp.post('/api/llm-providers/:contextId/refresh', gate(), async (req, res) => {
|
|
684
693
|
const contextId = req.params.contextId;
|
|
685
|
-
if (!contextId) {
|
|
686
|
-
res.status(400).json({ error: 'contextId is required' });
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
694
|
try {
|
|
690
695
|
const llmEntries = caps.llm.all();
|
|
691
696
|
const target = llmEntries.find(e => e.contextId === contextId && typeof e.instance.refresh === 'function');
|
|
@@ -806,15 +811,13 @@ async function startWebuiServer(caps) {
|
|
|
806
811
|
try {
|
|
807
812
|
const models = await provider.instance.listModels();
|
|
808
813
|
for (const m of models) {
|
|
809
|
-
const
|
|
810
|
-
const modelId = isModelInfo ? m.id : String(m);
|
|
811
|
-
const modelCaps = isModelInfo ? m.capabilities : [];
|
|
814
|
+
const modelId = String(m);
|
|
812
815
|
aggregated.push({
|
|
813
816
|
value: modelId,
|
|
814
817
|
model: modelId,
|
|
815
818
|
provider: provider.label ?? provider.contextId,
|
|
816
819
|
contextId: provider.contextId,
|
|
817
|
-
capabilities:
|
|
820
|
+
capabilities: [],
|
|
818
821
|
});
|
|
819
822
|
flatValues.push(modelId);
|
|
820
823
|
}
|
|
@@ -872,8 +875,17 @@ async function startWebuiServer(caps) {
|
|
|
872
875
|
allClients.add(ws);
|
|
873
876
|
heartbeat.track(ws);
|
|
874
877
|
ws.on('message', async (data) => {
|
|
878
|
+
// 非 JSON 帧与形状不对的帧同属协议违规:都只记一行告警,外层 catch 留给真正的处理失败
|
|
879
|
+
let raw;
|
|
875
880
|
try {
|
|
876
|
-
|
|
881
|
+
raw = JSON.parse(data.toString());
|
|
882
|
+
}
|
|
883
|
+
catch {
|
|
884
|
+
logger.warn('WebUI 收到协议违规消息: 非 JSON 帧');
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
try {
|
|
888
|
+
const parseResult = WSIncomingSchema.safeParse(raw);
|
|
877
889
|
if (!parseResult.success) {
|
|
878
890
|
logger.warn(`WebUI 收到协议违规消息: ${parseResult.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ')}`);
|
|
879
891
|
return;
|
|
@@ -1314,7 +1326,7 @@ async function startWebuiServer(caps) {
|
|
|
1314
1326
|
});
|
|
1315
1327
|
/**
|
|
1316
1328
|
* 全动态发现前端:按 `aalis.client:true` 标记扫描,**不硬编码任何前端包名**
|
|
1317
|
-
*
|
|
1329
|
+
* (忒修斯之船——任意第三方前端带标记+dist 即被发现)。
|
|
1318
1330
|
* 覆盖三种拓扑:monorepo(扫 packages 同级目录)/ 独立项目(扫 node_modules/@aalis + 根 deps)。
|
|
1319
1331
|
*
|
|
1320
1332
|
* **幂等**,可重复调用:装完一个 `aalis-interface` 包后要立刻重跑,否则它不会出现在服务页
|
|
@@ -1323,8 +1335,6 @@ async function startWebuiServer(caps) {
|
|
|
1323
1335
|
*
|
|
1324
1336
|
* 只登记新候选,**不切换活跃前端**:换前端是用户在服务页的显式选择,装了个新的不该把
|
|
1325
1337
|
* 他正在用的顶掉。
|
|
1326
|
-
*
|
|
1327
|
-
* @returns 本次新发现的候选数
|
|
1328
1338
|
*/
|
|
1329
1339
|
function discoverAndProvideClients() {
|
|
1330
1340
|
const projectRequire = createRequire(pathToFileURL(resolve(process.cwd(), 'package.json')));
|
|
@@ -1382,10 +1392,13 @@ async function startWebuiServer(caps) {
|
|
|
1382
1392
|
clientProviderDisposers.set(candidate.id, provide(webuiClient, { getClientDir: () => candidate.dir }, { label: candidate.label, onBehalfOf: candidate.id }));
|
|
1383
1393
|
logger.info(`发现前端: ${candidate.label} (${candidate.dir})`);
|
|
1384
1394
|
}
|
|
1385
|
-
return fresh.length;
|
|
1386
1395
|
}
|
|
1387
1396
|
// 启动服务器
|
|
1388
|
-
events.on('app:ready', () => {
|
|
1397
|
+
events.on('app:ready', async () => {
|
|
1398
|
+
// 冷启动时 storage 的激活在 app:ready 之前收敛;等晚到的读回落定,listen / autoOpen /
|
|
1399
|
+
// access.txt 用的就是持久化的 token
|
|
1400
|
+
if (tokenLoading)
|
|
1401
|
+
await tokenLoading;
|
|
1389
1402
|
discoverAndProvideClients();
|
|
1390
1403
|
// 活跃前端 = 解析后的 webui-client 服务(servicePreferences 偏好 > 优先级 > 注册顺序);零前端则 404。
|
|
1391
1404
|
const activeDir = currentClientDir();
|
|
@@ -1415,6 +1428,7 @@ async function startWebuiServer(caps) {
|
|
|
1415
1428
|
// 展示/打开一律用回环;局域网访问地址由用户按本机 IP 自行替换。
|
|
1416
1429
|
const displayHost = uiConfig.host === '0.0.0.0' || uiConfig.host === '::' ? '127.0.0.1' : uiConfig.host;
|
|
1417
1430
|
const url = `http://${displayHost}:${uiConfig.port}/`;
|
|
1431
|
+
listeningUrl = url;
|
|
1418
1432
|
const accessUrl = `${url}?token=${authToken}`;
|
|
1419
1433
|
logger.info(`WebUI 已启动: ${url}`);
|
|
1420
1434
|
// 多前端时提示恢复页 URL:万一切到无切换 UI 的前端被卡住,可在此切回(详见 client-switch-page.ts)。
|
|
@@ -1426,7 +1440,7 @@ async function startWebuiServer(caps) {
|
|
|
1426
1440
|
: uiConfig.tokenMode === 'fixed'
|
|
1427
1441
|
? 'token 来自配置 fixedToken,固定不变'
|
|
1428
1442
|
: 'token 已持久化到 storage data:/webui/token,重启沿用';
|
|
1429
|
-
// 不把 token 打进日志(会落 latest.log / 被 /api/logs 回放 / 贴日志求助时外泄)。
|
|
1443
|
+
// 不把 token 打进日志(会落 latest.log / 被 /api/logs/* 回放 / 贴日志求助时外泄)。
|
|
1430
1444
|
// 仅打不带 token 的 URL,完整一键登录链接见 access.txt(该文件应 0o600,见 token 落盘)。
|
|
1431
1445
|
logger.info(`首次访问 URL(${tokenHint}): ${url} —— 完整一键登录链接见 ${accessFileUri}`);
|
|
1432
1446
|
void (async () => {
|
|
@@ -1522,11 +1536,6 @@ async function startWebuiServer(caps) {
|
|
|
1522
1536
|
const webuiService = {
|
|
1523
1537
|
getPort: () => uiConfig.port,
|
|
1524
1538
|
getHost: () => uiConfig.host,
|
|
1525
|
-
setClientDir(dir) {
|
|
1526
|
-
clientDist = dir;
|
|
1527
|
-
mountStaticDir(dir);
|
|
1528
|
-
logger.info(`前端已切换: ${dir}`);
|
|
1529
|
-
},
|
|
1530
1539
|
registerPage(page, contextId) {
|
|
1531
1540
|
const list = registeredPages.get(contextId) ?? [];
|
|
1532
1541
|
const entry = { ...page, pluginName: contextId };
|