mioku-service-webui 4.8.1 → 4.10.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/index.ts +2 -0
- package/package.json +1 -1
- package/routes/agent.ts +132 -0
- package/routes/index.ts +1 -0
- package/system.ts +267 -142
- package/utils.ts +1 -0
package/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
createConfigRoutes,
|
|
27
27
|
createWebUISettingsRoutes,
|
|
28
28
|
createAIRoutes,
|
|
29
|
+
createAgentRoutes,
|
|
29
30
|
createDBRoutes,
|
|
30
31
|
createPluginConfigRoutes,
|
|
31
32
|
createServiceConfigRoutes,
|
|
@@ -120,6 +121,7 @@ class WebUIRuntime {
|
|
|
120
121
|
this.app.route("/api/config", createConfigRoutes());
|
|
121
122
|
this.app.route("/api/settings", createWebUISettingsRoutes());
|
|
122
123
|
this.app.route("/api/ai", createAIRoutes());
|
|
124
|
+
this.app.route("/api/agent", createAgentRoutes());
|
|
123
125
|
this.app.route("/api/manage", createManageRoutes());
|
|
124
126
|
this.app.route("/api/store", createStoreRoutes());
|
|
125
127
|
this.app.route("/api/db", createDBRoutes());
|
package/package.json
CHANGED
package/routes/agent.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { logger } from "mioku";
|
|
3
|
+
import { getAgentConfig, updateAgentConfig } from "../system";
|
|
4
|
+
import aiService from "mioku-service-ai";
|
|
5
|
+
|
|
6
|
+
const PERMISSION_LEVELS = new Set([
|
|
7
|
+
"read-only",
|
|
8
|
+
"workspace-write",
|
|
9
|
+
"auto",
|
|
10
|
+
"full",
|
|
11
|
+
"yolo",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
function normalizeIdList(input: unknown): number[] {
|
|
15
|
+
if (!Array.isArray(input)) return [];
|
|
16
|
+
return Array.from(
|
|
17
|
+
new Set(
|
|
18
|
+
input
|
|
19
|
+
.map((item) => Math.floor(Number(item)))
|
|
20
|
+
.filter((id: number) => Number.isFinite(id) && id > 0),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sanitizeAccess(input: any): any {
|
|
26
|
+
const access = input && typeof input === "object" ? input : {};
|
|
27
|
+
return {
|
|
28
|
+
allowAdmins: Boolean(access.allowAdmins),
|
|
29
|
+
users: normalizeIdList(access.users),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sanitizeBaseForSave(body: any): any {
|
|
34
|
+
if (!body || typeof body !== "object") return body;
|
|
35
|
+
const next = { ...body };
|
|
36
|
+
delete next.persona;
|
|
37
|
+
delete next.roleBindings;
|
|
38
|
+
next.access = sanitizeAccess(next.access);
|
|
39
|
+
next.workspaceDir = String(next.workspaceDir ?? "").trim();
|
|
40
|
+
next.permissionLevel = PERMISSION_LEVELS.has(next.permissionLevel)
|
|
41
|
+
? next.permissionLevel
|
|
42
|
+
: "workspace-write";
|
|
43
|
+
next.model = String(next.model ?? "").trim();
|
|
44
|
+
return next;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sanitizeSettingsForSave(body: any): any {
|
|
48
|
+
if (!body || typeof body !== "object") return body;
|
|
49
|
+
const next = { ...body };
|
|
50
|
+
const positive = (value: unknown, fallback: number) => {
|
|
51
|
+
const num = Math.floor(Number(value));
|
|
52
|
+
return Number.isFinite(num) && num > 0 ? num : fallback;
|
|
53
|
+
};
|
|
54
|
+
delete next.temperature;
|
|
55
|
+
delete next.enableMediaRecognition;
|
|
56
|
+
next.maxIterations = positive(next.maxIterations, 500);
|
|
57
|
+
next.maxContextTokens = positive(next.maxContextTokens, 524288);
|
|
58
|
+
next.stream = next.stream !== false;
|
|
59
|
+
next.enableMarkdownScreenshot = next.enableMarkdownScreenshot !== false;
|
|
60
|
+
next.compaction = {
|
|
61
|
+
enabled: next.compaction?.enabled !== false,
|
|
62
|
+
keepRecentMessages: positive(next.compaction?.keepRecentMessages, 20),
|
|
63
|
+
};
|
|
64
|
+
next.webSearch = {
|
|
65
|
+
enabled: next.webSearch?.enabled !== false,
|
|
66
|
+
baseUrl: String(next.webSearch?.baseUrl ?? "").trim(),
|
|
67
|
+
timeoutMs: positive(next.webSearch?.timeoutMs, 8000),
|
|
68
|
+
defaultLimit: positive(next.webSearch?.defaultLimit, 5),
|
|
69
|
+
maxLimit: positive(next.webSearch?.maxLimit, 8),
|
|
70
|
+
maxSearchCount: positive(next.webSearch?.maxSearchCount, 50),
|
|
71
|
+
};
|
|
72
|
+
next.webFetch = {
|
|
73
|
+
enabled: next.webFetch?.enabled !== false,
|
|
74
|
+
timeoutMs: positive(next.webFetch?.timeoutMs, 15000),
|
|
75
|
+
maxChars: positive(next.webFetch?.maxChars, 12000),
|
|
76
|
+
};
|
|
77
|
+
next.bash = {
|
|
78
|
+
enabled: next.bash?.enabled !== false,
|
|
79
|
+
timeoutMs: positive(next.bash?.timeoutMs, 120000),
|
|
80
|
+
approvalTimeoutMs: positive(next.bash?.approvalTimeoutMs, 300000),
|
|
81
|
+
};
|
|
82
|
+
next.dataCollection = { enabled: next.dataCollection?.enabled !== false };
|
|
83
|
+
next.debug = Boolean(next.debug);
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getAIApi() {
|
|
88
|
+
return aiService?.api as any;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createAgentRoutes() {
|
|
92
|
+
const app = new Hono();
|
|
93
|
+
|
|
94
|
+
app.get("/base", (c) =>
|
|
95
|
+
c.json({ ok: true, data: getAgentConfig("base.json") }),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
app.put("/base", async (c) => {
|
|
99
|
+
const body = sanitizeBaseForSave(await c.req.json());
|
|
100
|
+
logger.info(`[webui-action] agent.base.update`, {
|
|
101
|
+
permissionLevel: body?.permissionLevel,
|
|
102
|
+
});
|
|
103
|
+
return c.json({ ok: true, data: updateAgentConfig("base.json", body) });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
app.get("/settings", (c) =>
|
|
107
|
+
c.json({ ok: true, data: getAgentConfig("settings.json") }),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
app.put("/settings", async (c) => {
|
|
111
|
+
const body = sanitizeSettingsForSave(await c.req.json());
|
|
112
|
+
logger.info(`[webui-action] agent.settings.update`);
|
|
113
|
+
return c.json({ ok: true, data: updateAgentConfig("settings.json", body) });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
app.get("/status", (c) => {
|
|
117
|
+
const api = getAIApi();
|
|
118
|
+
const bindings = api?.getRoleBindings?.() ?? {};
|
|
119
|
+
const base = getAgentConfig("base.json");
|
|
120
|
+
return c.json({
|
|
121
|
+
ok: true,
|
|
122
|
+
data: {
|
|
123
|
+
aiConfigured: Boolean(bindings.main),
|
|
124
|
+
mainModel: bindings.main ?? "",
|
|
125
|
+
permissionLevel: base?.permissionLevel ?? "workspace-write",
|
|
126
|
+
workspaceDir: base?.workspaceDir ?? "",
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return app;
|
|
132
|
+
}
|
package/routes/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createConfigRoutes } from "./config";
|
|
2
2
|
export { createWebUISettingsRoutes } from "./webui";
|
|
3
3
|
export { createAIRoutes } from "./ai";
|
|
4
|
+
export { createAgentRoutes } from "./agent";
|
|
4
5
|
export { createDBRoutes, createPluginConfigRoutes, createMemeRoutes, createServiceConfigRoutes } from "./db";
|
|
5
6
|
export { createAdapterConfigRoutes } from "./adapter-config";
|
|
6
7
|
export { createManageRoutes } from "./manage";
|
package/system.ts
CHANGED
|
@@ -4,8 +4,8 @@ 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 {connectedBots,
|
|
8
|
-
|
|
7
|
+
import { buildAdapterReport, connectedBots, logger } from "mioku";
|
|
8
|
+
import type { AdapterInstanceStatus, Bot } from "mioku";
|
|
9
9
|
import type {
|
|
10
10
|
InstallRequest,
|
|
11
11
|
ManagedTarget,
|
|
@@ -16,7 +16,9 @@ import type {
|
|
|
16
16
|
} from "./types";
|
|
17
17
|
import {
|
|
18
18
|
AUTH_PATH,
|
|
19
|
+
AGENT_CONFIG_DIR,
|
|
19
20
|
CHAT_CONFIG_DIR,
|
|
21
|
+
CONFIG_DIR,
|
|
20
22
|
defaultWebUISettings,
|
|
21
23
|
ensureDir,
|
|
22
24
|
getInstallCommand,
|
|
@@ -641,6 +643,11 @@ interface ManagedPackageUpdateInfo {
|
|
|
641
643
|
error?: string;
|
|
642
644
|
}
|
|
643
645
|
|
|
646
|
+
interface NpmPackageUpdateInfo extends ManagedPackageUpdateInfo {
|
|
647
|
+
currentVersion: string;
|
|
648
|
+
latestVersion: string;
|
|
649
|
+
}
|
|
650
|
+
|
|
644
651
|
interface ManagedPackageUpdateCacheEntry {
|
|
645
652
|
checkedAt: number;
|
|
646
653
|
info: ManagedPackageUpdateInfo;
|
|
@@ -728,6 +735,65 @@ function isManagedUpdateCacheFresh(
|
|
|
728
735
|
return Date.now() - entry.checkedAt < MANAGED_UPDATE_CACHE_TTL_MS;
|
|
729
736
|
}
|
|
730
737
|
|
|
738
|
+
/** 本地已安装版本:node_modules/<fullName>/package.json */
|
|
739
|
+
function readLocalPackageVersion(fullName: string): string {
|
|
740
|
+
const pkg = readPackageJson(path.join(NODE_MODULES_DIR, fullName));
|
|
741
|
+
return pkg?.version || "";
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
async function getNpmLatestVersion(fullName: string): Promise<string> {
|
|
745
|
+
try {
|
|
746
|
+
const data = await fetchJson(
|
|
747
|
+
`https://registry.npmjs.org/${encodeURIComponent(fullName)}`,
|
|
748
|
+
);
|
|
749
|
+
return String(data?.["dist-tags"]?.latest || "").trim();
|
|
750
|
+
} catch {
|
|
751
|
+
return "";
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function npmManagedUpdateError(
|
|
756
|
+
fullName: string,
|
|
757
|
+
error: string,
|
|
758
|
+
): NpmPackageUpdateInfo {
|
|
759
|
+
return {
|
|
760
|
+
state: "unknown",
|
|
761
|
+
hasUpdates: false,
|
|
762
|
+
behind: 0,
|
|
763
|
+
changelog: [],
|
|
764
|
+
error,
|
|
765
|
+
currentVersion: readLocalPackageVersion(fullName),
|
|
766
|
+
latestVersion: "",
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async function checkNpmManagedUpdate(
|
|
771
|
+
fullName: string,
|
|
772
|
+
): Promise<NpmPackageUpdateInfo> {
|
|
773
|
+
const current = readLocalPackageVersion(fullName);
|
|
774
|
+
let latest = "";
|
|
775
|
+
try {
|
|
776
|
+
latest = await getNpmLatestVersion(fullName);
|
|
777
|
+
} catch (error: any) {
|
|
778
|
+
return npmManagedUpdateError(
|
|
779
|
+
fullName,
|
|
780
|
+
error?.message || "UPDATE_CHECK_FAILED",
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
if (!latest) {
|
|
784
|
+
return npmManagedUpdateError(fullName, "无法获取 npm 最新版本");
|
|
785
|
+
}
|
|
786
|
+
const hasUpdates = Boolean(current) && current !== latest;
|
|
787
|
+
return {
|
|
788
|
+
state: hasUpdates ? "has-updates" : "up-to-date",
|
|
789
|
+
hasUpdates,
|
|
790
|
+
behind: hasUpdates ? 1 : 0,
|
|
791
|
+
changelog: [],
|
|
792
|
+
currentVersion: current || "0.0.0",
|
|
793
|
+
latestVersion: latest,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
731
797
|
async function refreshManagedUpdatesInBackground(
|
|
732
798
|
target: ManagedTarget,
|
|
733
799
|
packages: Array<Record<string, any>>,
|
|
@@ -736,21 +802,9 @@ async function refreshManagedUpdatesInBackground(
|
|
|
736
802
|
const name = String(item.name || "");
|
|
737
803
|
if (!name) continue;
|
|
738
804
|
|
|
739
|
-
|
|
740
|
-
setCachedManagedUpdateInfo(target, name, {
|
|
741
|
-
state: "no-git",
|
|
742
|
-
hasUpdates: false,
|
|
743
|
-
behind: 0,
|
|
744
|
-
changelog: [],
|
|
745
|
-
error: "NOT_GIT_REPO",
|
|
746
|
-
});
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
|
|
805
|
+
const fullName = fullPackageName(target, name);
|
|
750
806
|
try {
|
|
751
|
-
const updateInfo = await
|
|
752
|
-
String(item.path || ""),
|
|
753
|
-
);
|
|
807
|
+
const updateInfo = await checkNpmManagedUpdate(fullName);
|
|
754
808
|
setCachedManagedUpdateInfo(target, name, updateInfo);
|
|
755
809
|
} catch (error: any) {
|
|
756
810
|
setCachedManagedUpdateInfo(target, name, {
|
|
@@ -770,8 +824,8 @@ function scheduleManagedUpdatesRefresh(
|
|
|
770
824
|
): void {
|
|
771
825
|
if (managedOverviewRefreshInFlight.has(target)) return;
|
|
772
826
|
|
|
827
|
+
// npm 包与 git 包统一走 registry 更新检查(已不再使用 git 仓库形式)
|
|
773
828
|
const shouldRefresh = packages.some((item) => {
|
|
774
|
-
if (!item.hasGit) return false;
|
|
775
829
|
const cached = getCachedManagedUpdateInfo(target, String(item.name || ""));
|
|
776
830
|
return !isManagedUpdateCacheFresh(cached);
|
|
777
831
|
});
|
|
@@ -1002,10 +1056,8 @@ export async function installManagedPackage(
|
|
|
1002
1056
|
...Object.keys(afterPkg?.devDependencies || {}),
|
|
1003
1057
|
];
|
|
1004
1058
|
const installedName =
|
|
1005
|
-
afterDeps.find(
|
|
1006
|
-
|
|
1007
|
-
!beforeDeps.has(name) && isNpmManagedName(name),
|
|
1008
|
-
) || pkgName;
|
|
1059
|
+
afterDeps.find((name) => !beforeDeps.has(name) && isNpmManagedName(name)) ||
|
|
1060
|
+
pkgName;
|
|
1009
1061
|
|
|
1010
1062
|
const packageJsonPath = path.join(
|
|
1011
1063
|
NODE_MODULES_DIR,
|
|
@@ -1039,34 +1091,25 @@ export async function checkUpdate(
|
|
|
1039
1091
|
name: string,
|
|
1040
1092
|
target: ManagedTarget,
|
|
1041
1093
|
): Promise<Record<string, any>> {
|
|
1042
|
-
|
|
1094
|
+
// 现在插件/服务/适配器全部以 npm 包形式安装,统一走 npm 更新检查
|
|
1095
|
+
const fullName = isNpmManagedName(name)
|
|
1096
|
+
? name
|
|
1097
|
+
: fullPackageName(target, name);
|
|
1098
|
+
const localPath = path.join(NODE_MODULES_DIR, fullName);
|
|
1099
|
+
const isNpmPackage = fs.existsSync(localPath) || isNpmManagedName(name);
|
|
1043
1100
|
|
|
1044
1101
|
if (isNpmPackage) {
|
|
1045
|
-
|
|
1046
|
-
const currentVersion = await runCommand(
|
|
1047
|
-
"bun",
|
|
1048
|
-
["npm", "info", name, "version"],
|
|
1049
|
-
process.cwd(),
|
|
1050
|
-
);
|
|
1051
|
-
const latestVersion = await runCommand(
|
|
1052
|
-
"bun",
|
|
1053
|
-
["npm", "info", name, "dist-tags.latest"],
|
|
1054
|
-
process.cwd(),
|
|
1055
|
-
);
|
|
1056
|
-
const current = currentVersion.stdout?.trim() || "";
|
|
1057
|
-
const latest = latestVersion.stdout?.trim() || "";
|
|
1058
|
-
const hasUpdates = current !== latest && latest !== "";
|
|
1059
|
-
|
|
1102
|
+
const info = await checkNpmManagedUpdate(fullName);
|
|
1060
1103
|
return {
|
|
1061
1104
|
ok: true,
|
|
1062
|
-
state:
|
|
1063
|
-
hasUpdates,
|
|
1064
|
-
behind:
|
|
1065
|
-
changelog:
|
|
1105
|
+
state: info.state,
|
|
1106
|
+
hasUpdates: info.hasUpdates,
|
|
1107
|
+
behind: info.behind,
|
|
1108
|
+
changelog: info.changelog,
|
|
1066
1109
|
hasGit: false,
|
|
1067
|
-
error: null,
|
|
1068
|
-
currentVersion:
|
|
1069
|
-
latestVersion:
|
|
1110
|
+
error: info.error || null,
|
|
1111
|
+
currentVersion: info.currentVersion,
|
|
1112
|
+
latestVersion: info.latestVersion,
|
|
1070
1113
|
};
|
|
1071
1114
|
}
|
|
1072
1115
|
|
|
@@ -1093,28 +1136,30 @@ export async function updateManagedPackage(
|
|
|
1093
1136
|
): Promise<Record<string, any>> {
|
|
1094
1137
|
const installCmd = getInstallCommand();
|
|
1095
1138
|
|
|
1096
|
-
//
|
|
1097
|
-
const
|
|
1139
|
+
// 前端传短名(如 chat),统一还原为完整 npm 包名
|
|
1140
|
+
const fullName = isNpmManagedName(input.name)
|
|
1141
|
+
? input.name
|
|
1142
|
+
: fullPackageName(input.target, input.name);
|
|
1098
1143
|
const npmPackagePath = path.join(
|
|
1099
1144
|
process.cwd(),
|
|
1100
1145
|
"node_modules",
|
|
1101
|
-
|
|
1146
|
+
fullName,
|
|
1102
1147
|
"package.json",
|
|
1103
1148
|
);
|
|
1104
1149
|
|
|
1105
|
-
if (
|
|
1106
|
-
// Bun
|
|
1107
|
-
const updateArgs = installCmd.args.map((arg) =>
|
|
1108
|
-
arg === "install" ? "update" : arg,
|
|
1109
|
-
);
|
|
1150
|
+
if (fs.existsSync(npmPackagePath)) {
|
|
1151
|
+
// Bun 包更新:bun add 会拉取 npm 最新版本
|
|
1110
1152
|
const update = await runCommand(
|
|
1111
1153
|
installCmd.cmd,
|
|
1112
|
-
[...
|
|
1154
|
+
[...installCmd.args, fullName],
|
|
1113
1155
|
process.cwd(),
|
|
1114
1156
|
);
|
|
1115
1157
|
if (update.code !== 0) {
|
|
1116
|
-
throw new Error(`bun
|
|
1158
|
+
throw new Error(`bun add 更新失败: ${update.stderr || update.stdout}`);
|
|
1117
1159
|
}
|
|
1160
|
+
managedPackageUpdateCache.delete(
|
|
1161
|
+
makeManagedUpdateCacheKey(input.target, fullName),
|
|
1162
|
+
);
|
|
1118
1163
|
return {
|
|
1119
1164
|
ok: true,
|
|
1120
1165
|
restartRequired: true,
|
|
@@ -1173,9 +1218,7 @@ export async function removeManagedPackage(
|
|
|
1173
1218
|
: fullPackageName(input.target, input.name);
|
|
1174
1219
|
|
|
1175
1220
|
const installCmd = getInstallCommand();
|
|
1176
|
-
const removeArgs = installCmd.args.map((a) =>
|
|
1177
|
-
a === "add" ? "remove" : a,
|
|
1178
|
-
);
|
|
1221
|
+
const removeArgs = installCmd.args.map((a) => (a === "add" ? "remove" : a));
|
|
1179
1222
|
|
|
1180
1223
|
const result = await runCommand(
|
|
1181
1224
|
installCmd.cmd,
|
|
@@ -1194,10 +1237,15 @@ export async function removeManagedPackage(
|
|
|
1194
1237
|
try {
|
|
1195
1238
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
1196
1239
|
const mioki = pkg.mioki ?? {};
|
|
1197
|
-
const plugins: string[] = Array.isArray(mioki.plugins)
|
|
1240
|
+
const plugins: string[] = Array.isArray(mioki.plugins)
|
|
1241
|
+
? mioki.plugins
|
|
1242
|
+
: [];
|
|
1198
1243
|
const shortName = fullName.replace(/^mioku-plugin-/, "");
|
|
1199
1244
|
if (plugins.includes(shortName)) {
|
|
1200
|
-
pkg.mioki = {
|
|
1245
|
+
pkg.mioki = {
|
|
1246
|
+
...mioki,
|
|
1247
|
+
plugins: plugins.filter((p: string) => p !== shortName),
|
|
1248
|
+
};
|
|
1201
1249
|
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
1202
1250
|
}
|
|
1203
1251
|
} catch {
|
|
@@ -1226,17 +1274,6 @@ export async function listManagedPackagesWithUpdates(
|
|
|
1226
1274
|
return packages.map((item) => {
|
|
1227
1275
|
const name = String(item.name || "");
|
|
1228
1276
|
|
|
1229
|
-
if (!item.hasGit) {
|
|
1230
|
-
return {
|
|
1231
|
-
...item,
|
|
1232
|
-
updateState: "no-git",
|
|
1233
|
-
hasUpdates: false,
|
|
1234
|
-
behind: 0,
|
|
1235
|
-
updateError: "NOT_GIT_REPO",
|
|
1236
|
-
updateChecking: false,
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
1277
|
const cached = getCachedManagedUpdateInfo(target, name);
|
|
1241
1278
|
if (cached) {
|
|
1242
1279
|
return {
|
|
@@ -1300,6 +1337,9 @@ export async function getManagedPackageDetail(
|
|
|
1300
1337
|
} else {
|
|
1301
1338
|
repositoryUrl = normalizeRepositoryUrl(latest?.repository);
|
|
1302
1339
|
}
|
|
1340
|
+
// npm 包以包内 repository 字段为准,缺失时回退到 mioku-lab 仓库
|
|
1341
|
+
const packageRepository = normalizeRepositoryUrl(latest?.repository);
|
|
1342
|
+
const finalRepository = packageRepository || repositoryUrl;
|
|
1303
1343
|
|
|
1304
1344
|
// 获取本地已安装版本
|
|
1305
1345
|
const localPkg =
|
|
@@ -1325,7 +1365,7 @@ export async function getManagedPackageDetail(
|
|
|
1325
1365
|
hasGit: false,
|
|
1326
1366
|
isSystemPlugin,
|
|
1327
1367
|
isSystemService,
|
|
1328
|
-
repository:
|
|
1368
|
+
repository: finalRepository,
|
|
1329
1369
|
originUrl: "",
|
|
1330
1370
|
homepage: String(latest?.homepage || "").trim(),
|
|
1331
1371
|
requiredServices: Array.isArray(latest?.mioku?.services)
|
|
@@ -1335,7 +1375,7 @@ export async function getManagedPackageDetail(
|
|
|
1335
1375
|
help: latest?.mioku?.help || null,
|
|
1336
1376
|
readme: String(data?.readme || "").trim(),
|
|
1337
1377
|
readmeFile: "README.md",
|
|
1338
|
-
updateState: "
|
|
1378
|
+
updateState: hasUpdates ? "has-updates" : "up-to-date",
|
|
1339
1379
|
hasUpdates,
|
|
1340
1380
|
behind: hasUpdates ? 1 : 0,
|
|
1341
1381
|
changelog: [],
|
|
@@ -1445,19 +1485,24 @@ export async function updateAllManagedPackages(input: {
|
|
|
1445
1485
|
const results: Array<Record<string, any>> = [];
|
|
1446
1486
|
|
|
1447
1487
|
for (const item of packages) {
|
|
1448
|
-
const
|
|
1449
|
-
if (
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1488
|
+
const name = String(item.name || "");
|
|
1489
|
+
if (!name) continue;
|
|
1490
|
+
|
|
1491
|
+
// 统一走 npm 更新检查(已不再使用 git 仓库形式)
|
|
1492
|
+
const cached = getCachedManagedUpdateInfo(input.target, name);
|
|
1493
|
+
let updateInfo: ManagedPackageUpdateInfo;
|
|
1494
|
+
if (cached && isManagedUpdateCacheFresh(cached)) {
|
|
1495
|
+
updateInfo = cached.info;
|
|
1496
|
+
} else {
|
|
1497
|
+
updateInfo = await checkNpmManagedUpdate(
|
|
1498
|
+
fullPackageName(input.target, name),
|
|
1499
|
+
);
|
|
1500
|
+
setCachedManagedUpdateInfo(input.target, name, updateInfo);
|
|
1457
1501
|
}
|
|
1502
|
+
|
|
1458
1503
|
if (!updateInfo.hasUpdates) {
|
|
1459
1504
|
results.push({
|
|
1460
|
-
name
|
|
1505
|
+
name,
|
|
1461
1506
|
ok: true,
|
|
1462
1507
|
skipped: true,
|
|
1463
1508
|
reason: updateInfo.state === "unknown" ? "CHECK_FAILED" : "UP_TO_DATE",
|
|
@@ -1468,19 +1513,19 @@ export async function updateAllManagedPackages(input: {
|
|
|
1468
1513
|
|
|
1469
1514
|
try {
|
|
1470
1515
|
const updated = await updateManagedPackage({
|
|
1471
|
-
name
|
|
1516
|
+
name,
|
|
1472
1517
|
target: input.target,
|
|
1473
1518
|
packageManager: input.packageManager,
|
|
1474
1519
|
});
|
|
1475
1520
|
results.push({
|
|
1476
|
-
name
|
|
1521
|
+
name,
|
|
1477
1522
|
ok: true,
|
|
1478
1523
|
skipped: false,
|
|
1479
1524
|
...updated,
|
|
1480
1525
|
});
|
|
1481
1526
|
} catch (error: any) {
|
|
1482
1527
|
results.push({
|
|
1483
|
-
name
|
|
1528
|
+
name,
|
|
1484
1529
|
ok: false,
|
|
1485
1530
|
skipped: false,
|
|
1486
1531
|
error: error?.message || "UPDATE_FAILED",
|
|
@@ -1828,10 +1873,7 @@ export async function notifyOwnersAuthTokenRefreshed(
|
|
|
1828
1873
|
continue;
|
|
1829
1874
|
}
|
|
1830
1875
|
try {
|
|
1831
|
-
await bot.sendMessage(
|
|
1832
|
-
{ type: "private", user_id: ownerId },
|
|
1833
|
-
message,
|
|
1834
|
-
);
|
|
1876
|
+
await bot.sendMessage({ type: "private", user_id: ownerId }, message);
|
|
1835
1877
|
logger.info(
|
|
1836
1878
|
`[webui] 已通过 ${bot.adapter ?? "bot"}/${bot.bot_id ?? "?"} 通知主人 ${ownerId} 密钥更新`,
|
|
1837
1879
|
);
|
|
@@ -1843,9 +1885,7 @@ export async function notifyOwnersAuthTokenRefreshed(
|
|
|
1843
1885
|
}
|
|
1844
1886
|
if (!notified) {
|
|
1845
1887
|
const msg = formatSendError(lastError);
|
|
1846
|
-
logger.warn(
|
|
1847
|
-
`[webui] 通知主人 ${ownerId} 密钥更新失败: ${msg}`,
|
|
1848
|
-
);
|
|
1888
|
+
logger.warn(`[webui] 通知主人 ${ownerId} 密钥更新失败: ${msg}`);
|
|
1849
1889
|
}
|
|
1850
1890
|
}
|
|
1851
1891
|
}
|
|
@@ -2108,14 +2148,6 @@ async function getCpuUsagePercent(): Promise<number> {
|
|
|
2108
2148
|
return Number((((totalDelta - idleDelta) / totalDelta) * 100).toFixed(1));
|
|
2109
2149
|
}
|
|
2110
2150
|
|
|
2111
|
-
function toHttpBaseUrl(bot: { options?: { protocol?: string; host?: string; port?: number } }): string {
|
|
2112
|
-
const protocol = String(bot.options?.protocol || "ws");
|
|
2113
|
-
const httpProtocol = protocol === "wss" ? "https" : "http";
|
|
2114
|
-
const host = bot.options?.host || "127.0.0.1";
|
|
2115
|
-
const port = bot.options?.port || 3001;
|
|
2116
|
-
return `${httpProtocol}://${host}:${port}`;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
2151
|
async function fetchYiyan(): Promise<{ text: string }> {
|
|
2120
2152
|
const controller = new AbortController();
|
|
2121
2153
|
const timer = setTimeout(() => controller.abort(), 4500);
|
|
@@ -2143,61 +2175,138 @@ function normalizeVersionSpec(input: string): string {
|
|
|
2143
2175
|
return matched?.[0] || cleaned;
|
|
2144
2176
|
}
|
|
2145
2177
|
|
|
2146
|
-
|
|
2178
|
+
/** 读取已安装适配器包的版本(与 help 状态图里 ctx.getAdapter().version 同源) */
|
|
2179
|
+
function readAdapterPackageVersion(name: string): string {
|
|
2180
|
+
const pkg = readPackageJson(
|
|
2181
|
+
path.join(NODE_MODULES_DIR, `mioku-adapter-${name}`),
|
|
2182
|
+
);
|
|
2183
|
+
return pkg?.version || "";
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
/** 与 help 状态图一致:stdin 使用 help/status 配置的随机头像,未配置时用默认随机图接口 */
|
|
2187
|
+
function readStdinAvatar(): string {
|
|
2188
|
+
const statusConfig = readJsonFile<any>(
|
|
2189
|
+
path.join(CONFIG_DIR, "help", "status.json"),
|
|
2190
|
+
null,
|
|
2191
|
+
);
|
|
2192
|
+
const configured = String(statusConfig?.stdinAvatar || "").trim();
|
|
2193
|
+
if (configured) return configured;
|
|
2194
|
+
return "https://uapis.cn/api/v1/random/image?category=bq&type=eciyuan";
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
async function getBotDetails(bot: Bot): Promise<Record<string, any>> {
|
|
2147
2198
|
const botAny = bot as unknown as {
|
|
2148
2199
|
uin?: number | string;
|
|
2149
2200
|
user_id?: number | string;
|
|
2150
2201
|
name?: string;
|
|
2151
|
-
options?: { protocol?: string; host?: string; port?: number };
|
|
2152
|
-
app_version?: string;
|
|
2153
2202
|
};
|
|
2154
|
-
const
|
|
2203
|
+
const accountId = String(botAny.uin ?? botAny.user_id ?? bot.bot_id);
|
|
2204
|
+
const isStdin = bot.adapter === "stdin";
|
|
2155
2205
|
const base = {
|
|
2156
2206
|
botId: bot.bot_id,
|
|
2157
|
-
|
|
2207
|
+
accountId,
|
|
2208
|
+
qq: accountId,
|
|
2158
2209
|
nickname: bot.nickname || botAny.name || "Unknown Bot",
|
|
2159
|
-
avatar:
|
|
2210
|
+
avatar: isStdin
|
|
2211
|
+
? readStdinAvatar()
|
|
2212
|
+
: `https://q1.qlogo.cn/g?b=qq&nk=${accountId}&s=160`,
|
|
2160
2213
|
online: bot.online,
|
|
2161
|
-
|
|
2162
|
-
|
|
2214
|
+
adapter: String(bot.adapter),
|
|
2215
|
+
adapterVersion: readAdapterPackageVersion(String(bot.adapter)),
|
|
2216
|
+
implLabel: "",
|
|
2217
|
+
framework: isStdin ? "stdin" : String(bot.adapter),
|
|
2218
|
+
appVersion: "",
|
|
2219
|
+
protocolVersion: "",
|
|
2220
|
+
platform: "",
|
|
2221
|
+
platformVersion: "",
|
|
2163
2222
|
groupCount: 0,
|
|
2164
2223
|
friendCount: 0,
|
|
2224
|
+
send: 0,
|
|
2225
|
+
receive: 0,
|
|
2165
2226
|
onlineDurationMs: 0,
|
|
2166
|
-
statusText: "online",
|
|
2227
|
+
statusText: bot.online ? "online" : "offline",
|
|
2167
2228
|
};
|
|
2168
2229
|
|
|
2169
2230
|
try {
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2231
|
+
if (typeof bot.getAvatar === "function" && !isStdin) {
|
|
2232
|
+
const avatar = await bot.getAvatar().catch(() => null);
|
|
2233
|
+
if (avatar) base.avatar = avatar;
|
|
2234
|
+
}
|
|
2235
|
+
} catch {
|
|
2236
|
+
// keep qlogo fallback
|
|
2237
|
+
}
|
|
2238
|
+
return base;
|
|
2239
|
+
}
|
|
2176
2240
|
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2241
|
+
async function getBotOverviewList(): Promise<Record<string, any>[]> {
|
|
2242
|
+
const bots = Array.from(connectedBots.values());
|
|
2243
|
+
if (bots.length === 0) return [];
|
|
2244
|
+
const sorted = [...bots].sort(
|
|
2245
|
+
(a, b) => Number(b.adapter === "stdin") - Number(a.adapter === "stdin"),
|
|
2246
|
+
);
|
|
2183
2247
|
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2248
|
+
const adapters = sorted.map((bot) => ({
|
|
2249
|
+
name: String(bot.adapter),
|
|
2250
|
+
version: readAdapterPackageVersion(String(bot.adapter)),
|
|
2251
|
+
}));
|
|
2252
|
+
const report = await buildAdapterReport({ bots: sorted, adapters }).catch(
|
|
2253
|
+
() => null,
|
|
2254
|
+
);
|
|
2255
|
+
const instances = new Map<string, AdapterInstanceStatus>();
|
|
2256
|
+
const implOf = new Map<
|
|
2257
|
+
string,
|
|
2258
|
+
{ name: string; version?: string } | undefined
|
|
2259
|
+
>();
|
|
2260
|
+
for (const entry of report?.adapters ?? []) {
|
|
2261
|
+
implOf.set(entry.name, entry.impl);
|
|
2262
|
+
for (const instance of entry.instances) {
|
|
2263
|
+
instances.set(`${entry.name}:${instance.bot_id}`, instance);
|
|
2264
|
+
}
|
|
2200
2265
|
}
|
|
2266
|
+
|
|
2267
|
+
return Promise.all(
|
|
2268
|
+
sorted.map(async (bot) => {
|
|
2269
|
+
const detail = await getBotDetails(bot);
|
|
2270
|
+
const uin = String(detail.accountId);
|
|
2271
|
+
const adapterName = String(detail.adapter);
|
|
2272
|
+
const isStdin = adapterName === "stdin";
|
|
2273
|
+
const instance = instances.get(`${adapterName}:${uin}`);
|
|
2274
|
+
const lib = implOf.get(adapterName);
|
|
2275
|
+
const appVersion = instance?.version || lib?.version || "";
|
|
2276
|
+
const implLabel = isStdin
|
|
2277
|
+
? "stdin"
|
|
2278
|
+
: instance?.impl
|
|
2279
|
+
? instance.version
|
|
2280
|
+
? `${instance.impl}/${instance.version}`
|
|
2281
|
+
: String(instance.impl)
|
|
2282
|
+
: lib?.name
|
|
2283
|
+
? lib.version
|
|
2284
|
+
? `${lib.name} v${lib.version}`
|
|
2285
|
+
: lib.name
|
|
2286
|
+
: adapterName;
|
|
2287
|
+
|
|
2288
|
+
return {
|
|
2289
|
+
...detail,
|
|
2290
|
+
implLabel,
|
|
2291
|
+
framework: isStdin ? "stdin" : adapterName,
|
|
2292
|
+
appVersion: isStdin
|
|
2293
|
+
? String(readAdapterPackageVersion(adapterName))
|
|
2294
|
+
: appVersion,
|
|
2295
|
+
protocolVersion: instance?.protocol ?? "",
|
|
2296
|
+
platform: instance?.platform ?? "",
|
|
2297
|
+
platformVersion: instance?.platformVersion ?? "",
|
|
2298
|
+
online: instance?.online ?? bot.online,
|
|
2299
|
+
groupCount: instance?.stats.groups ?? 0,
|
|
2300
|
+
friendCount: instance?.stats.friends ?? 0,
|
|
2301
|
+
send: instance?.stats.sent ?? 0,
|
|
2302
|
+
receive: instance?.stats.received ?? 0,
|
|
2303
|
+
onlineDurationMs:
|
|
2304
|
+
bot.connected_at != null && bot.online
|
|
2305
|
+
? Math.max(0, Date.now() - bot.connected_at)
|
|
2306
|
+
: 0,
|
|
2307
|
+
};
|
|
2308
|
+
}),
|
|
2309
|
+
);
|
|
2201
2310
|
}
|
|
2202
2311
|
|
|
2203
2312
|
export async function getSystemOverview(): Promise<Record<string, any>> {
|
|
@@ -2221,9 +2330,10 @@ export async function getSystemOverview(): Promise<Record<string, any>> {
|
|
|
2221
2330
|
: 0;
|
|
2222
2331
|
const siSnapshot = await getSystemInformationSnapshot();
|
|
2223
2332
|
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2226
|
-
const selectedBot =
|
|
2333
|
+
const bots = await getBotOverviewList();
|
|
2334
|
+
// 默认展示第一个非 stdin 的 bot(stdin 是终端通道,不是真实账号)
|
|
2335
|
+
const selectedBot =
|
|
2336
|
+
bots.find((bot) => bot.adapter !== "stdin") || bots[0] || null;
|
|
2227
2337
|
|
|
2228
2338
|
return {
|
|
2229
2339
|
uptimeSeconds: process.uptime(),
|
|
@@ -2310,15 +2420,30 @@ export function updateChatConfig(fileName: string, data: any): any {
|
|
|
2310
2420
|
return data;
|
|
2311
2421
|
}
|
|
2312
2422
|
|
|
2423
|
+
export function getAgentConfig(fileName: string): any {
|
|
2424
|
+
const filePath = path.join(AGENT_CONFIG_DIR, fileName);
|
|
2425
|
+
return readJsonFile(filePath, {});
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
export function updateAgentConfig(fileName: string, data: any): any {
|
|
2429
|
+
const filePath = path.join(AGENT_CONFIG_DIR, fileName);
|
|
2430
|
+
writeJsonFile(filePath, data);
|
|
2431
|
+
return data;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2313
2434
|
export function getAdapterConfigs(adapterName: string): any {
|
|
2314
2435
|
const rootPkg = readRootPackageJson();
|
|
2315
2436
|
const adapters = rootPkg?.mioku?.adapters ?? {};
|
|
2316
|
-
const safeName = String(adapterName || "")
|
|
2437
|
+
const safeName = String(adapterName || "")
|
|
2438
|
+
.trim()
|
|
2439
|
+
.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2317
2440
|
return adapters[safeName] ?? {};
|
|
2318
2441
|
}
|
|
2319
2442
|
|
|
2320
2443
|
export function updateAdapterConfig(adapterName: string, data: any): any {
|
|
2321
|
-
const safeName = String(adapterName || "")
|
|
2444
|
+
const safeName = String(adapterName || "")
|
|
2445
|
+
.trim()
|
|
2446
|
+
.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2322
2447
|
if (!safeName) throw new Error("适配器名称无效");
|
|
2323
2448
|
const rootPkg = readRootPackageJson();
|
|
2324
2449
|
rootPkg.mioku = rootPkg.mioku ?? {};
|
package/utils.ts
CHANGED
|
@@ -28,6 +28,7 @@ export const DATA_DIR = getDataDir();
|
|
|
28
28
|
export const PLUGINS_DIR = path.join(process.cwd(), "plugins");
|
|
29
29
|
export const SERVICES_DIR = path.join(process.cwd(), "node_modules");
|
|
30
30
|
export const CHAT_CONFIG_DIR = path.join(getConfigDir(), "chat");
|
|
31
|
+
export const AGENT_CONFIG_DIR = path.join(getConfigDir(), "agent");
|
|
31
32
|
export const CHAT_DATA_DIR = path.join(getDataDir(), "chat");
|
|
32
33
|
export const LOGS_DIR = path.join(process.cwd(), "logs");
|
|
33
34
|
export const NODE_MODULES_DIR = path.join(process.cwd(), "node_modules");
|