@aalis/plugin-webui-server 0.12.0 → 0.14.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 +111 -91
- package/dist/index.js.map +1 -1
- package/dist/routes/marketplace.d.ts +30 -26
- package/dist/routes/marketplace.d.ts.map +1 -1
- package/dist/routes/marketplace.js +119 -85
- package/dist/routes/marketplace.js.map +1 -1
- package/dist/routes/plugins.d.ts +24 -4
- package/dist/routes/plugins.d.ts.map +1 -1
- package/dist/routes/plugins.js +282 -90
- 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;AAgRzD;;;;;;;;;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';
|
|
@@ -65,7 +67,7 @@ const configSchema = {
|
|
|
65
67
|
type: 'boolean',
|
|
66
68
|
label: '启动时自动打开浏览器',
|
|
67
69
|
default: true,
|
|
68
|
-
description: '
|
|
70
|
+
description: '访问 token 为新生成时(persist 模式首次生成、ephemeral 模式每次生成)以含 token 的 URL 自动开启默认浏览器;沿用已有 token(persist 读回已持久化的 token、fixed 用配置的 fixedToken)时不打开;fixed 模式 fixedToken 为空时按 persist 处理。SSH/headless 环境建议关闭',
|
|
69
71
|
},
|
|
70
72
|
tokenMode: {
|
|
71
73
|
type: 'select',
|
|
@@ -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,50 @@ 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
|
+
// autoOpen 只在 token 是本次新生成时打开浏览器:沿用已有 token(fixed、persist 读回)时浏览器里的 cookie 仍有效,
|
|
255
|
+
// 再开一页只是重复。app:ready 是粘性事件,本插件每次 bounce 都会重新收到,不按此判断就每次重载多开一个标签页
|
|
256
|
+
let tokenFresh = fixedToken === '';
|
|
257
|
+
let tokenLoading;
|
|
258
|
+
caps.storage.follow(() => {
|
|
259
|
+
if (tokenSettled)
|
|
260
|
+
return;
|
|
261
|
+
tokenLoading = (async () => {
|
|
262
|
+
try {
|
|
263
|
+
const raw = await storage.readFile(tokenFileUri, 'utf-8');
|
|
264
|
+
const existing = (typeof raw === 'string' ? raw : raw.toString('utf-8')).trim();
|
|
265
|
+
if (existing) {
|
|
266
|
+
tokenSettled = true;
|
|
267
|
+
tokenFresh = false;
|
|
268
|
+
if (existing !== authToken) {
|
|
269
|
+
authToken = existing;
|
|
270
|
+
if (listeningUrl)
|
|
271
|
+
void writeAccessFile(listeningUrl, authToken);
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
/* 不存在或读不了 */
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
await storage.writeFile(tokenFileUri, authToken);
|
|
281
|
+
tokenSettled = true;
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
logger.warn(`持久化 token 失败,本次仍可使用但重启会再生成: ${err.message}`);
|
|
285
|
+
}
|
|
286
|
+
})();
|
|
287
|
+
});
|
|
288
|
+
// storage 已在线时 follow 是同步首挂:等读回完成再继续,认证从第一个请求起就用持久化的 token
|
|
289
|
+
if (tokenLoading)
|
|
290
|
+
await tokenLoading;
|
|
291
|
+
const auth = createAuthSystem(() => authToken);
|
|
274
292
|
const expressApp = express();
|
|
275
293
|
expressApp.use(express.json({ limit: '10mb' }));
|
|
276
294
|
expressApp.use(auth.middleware);
|
|
@@ -367,21 +385,17 @@ async function startWebuiServer(caps) {
|
|
|
367
385
|
// 获取系统状态
|
|
368
386
|
expressApp.get('/api/status', gate(), (_req, res) => {
|
|
369
387
|
const personaSvc = caps.persona.current;
|
|
370
|
-
// 判断上传能力。media / file-reader
|
|
388
|
+
// 判断上传能力。media / file-reader 在本插件里只做在场探测,为一个布尔值反向依赖
|
|
371
389
|
// 它们的包不值当(file-reader 还是插件包),故按名动态查——动态查到的不算声明依赖。
|
|
372
390
|
const hasMedia = services.get('media') !== undefined;
|
|
373
391
|
const llmHasVision = listLLMModels(caps.llm).some(e => e.instance.capabilities.includes('vision'));
|
|
374
392
|
const hasFileReader = services.get('file-reader') !== undefined;
|
|
393
|
+
const appName = caps.hostConfig.current?.get('name') ?? 'Aalis';
|
|
375
394
|
res.json({
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
llm: caps.llm.current !== undefined,
|
|
381
|
-
agent: caps.agent.current !== undefined,
|
|
382
|
-
memory: caps.memory.current !== undefined,
|
|
383
|
-
persona: personaSvc !== undefined,
|
|
384
|
-
},
|
|
395
|
+
/** 对话对象的显示名:装有人设时是人设名,否则同 appName */
|
|
396
|
+
name: personaSvc?.getPersonaName() ?? appName,
|
|
397
|
+
/** 全局配置里的应用名称,每次实时读配置文档 */
|
|
398
|
+
appName,
|
|
385
399
|
/** 上传能力:客户端据此决定显示哪些上传按钮 */
|
|
386
400
|
uploadCapabilities: {
|
|
387
401
|
/** 是否支持图片上传(media 可用 或 LLM 声明了 vision) */
|
|
@@ -424,6 +438,7 @@ async function startWebuiServer(caps) {
|
|
|
424
438
|
registerPluginRoutes(expressApp, {
|
|
425
439
|
app: caps.app,
|
|
426
440
|
plugins: caps.plugins,
|
|
441
|
+
source: caps.source,
|
|
427
442
|
hostConfig: caps.hostConfig,
|
|
428
443
|
tools: caps.tools,
|
|
429
444
|
commands: caps.commands,
|
|
@@ -437,20 +452,10 @@ async function startWebuiServer(caps) {
|
|
|
437
452
|
logger,
|
|
438
453
|
plugins: caps.plugins,
|
|
439
454
|
services,
|
|
440
|
-
|
|
441
|
-
packageManager: () => services.get('package-manager'),
|
|
455
|
+
packageManager: caps.packageManager,
|
|
442
456
|
}, gate, uiConfig.marketplaceRegistry, () => collectLocalPackageDeps(localScanDirs, fsScanEnv), () => discoverAndProvideClients());
|
|
443
|
-
//
|
|
457
|
+
// 历史日志从 data/latest.log 读(lazy load)。
|
|
444
458
|
// 单进程内 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
459
|
// 尾部 N 条(首屏 / 显式刷新)
|
|
455
460
|
expressApp.get('/api/logs/tail', gate(), async (req, res) => {
|
|
456
461
|
const limit = Math.min(Math.max(Number(req.query.limit) || 200, 1), 5000);
|
|
@@ -487,8 +492,9 @@ async function startWebuiServer(caps) {
|
|
|
487
492
|
displayNameMap.set(p.instanceId, p.displayName);
|
|
488
493
|
}
|
|
489
494
|
}
|
|
490
|
-
//
|
|
491
|
-
|
|
495
|
+
// 根激活归宿主:core 的内置服务与宿主登记的 host-config / plugin-source 都在这里(与市场依赖图同一标签)。
|
|
496
|
+
// 别把内部 id 'root' 裸露给用户。
|
|
497
|
+
displayNameMap.set('root', '宿主');
|
|
492
498
|
const serviceNames = services.names();
|
|
493
499
|
const detail = {};
|
|
494
500
|
for (const svcName of serviceNames) {
|
|
@@ -524,7 +530,12 @@ async function startWebuiServer(caps) {
|
|
|
524
530
|
res.status(404).json({ ok: false, error: `service "${svcName}" has no provider with contextId "${contextId}"` });
|
|
525
531
|
return;
|
|
526
532
|
}
|
|
527
|
-
|
|
533
|
+
// 偏好要跨重启保留就得写进文档:宿主没提供文档时不动运行态
|
|
534
|
+
const host = caps.hostConfig.current;
|
|
535
|
+
if (!host) {
|
|
536
|
+
res.status(503).json({ ok: false, error: '宿主未提供配置文档(host-config),无法保存偏好' });
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
528
539
|
if (!services.prefer(svcName, contextId)) {
|
|
529
540
|
res.status(409).json({ ok: false, error: `service "${svcName}" rejected provider preference "${contextId}"` });
|
|
530
541
|
return;
|
|
@@ -534,18 +545,24 @@ async function startWebuiServer(caps) {
|
|
|
534
545
|
// 重挂与偏好同属内存态,必须在等落盘之前一起生效:save 拒绝时才不会留下「解析选 B、静态挂 A」。
|
|
535
546
|
if (svcName === 'webui-client')
|
|
536
547
|
remountActiveClient();
|
|
537
|
-
await
|
|
548
|
+
if (!(await saveAfterApply(host, res, 'restart')))
|
|
549
|
+
return;
|
|
538
550
|
res.json({ ok: true });
|
|
539
551
|
});
|
|
540
552
|
/** 清除服务偏好 */
|
|
541
553
|
expressApp.delete('/api/services/:name/prefer', gate(), async (req, res) => {
|
|
542
554
|
const svcName = String(req.params.name);
|
|
543
|
-
const host = caps.hostConfig.
|
|
555
|
+
const host = caps.hostConfig.current;
|
|
556
|
+
if (!host) {
|
|
557
|
+
res.status(503).json({ ok: false, error: '宿主未提供配置文档(host-config),无法保存偏好' });
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
544
560
|
services.unprefer(svcName);
|
|
545
561
|
host.removeServicePreference(svcName);
|
|
546
562
|
if (svcName === 'webui-client')
|
|
547
563
|
remountActiveClient();
|
|
548
|
-
await
|
|
564
|
+
if (!(await saveAfterApply(host, res, 'restart')))
|
|
565
|
+
return;
|
|
549
566
|
res.json({ ok: true });
|
|
550
567
|
});
|
|
551
568
|
// 前端切换已统一到「服务偏好」:前端是 webui-client 服务的多 provider,切换走
|
|
@@ -680,18 +697,17 @@ async function startWebuiServer(caps) {
|
|
|
680
697
|
// 触发指定 provider 重新探测远端模型列表(用于 webui 上的"刷新模型"按钮)
|
|
681
698
|
// 仅对在 LLMModel 上实现了 refresh() 的 provider 生效(远端动态发现型,如 Ollama / OpenAI)。
|
|
682
699
|
// 同 provider 下所有 model entries 共享同一份 refresh 闭包,调任一个 entry 即可。
|
|
700
|
+
// 按 providerId 找条目,与 /api/llm-providers 的聚合同一口径:条目的 contextId 是 `<provider>/<model>`,不等于 provider
|
|
683
701
|
expressApp.post('/api/llm-providers/:contextId/refresh', gate(), async (req, res) => {
|
|
684
702
|
const contextId = req.params.contextId;
|
|
685
|
-
if (!contextId) {
|
|
686
|
-
res.status(400).json({ error: 'contextId is required' });
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
703
|
try {
|
|
690
|
-
const llmEntries = caps.llm.all();
|
|
691
|
-
const target = llmEntries.find(e =>
|
|
704
|
+
const llmEntries = caps.llm.all().filter(e => e.instance.providerId === contextId);
|
|
705
|
+
const target = llmEntries.find(e => typeof e.instance.refresh === 'function');
|
|
692
706
|
if (!target) {
|
|
693
707
|
res.status(404).json({
|
|
694
|
-
error:
|
|
708
|
+
error: llmEntries.length > 0
|
|
709
|
+
? `提供者 ${contextId} 不支持运行时刷新模型列表(例如关闭了模型发现 discoverModels)`
|
|
710
|
+
: `提供者 ${contextId} 当前没有已注册的模型,无法刷新`,
|
|
695
711
|
});
|
|
696
712
|
return;
|
|
697
713
|
}
|
|
@@ -806,15 +822,13 @@ async function startWebuiServer(caps) {
|
|
|
806
822
|
try {
|
|
807
823
|
const models = await provider.instance.listModels();
|
|
808
824
|
for (const m of models) {
|
|
809
|
-
const
|
|
810
|
-
const modelId = isModelInfo ? m.id : String(m);
|
|
811
|
-
const modelCaps = isModelInfo ? m.capabilities : [];
|
|
825
|
+
const modelId = String(m);
|
|
812
826
|
aggregated.push({
|
|
813
827
|
value: modelId,
|
|
814
828
|
model: modelId,
|
|
815
829
|
provider: provider.label ?? provider.contextId,
|
|
816
830
|
contextId: provider.contextId,
|
|
817
|
-
capabilities:
|
|
831
|
+
capabilities: [],
|
|
818
832
|
});
|
|
819
833
|
flatValues.push(modelId);
|
|
820
834
|
}
|
|
@@ -872,8 +886,17 @@ async function startWebuiServer(caps) {
|
|
|
872
886
|
allClients.add(ws);
|
|
873
887
|
heartbeat.track(ws);
|
|
874
888
|
ws.on('message', async (data) => {
|
|
889
|
+
// 非 JSON 帧与形状不对的帧同属协议违规:都只记一行告警,外层 catch 留给真正的处理失败
|
|
890
|
+
let raw;
|
|
875
891
|
try {
|
|
876
|
-
|
|
892
|
+
raw = JSON.parse(data.toString());
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
logger.warn('WebUI 收到协议违规消息: 非 JSON 帧');
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
try {
|
|
899
|
+
const parseResult = WSIncomingSchema.safeParse(raw);
|
|
877
900
|
if (!parseResult.success) {
|
|
878
901
|
logger.warn(`WebUI 收到协议违规消息: ${parseResult.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ')}`);
|
|
879
902
|
return;
|
|
@@ -1314,7 +1337,7 @@ async function startWebuiServer(caps) {
|
|
|
1314
1337
|
});
|
|
1315
1338
|
/**
|
|
1316
1339
|
* 全动态发现前端:按 `aalis.client:true` 标记扫描,**不硬编码任何前端包名**
|
|
1317
|
-
*
|
|
1340
|
+
* (忒修斯之船——任意第三方前端带标记+dist 即被发现)。
|
|
1318
1341
|
* 覆盖三种拓扑:monorepo(扫 packages 同级目录)/ 独立项目(扫 node_modules/@aalis + 根 deps)。
|
|
1319
1342
|
*
|
|
1320
1343
|
* **幂等**,可重复调用:装完一个 `aalis-interface` 包后要立刻重跑,否则它不会出现在服务页
|
|
@@ -1323,8 +1346,6 @@ async function startWebuiServer(caps) {
|
|
|
1323
1346
|
*
|
|
1324
1347
|
* 只登记新候选,**不切换活跃前端**:换前端是用户在服务页的显式选择,装了个新的不该把
|
|
1325
1348
|
* 他正在用的顶掉。
|
|
1326
|
-
*
|
|
1327
|
-
* @returns 本次新发现的候选数
|
|
1328
1349
|
*/
|
|
1329
1350
|
function discoverAndProvideClients() {
|
|
1330
1351
|
const projectRequire = createRequire(pathToFileURL(resolve(process.cwd(), 'package.json')));
|
|
@@ -1382,10 +1403,13 @@ async function startWebuiServer(caps) {
|
|
|
1382
1403
|
clientProviderDisposers.set(candidate.id, provide(webuiClient, { getClientDir: () => candidate.dir }, { label: candidate.label, onBehalfOf: candidate.id }));
|
|
1383
1404
|
logger.info(`发现前端: ${candidate.label} (${candidate.dir})`);
|
|
1384
1405
|
}
|
|
1385
|
-
return fresh.length;
|
|
1386
1406
|
}
|
|
1387
1407
|
// 启动服务器
|
|
1388
|
-
events.on('app:ready', () => {
|
|
1408
|
+
events.on('app:ready', async () => {
|
|
1409
|
+
// 冷启动时 storage 的激活在 app:ready 之前收敛;等晚到的读回落定,listen / autoOpen /
|
|
1410
|
+
// access.txt 用的就是持久化的 token
|
|
1411
|
+
if (tokenLoading)
|
|
1412
|
+
await tokenLoading;
|
|
1389
1413
|
discoverAndProvideClients();
|
|
1390
1414
|
// 活跃前端 = 解析后的 webui-client 服务(servicePreferences 偏好 > 优先级 > 注册顺序);零前端则 404。
|
|
1391
1415
|
const activeDir = currentClientDir();
|
|
@@ -1415,6 +1439,7 @@ async function startWebuiServer(caps) {
|
|
|
1415
1439
|
// 展示/打开一律用回环;局域网访问地址由用户按本机 IP 自行替换。
|
|
1416
1440
|
const displayHost = uiConfig.host === '0.0.0.0' || uiConfig.host === '::' ? '127.0.0.1' : uiConfig.host;
|
|
1417
1441
|
const url = `http://${displayHost}:${uiConfig.port}/`;
|
|
1442
|
+
listeningUrl = url;
|
|
1418
1443
|
const accessUrl = `${url}?token=${authToken}`;
|
|
1419
1444
|
logger.info(`WebUI 已启动: ${url}`);
|
|
1420
1445
|
// 多前端时提示恢复页 URL:万一切到无切换 UI 的前端被卡住,可在此切回(详见 client-switch-page.ts)。
|
|
@@ -1426,7 +1451,7 @@ async function startWebuiServer(caps) {
|
|
|
1426
1451
|
: uiConfig.tokenMode === 'fixed'
|
|
1427
1452
|
? 'token 来自配置 fixedToken,固定不变'
|
|
1428
1453
|
: 'token 已持久化到 storage data:/webui/token,重启沿用';
|
|
1429
|
-
// 不把 token 打进日志(会落 latest.log / 被 /api/logs 回放 / 贴日志求助时外泄)。
|
|
1454
|
+
// 不把 token 打进日志(会落 latest.log / 被 /api/logs/* 回放 / 贴日志求助时外泄)。
|
|
1430
1455
|
// 仅打不带 token 的 URL,完整一键登录链接见 access.txt(该文件应 0o600,见 token 落盘)。
|
|
1431
1456
|
logger.info(`首次访问 URL(${tokenHint}): ${url} —— 完整一键登录链接见 ${accessFileUri}`);
|
|
1432
1457
|
void (async () => {
|
|
@@ -1449,7 +1474,7 @@ async function startWebuiServer(caps) {
|
|
|
1449
1474
|
}
|
|
1450
1475
|
logger.info(`访问凭据已写入: ${accessFileUri}(绝对路径: ${absHint})`);
|
|
1451
1476
|
})();
|
|
1452
|
-
if (uiConfig.autoOpen)
|
|
1477
|
+
if (uiConfig.autoOpen && tokenFresh)
|
|
1453
1478
|
openBrowser(accessUrl, createProcessGateway(caps.process));
|
|
1454
1479
|
});
|
|
1455
1480
|
});
|
|
@@ -1522,11 +1547,6 @@ async function startWebuiServer(caps) {
|
|
|
1522
1547
|
const webuiService = {
|
|
1523
1548
|
getPort: () => uiConfig.port,
|
|
1524
1549
|
getHost: () => uiConfig.host,
|
|
1525
|
-
setClientDir(dir) {
|
|
1526
|
-
clientDist = dir;
|
|
1527
|
-
mountStaticDir(dir);
|
|
1528
|
-
logger.info(`前端已切换: ${dir}`);
|
|
1529
|
-
},
|
|
1530
1550
|
registerPage(page, contextId) {
|
|
1531
1551
|
const list = registeredPages.get(contextId) ?? [];
|
|
1532
1552
|
const entry = { ...page, pluginName: contextId };
|