@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 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 由调用方传入(参见 index.ts resolveAuthToken,支持 ephemeral / persist / fixed 三种模式)
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(token: string, _logger: Logger): AuthSystem;
21
+ export declare function createAuthSystem(getToken: () => string): AuthSystem;
22
22
  /**
23
23
  * 跨平台打开默认浏览器到指定 URL(无外部依赖)
24
24
  * 失败静默;此为便利功能,不影响服务启动。
@@ -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,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,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;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA+E3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAwBnE"}
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 由调用方传入(参见 index.ts resolveAuthToken,支持 ephemeral / persist / fixed 三种模式)
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(token, _logger) {
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 === token;
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":"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"}
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
- * 默认活跃前端由调用方按 config 或「排序后第一个」决定——本函数不做偏好判断、不认任何具体名字。
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;;;;GAIG;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"}
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"}
@@ -1,12 +1,13 @@
1
1
  // 全动态前端发现(纯逻辑,注入 fs/path 便于单测)。
2
2
  //
3
- // 按 `aalis.client:true` 标记 + 存在 `dist/index.html` 收录前端候选——**不硬编码任何前端包名**,
4
- // 与 runtime 加载器的 marker 驱动一致(忒修斯之船:任意第三方前端,带标记+dist 即被发现)。
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
- * 默认活跃前端由调用方按 config 或「排序后第一个」决定——本函数不做偏好判断、不认任何具体名字。
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":"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"}
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/dist/composition/descriptors.js").OptionalUse<import("@aalis/core").HostConfig, import("@aalis/core").ServiceRef<import("@aalis/core").HostConfig>>;
23
- app: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/core").AppService, import("@aalis/core").ServiceRef<import("@aalis/core").AppService>>;
24
- plugins: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/core").PluginManagerService, import("@aalis/core").ServiceRef<import("@aalis/core").PluginManagerService>>;
25
- storage: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<StorageService, import("@aalis/core").ServiceRef<StorageService>>;
26
- authority: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-authority").AuthorityService, import("@aalis/core").ServiceRef<import("@aalis/api-authority").AuthorityService>>;
27
- commands: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-commands").CommandService, import("@aalis/api-commands").BoundCommands>;
28
- platform: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<PlatformAdapter, import("@aalis/core").ServiceRef<PlatformAdapter>>;
29
- process: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-process").ProcessService, import("@aalis/core").ServiceRef<import("@aalis/api-process").ProcessService>>;
30
- sessionConfirm: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-session-confirm").SessionConfirmService, import("@aalis/core").ServiceRef<import("@aalis/api-session-confirm").SessionConfirmService>>;
31
- tools: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-tools").ToolService, import("@aalis/api-tools").BoundTools>;
32
- llm: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-llm").LLMModel, import("@aalis/core").ServiceRef<import("@aalis/api-llm").LLMModel>>;
33
- persona: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-persona").PersonaService, import("@aalis/core").ServiceRef<import("@aalis/api-persona").PersonaService>>;
34
- agent: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-agent").AgentService, import("@aalis/api-agent").BoundAgent>;
35
- memory: import("@aalis/core/dist/composition/descriptors.js").OptionalUse<import("@aalis/api-memory").MemoryService, import("@aalis/core").ServiceRef<import("@aalis/api-memory").MemoryService>>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,EAIL,KAAK,eAAe,EAGrB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAmIzD,OAAO,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAmJlE;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAK5F;;;;;;;;;;;;;;;;;;;;;;;AA8BD,wBAQG"}
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 { memory } from '@aalis/api-memory';
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, hostConfig, LogHub, lifecycle, logger, optional, pluginsService, provide, services, } from '@aalis/core';
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
- async function resolveAuthToken() {
225
- if (uiConfig.tokenMode === 'fixed' && uiConfig.fixedToken.trim()) {
226
- return uiConfig.fixedToken.trim();
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
- const authToken = await resolveAuthToken();
273
- const auth = createAuthSystem(authToken, logger.child('auth'));
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 / cli 在本插件里只做在场探测,为一个布尔值反向依赖
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.require().get('name'),
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
- // package-manager 由插件提供,本包不反向依赖那个插件包:按名动态查,缺席时装卸路由回 503
441
- packageManager: () => services.get('package-manager'),
447
+ packageManager: caps.packageManager,
442
448
  }, gate, uiConfig.marketplaceRegistry, () => collectLocalPackageDeps(localScanDirs, fsScanEnv), () => discoverAndProvideClients());
443
- // 获取历史日志:从 data/latest.log 读尾部 N 条(lazy load)。
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
- // 根激活提供核心服务——显示包名,别把内部 id 'root' 裸露给用户。
491
- displayNameMap.set('root', '@aalis/core');
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
- const host = caps.hostConfig.require();
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 caps.app.require().saveConfig();
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.require();
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 caps.app.require().saveConfig();
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 isModelInfo = typeof m === 'object' && m !== null && 'id' in m;
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: modelCaps,
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
- const parseResult = WSIncomingSchema.safeParse(JSON.parse(data.toString()));
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
- * (与 runtime 加载器的 marker 驱动一致;忒修斯之船——任意第三方前端带标记+dist 即被发现)。
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 };