mioku-service-webui 4.13.0 → 4.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/database.ts +7 -1
- package/package.json +1 -1
- package/routes/agent.ts +39 -59
- package/routes/ai.ts +61 -116
- package/system.ts +22 -5
package/database.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getConfigDefaults, withConfigDefaults } from "mioku";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { Database } from "bun:sqlite";
|
|
@@ -359,7 +360,12 @@ export function getPluginConfigs(pluginName: string): Record<string, any> {
|
|
|
359
360
|
|
|
360
361
|
for (const file of files) {
|
|
361
362
|
const filePath = path.join(pluginDir, file);
|
|
362
|
-
|
|
363
|
+
const configName = path.basename(file, ".json");
|
|
364
|
+
// 磁盘值覆盖插件登记的默认值,缺字段时页面也能拿到完整配置
|
|
365
|
+
result[configName] = withConfigDefaults(
|
|
366
|
+
readJsonFile(filePath, {}),
|
|
367
|
+
getConfigDefaults("plugin", pluginName, configName),
|
|
368
|
+
);
|
|
363
369
|
}
|
|
364
370
|
|
|
365
371
|
return result;
|
package/package.json
CHANGED
package/routes/agent.ts
CHANGED
|
@@ -31,78 +31,44 @@ function sanitizeAccess(input: any): any {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 保存时只做「不该写进这个文件」的剔除,不补默认值。
|
|
36
|
+
*
|
|
37
|
+
* 默认值由 agent 插件在 `registerConfig` 时登记、读取时深合并
|
|
38
|
+
* (`mergeAgentConfig`)——这里再抄一份,加字段时就会两边漂移。
|
|
39
|
+
*/
|
|
34
40
|
function sanitizeBaseForSave(body: any): any {
|
|
35
41
|
if (!body || typeof body !== "object") return body;
|
|
36
42
|
const next = { ...body };
|
|
37
43
|
delete next.persona;
|
|
38
44
|
delete next.roleBindings;
|
|
39
|
-
next.access = sanitizeAccess(next.access);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if (next.access !== undefined) next.access = sanitizeAccess(next.access);
|
|
46
|
+
if (typeof next.workspaceDir === "string") {
|
|
47
|
+
next.workspaceDir = next.workspaceDir.trim();
|
|
48
|
+
}
|
|
49
|
+
if (typeof next.model === "string") next.model = next.model.trim();
|
|
50
|
+
if (!PERMISSION_LEVELS.has(next.permissionLevel)) delete next.permissionLevel;
|
|
45
51
|
return next;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
function sanitizeSettingsForSave(body: any): any {
|
|
49
55
|
if (!body || typeof body !== "object") return body;
|
|
50
56
|
const next = { ...body };
|
|
51
|
-
|
|
52
|
-
const num = Math.floor(Number(value));
|
|
53
|
-
return Number.isFinite(num) && num > 0 ? num : fallback;
|
|
54
|
-
};
|
|
55
|
-
const nonNegative = (value: unknown, fallback: number) => {
|
|
56
|
-
const num = Math.floor(Number(value));
|
|
57
|
-
return Number.isFinite(num) && num >= 0 ? num : fallback;
|
|
58
|
-
};
|
|
57
|
+
// 这两个属于 chat 插件的 base 配置,老版本 UI 可能还会带上来
|
|
59
58
|
delete next.temperature;
|
|
60
59
|
delete next.enableMediaRecognition;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
defaultLimit: positive(next.webSearch?.defaultLimit, 5),
|
|
74
|
-
maxLimit: positive(next.webSearch?.maxLimit, 8),
|
|
75
|
-
maxSearchCount: positive(next.webSearch?.maxSearchCount, 50),
|
|
76
|
-
};
|
|
77
|
-
next.webFetch = {
|
|
78
|
-
enabled: next.webFetch?.enabled !== false,
|
|
79
|
-
timeoutMs: positive(next.webFetch?.timeoutMs, 15000),
|
|
80
|
-
maxChars: positive(next.webFetch?.maxChars, 12000),
|
|
81
|
-
};
|
|
82
|
-
next.bash = {
|
|
83
|
-
enabled: next.bash?.enabled !== false,
|
|
84
|
-
timeoutMs: positive(next.bash?.timeoutMs, 120000),
|
|
85
|
-
approvalTimeoutMs: positive(next.bash?.approvalTimeoutMs, 300000),
|
|
86
|
-
};
|
|
87
|
-
next.skills = {
|
|
88
|
-
enabled: next.skills?.enabled !== false,
|
|
89
|
-
extraRoots: Array.isArray(next.skills?.extraRoots)
|
|
90
|
-
? next.skills.extraRoots
|
|
91
|
-
.map((item: unknown) => String(item ?? "").trim())
|
|
92
|
-
.filter(Boolean)
|
|
93
|
-
: [],
|
|
94
|
-
maxInPrompt: positive(next.skills?.maxInPrompt, 50),
|
|
95
|
-
descriptionMaxLength: positive(next.skills?.descriptionMaxLength, 500),
|
|
96
|
-
activationTtlMs: positive(next.skills?.activationTtlMs, 3_600_000),
|
|
97
|
-
rescanIntervalMs: nonNegative(next.skills?.rescanIntervalMs, 2000),
|
|
98
|
-
maxResourceFiles: positive(next.skills?.maxResourceFiles, 200),
|
|
99
|
-
};
|
|
100
|
-
next.dataCollection = { enabled: next.dataCollection?.enabled !== false };
|
|
101
|
-
next.mcp = {
|
|
102
|
-
enabled: next.mcp?.enabled !== false,
|
|
103
|
-
maxTools: nonNegative(next.mcp?.maxTools, 48),
|
|
104
|
-
};
|
|
105
|
-
next.debug = Boolean(next.debug);
|
|
60
|
+
if (
|
|
61
|
+
next.skills &&
|
|
62
|
+
typeof next.skills === "object" &&
|
|
63
|
+
Array.isArray(next.skills.extraRoots)
|
|
64
|
+
) {
|
|
65
|
+
next.skills = {
|
|
66
|
+
...next.skills,
|
|
67
|
+
extraRoots: next.skills.extraRoots
|
|
68
|
+
.map((item: unknown) => String(item ?? "").trim())
|
|
69
|
+
.filter(Boolean),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
106
72
|
return next;
|
|
107
73
|
}
|
|
108
74
|
|
|
@@ -127,6 +93,20 @@ export function createAgentRoutes() {
|
|
|
127
93
|
return c.json({ ok: true, data: updateAgentConfig("base.json", body) });
|
|
128
94
|
});
|
|
129
95
|
|
|
96
|
+
// 记忆配置由 agent 插件自己归一化,这里只做读写
|
|
97
|
+
app.get("/memory", (c) =>
|
|
98
|
+
c.json({ ok: true, data: getAgentConfig("memory.json") }),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
app.put("/memory", async (c) => {
|
|
102
|
+
const body = await c.req.json();
|
|
103
|
+
logger.info(`[webui-action] agent.memory.update`, {
|
|
104
|
+
enabled: body?.enabled,
|
|
105
|
+
retentionMonths: body?.retentionMonths,
|
|
106
|
+
});
|
|
107
|
+
return c.json({ ok: true, data: updateAgentConfig("memory.json", body) });
|
|
108
|
+
});
|
|
109
|
+
|
|
130
110
|
app.get("/settings", (c) =>
|
|
131
111
|
c.json({ ok: true, data: getAgentConfig("settings.json") }),
|
|
132
112
|
);
|
package/routes/ai.ts
CHANGED
|
@@ -5,12 +5,6 @@ import { listStickerCatalog } from "../database";
|
|
|
5
5
|
import aiService from "mioku-service-ai";
|
|
6
6
|
import type { AIModelRole, AIProtocol, AIUsageRange } from "mioku";
|
|
7
7
|
|
|
8
|
-
const DEFAULT_MEMORY_GROUP_HISTORY_LIMIT = 300;
|
|
9
|
-
const DEFAULT_MEMORY_USER_HISTORY_LIMIT = 100;
|
|
10
|
-
const DEFAULT_TOPIC_WINDOW_HOURS = 5;
|
|
11
|
-
const DEFAULT_TOPIC_HISTORY_WINDOW_COUNT = 3;
|
|
12
|
-
const DEFAULT_EXPRESSION_LEARN_AFTER_MESSAGES = 100;
|
|
13
|
-
const DEFAULT_EXPRESSION_SAMPLE_SIZE = 8;
|
|
14
8
|
const usageRanges = new Set<AIUsageRange>(["today", "7d", "30d"]);
|
|
15
9
|
const protocols = new Set<AIProtocol>([
|
|
16
10
|
"openai-chat",
|
|
@@ -24,111 +18,50 @@ function getAIApi() {
|
|
|
24
18
|
return aiService?.api as any;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
: DEFAULT_MEMORY_GROUP_HISTORY_LIMIT;
|
|
48
|
-
rawMemory.userHistoryLimit =
|
|
49
|
-
Number.isFinite(userHistoryLimit) && userHistoryLimit > 0
|
|
50
|
-
? Math.floor(userHistoryLimit)
|
|
51
|
-
: DEFAULT_MEMORY_USER_HISTORY_LIMIT;
|
|
52
|
-
rawMemory.enabled =
|
|
53
|
-
typeof rawMemory.enabled === "boolean" ? rawMemory.enabled : true;
|
|
54
|
-
|
|
55
|
-
data.memory = rawMemory;
|
|
56
|
-
|
|
57
|
-
const rawTopic =
|
|
58
|
-
data.topic && typeof data.topic === "object" && !Array.isArray(data.topic)
|
|
59
|
-
? { ...data.topic }
|
|
60
|
-
: {};
|
|
61
|
-
|
|
62
|
-
const windowHours = Number(rawTopic.windowHours);
|
|
63
|
-
const legacyTimeThresholdMs = Number(rawTopic.timeThresholdMs);
|
|
64
|
-
const historyWindowCount = Number(rawTopic.historyWindowCount);
|
|
65
|
-
|
|
66
|
-
rawTopic.enabled =
|
|
67
|
-
typeof rawTopic.enabled === "boolean" ? rawTopic.enabled : true;
|
|
68
|
-
rawTopic.windowHours =
|
|
69
|
-
Number.isFinite(windowHours) && windowHours > 0
|
|
70
|
-
? Math.floor(windowHours)
|
|
71
|
-
: Number.isFinite(legacyTimeThresholdMs) && legacyTimeThresholdMs > 0
|
|
72
|
-
? Math.max(1, Math.floor(legacyTimeThresholdMs / 3600_000))
|
|
73
|
-
: DEFAULT_TOPIC_WINDOW_HOURS;
|
|
74
|
-
rawTopic.historyWindowCount =
|
|
75
|
-
Number.isFinite(historyWindowCount) && historyWindowCount > 0
|
|
76
|
-
? Math.floor(historyWindowCount)
|
|
77
|
-
: DEFAULT_TOPIC_HISTORY_WINDOW_COUNT;
|
|
78
|
-
|
|
79
|
-
delete rawTopic.messageThreshold;
|
|
80
|
-
delete rawTopic.timeThresholdMs;
|
|
81
|
-
delete rawTopic.maxTopicsPerSession;
|
|
82
|
-
|
|
83
|
-
data.topic = rawTopic;
|
|
84
|
-
|
|
85
|
-
const rawExpression =
|
|
21
|
+
function stripDeadPersonalizationKeys(input: any): any {
|
|
22
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return {};
|
|
23
|
+
const data: Record<string, any> = { ...input };
|
|
24
|
+
|
|
25
|
+
delete data.memory;
|
|
26
|
+
delete data.planner;
|
|
27
|
+
|
|
28
|
+
if (
|
|
29
|
+
data.topic &&
|
|
30
|
+
typeof data.topic === "object" &&
|
|
31
|
+
!Array.isArray(data.topic)
|
|
32
|
+
) {
|
|
33
|
+
const topic = { ...data.topic };
|
|
34
|
+
delete topic.timeThresholdMs;
|
|
35
|
+
delete topic.messageThreshold;
|
|
36
|
+
delete topic.maxTopicsPerSession;
|
|
37
|
+
data.topic = topic;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (
|
|
86
41
|
data.expression &&
|
|
87
42
|
typeof data.expression === "object" &&
|
|
88
43
|
!Array.isArray(data.expression)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
typeof
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
data.expression = rawExpression;
|
|
111
|
-
|
|
112
|
-
const rawEmoji =
|
|
113
|
-
data.emoji && typeof data.emoji === "object" && !Array.isArray(data.emoji)
|
|
114
|
-
? { ...data.emoji }
|
|
115
|
-
: {};
|
|
116
|
-
rawEmoji.enabled =
|
|
117
|
-
typeof rawEmoji.enabled === "boolean" ? rawEmoji.enabled : true;
|
|
118
|
-
rawEmoji.characters = Array.isArray(rawEmoji.characters)
|
|
119
|
-
? rawEmoji.characters
|
|
120
|
-
.map(String)
|
|
121
|
-
.map((value: string) => value.trim())
|
|
122
|
-
.filter(Boolean)
|
|
123
|
-
: [];
|
|
124
|
-
rawEmoji.stickers = Array.isArray(rawEmoji.stickers)
|
|
125
|
-
? rawEmoji.stickers
|
|
126
|
-
.map(String)
|
|
127
|
-
.map((value: string) => value.trim())
|
|
128
|
-
.filter(Boolean)
|
|
129
|
-
: [];
|
|
130
|
-
delete rawEmoji.useAISelection;
|
|
131
|
-
data.emoji = rawEmoji;
|
|
44
|
+
) {
|
|
45
|
+
const expression = { ...data.expression };
|
|
46
|
+
delete expression.maxExpressions;
|
|
47
|
+
data.expression = expression;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
data.emoji &&
|
|
52
|
+
typeof data.emoji === "object" &&
|
|
53
|
+
!Array.isArray(data.emoji)
|
|
54
|
+
) {
|
|
55
|
+
const emoji = { ...data.emoji };
|
|
56
|
+
delete emoji.useAISelection;
|
|
57
|
+
for (const key of ["characters", "stickers"] as const) {
|
|
58
|
+
if (!Array.isArray(emoji[key])) continue;
|
|
59
|
+
emoji[key] = emoji[key]
|
|
60
|
+
.map((item: unknown) => String(item ?? "").trim())
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
data.emoji = emoji;
|
|
64
|
+
}
|
|
132
65
|
|
|
133
66
|
return data;
|
|
134
67
|
}
|
|
@@ -181,13 +114,13 @@ export function createAIRoutes() {
|
|
|
181
114
|
});
|
|
182
115
|
|
|
183
116
|
app.get("/personalization", (c) => {
|
|
184
|
-
const data =
|
|
117
|
+
const data = stripDeadPersonalizationKeys(
|
|
185
118
|
getChatConfig("personalization.json"),
|
|
186
119
|
);
|
|
187
120
|
return c.json({ ok: true, data });
|
|
188
121
|
});
|
|
189
122
|
app.put("/personalization", async (c) => {
|
|
190
|
-
const body =
|
|
123
|
+
const body = stripDeadPersonalizationKeys(await c.req.json());
|
|
191
124
|
logger.info(`[webui-action] ai.personalization.update`);
|
|
192
125
|
return c.json({
|
|
193
126
|
ok: true,
|
|
@@ -195,6 +128,19 @@ export function createAIRoutes() {
|
|
|
195
128
|
});
|
|
196
129
|
});
|
|
197
130
|
|
|
131
|
+
// 记忆配置由 chat 插件自己归一化(缺字段/坏值在读取时兜底),这里只做读写
|
|
132
|
+
app.get("/memory", (c) =>
|
|
133
|
+
c.json({ ok: true, data: getChatConfig("memory.json") }),
|
|
134
|
+
);
|
|
135
|
+
app.put("/memory", async (c) => {
|
|
136
|
+
const body = await c.req.json();
|
|
137
|
+
logger.info(`[webui-action] ai.memory.update`, {
|
|
138
|
+
enabled: body?.enabled,
|
|
139
|
+
retentionMonths: body?.retentionMonths,
|
|
140
|
+
});
|
|
141
|
+
return c.json({ ok: true, data: updateChatConfig("memory.json", body) });
|
|
142
|
+
});
|
|
143
|
+
|
|
198
144
|
app.get("/settings", (c) =>
|
|
199
145
|
c.json({ ok: true, data: getChatConfig("settings.json") }),
|
|
200
146
|
);
|
|
@@ -405,7 +351,9 @@ export function createAIRoutes() {
|
|
|
405
351
|
.filter((id: string) => id.includes("/") && id.length > 1),
|
|
406
352
|
),
|
|
407
353
|
);
|
|
408
|
-
logger.info(`[webui-action] ai.fallback.update`, {
|
|
354
|
+
logger.info(`[webui-action] ai.fallback.update`, {
|
|
355
|
+
count: fallback.length,
|
|
356
|
+
});
|
|
409
357
|
api.setMainFallbackChain(fallback);
|
|
410
358
|
const live = api.getMainFallbackChain?.() ?? fallback;
|
|
411
359
|
return c.json({ ok: true, data: { fallback: live, liveFallback: live } });
|
|
@@ -496,9 +444,7 @@ export function createAIRoutes() {
|
|
|
496
444
|
});
|
|
497
445
|
});
|
|
498
446
|
|
|
499
|
-
app.get("/stickers", (c) =>
|
|
500
|
-
c.json({ ok: true, data: listStickerCatalog() }),
|
|
501
|
-
);
|
|
447
|
+
app.get("/stickers", (c) => c.json({ ok: true, data: listStickerCatalog() }));
|
|
502
448
|
|
|
503
449
|
app.get("/usage", (c) => {
|
|
504
450
|
const api = getAIApi();
|
|
@@ -512,8 +458,7 @@ export function createAIRoutes() {
|
|
|
512
458
|
? (rawRange as AIUsageRange)
|
|
513
459
|
: "today";
|
|
514
460
|
const rawBotId = c.req.query("botId");
|
|
515
|
-
const botId =
|
|
516
|
-
rawBotId && rawBotId !== "all" ? Number(rawBotId) : undefined;
|
|
461
|
+
const botId = rawBotId && rawBotId !== "all" ? Number(rawBotId) : undefined;
|
|
517
462
|
|
|
518
463
|
if (botId !== undefined && !Number.isFinite(botId)) {
|
|
519
464
|
return c.json({ ok: false, error: "INVALID_BOT_ID" }, 400);
|
package/system.ts
CHANGED
|
@@ -4,7 +4,13 @@ import * as path from "node:path";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import AdmZip from "adm-zip";
|
|
6
6
|
import systemInfo from "systeminformation";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
buildAdapterReport,
|
|
9
|
+
connectedBots,
|
|
10
|
+
getConfigDefaults,
|
|
11
|
+
logger,
|
|
12
|
+
withConfigDefaults,
|
|
13
|
+
} from "mioku";
|
|
8
14
|
import type { AdapterInstanceStatus, Bot } from "mioku";
|
|
9
15
|
import type {
|
|
10
16
|
InstallRequest,
|
|
@@ -2389,9 +2395,21 @@ export async function getSaying(): Promise<{ text: string }> {
|
|
|
2389
2395
|
return fetchYiyan();
|
|
2390
2396
|
}
|
|
2391
2397
|
|
|
2398
|
+
/**
|
|
2399
|
+
* 读插件配置:磁盘值覆盖默认值。
|
|
2400
|
+
*
|
|
2401
|
+
* 默认值只由插件在 `registerConfig` 时登记一次(见 mioku 的 config-defaults),
|
|
2402
|
+
* WebUI 这边不再手抄字段——以前每加一个配置项,要在这里和插件各改一遍。
|
|
2403
|
+
*/
|
|
2404
|
+
function readPluginConfig(dir: string, owner: string, fileName: string): any {
|
|
2405
|
+
const stored = readJsonFile(path.join(dir, fileName), {});
|
|
2406
|
+
const name = fileName.replace(/\.json$/, "");
|
|
2407
|
+
const defaults = getConfigDefaults("plugin", owner, name);
|
|
2408
|
+
return withConfigDefaults(stored, defaults);
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2392
2411
|
export function getChatConfig(fileName: string): any {
|
|
2393
|
-
|
|
2394
|
-
return readJsonFile(filePath, {});
|
|
2412
|
+
return readPluginConfig(CHAT_CONFIG_DIR, "chat", fileName);
|
|
2395
2413
|
}
|
|
2396
2414
|
|
|
2397
2415
|
export function updateChatConfig(fileName: string, data: any): any {
|
|
@@ -2401,8 +2419,7 @@ export function updateChatConfig(fileName: string, data: any): any {
|
|
|
2401
2419
|
}
|
|
2402
2420
|
|
|
2403
2421
|
export function getAgentConfig(fileName: string): any {
|
|
2404
|
-
|
|
2405
|
-
return readJsonFile(filePath, {});
|
|
2422
|
+
return readPluginConfig(AGENT_CONFIG_DIR, "agent", fileName);
|
|
2406
2423
|
}
|
|
2407
2424
|
|
|
2408
2425
|
export function updateAgentConfig(fileName: string, data: any): any {
|