aios-management-web 0.1.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.
Files changed (91) hide show
  1. package/.env.json +21 -0
  2. package/README.md +257 -0
  3. package/data/management-console.db +0 -0
  4. package/data/management-console.db-shm +0 -0
  5. package/data/management-console.db-wal +0 -0
  6. package/dist/assets/index-CV_wjCAG.js +464 -0
  7. package/dist/assets/index-DfMPB0eV.css +1 -0
  8. package/dist/index.html +13 -0
  9. package/docs/spec.md +199 -0
  10. package/index.html +12 -0
  11. package/package.json +37 -0
  12. package/scripts/reset-kernel.js +59 -0
  13. package/scripts/reset-password.js +22 -0
  14. package/server/fakes.js +57 -0
  15. package/server/index.js +21 -0
  16. package/server/src/api/middleware/auth.js +29 -0
  17. package/server/src/api/middleware/internal.js +44 -0
  18. package/server/src/api/routes/index.js +677 -0
  19. package/server/src/app.js +90 -0
  20. package/server/src/background/index.js +106 -0
  21. package/server/src/background/protocol.js +15 -0
  22. package/server/src/config/env.js +90 -0
  23. package/server/src/db/index.js +501 -0
  24. package/server/src/infra/mqtt/management-rpc-client.js +213 -0
  25. package/server/src/infra/providers/hzg-provider-client.js +39 -0
  26. package/server/src/infra/s3/object-storage.js +97 -0
  27. package/server/src/services/agent-quota.js +54 -0
  28. package/server/src/services/agent-service.js +696 -0
  29. package/server/src/services/agent-status-sync-service.js +132 -0
  30. package/server/src/services/audit-log-service.js +39 -0
  31. package/server/src/services/auth-service.js +153 -0
  32. package/server/src/services/catalog-sync-service.js +712 -0
  33. package/server/src/services/external-service.js +308 -0
  34. package/server/src/services/kernel-reset-service.js +86 -0
  35. package/server/src/services/portal-service.js +555 -0
  36. package/server/src/services/system-service.js +580 -0
  37. package/server/src/services/topic-ping-service.js +282 -0
  38. package/server/src/utils/errors.js +36 -0
  39. package/server/src/utils/security.js +22 -0
  40. package/server/test/agent-service-alignment.test.js +316 -0
  41. package/server/test/agent-service-create.test.js +662 -0
  42. package/server/test/agent-status-sync-service.test.js +167 -0
  43. package/server/test/agent-update-audit.test.js +63 -0
  44. package/server/test/auth-middleware.test.js +71 -0
  45. package/server/test/background-services.test.js +160 -0
  46. package/server/test/catalog-sync-service.test.js +920 -0
  47. package/server/test/db-reset-migration.test.js +123 -0
  48. package/server/test/env-config.test.js +68 -0
  49. package/server/test/external-service.test.js +380 -0
  50. package/server/test/hzg-provider-client.test.js +50 -0
  51. package/server/test/internal-auth-middleware.test.js +66 -0
  52. package/server/test/kernel-reset-service.test.js +112 -0
  53. package/server/test/management-rpc-client.test.js +105 -0
  54. package/server/test/portal-service-access-tokens.test.js +121 -0
  55. package/server/test/portal-service-alignment.test.js +318 -0
  56. package/server/test/portal-service-management-logs.test.js +114 -0
  57. package/server/test/reset-kernel-cli.test.js +23 -0
  58. package/server/test/service-api-auth-middleware.test.js +59 -0
  59. package/server/test/system-service-alignment.test.js +265 -0
  60. package/server/test/topic-ping-service.test.js +182 -0
  61. package/server/test/usage-refresh-audit-route.test.js +82 -0
  62. package/src/App.jsx +1 -0
  63. package/src/api.js +1 -0
  64. package/src/app/App.jsx +346 -0
  65. package/src/app/api-client.js +112 -0
  66. package/src/components/AppShell.jsx +117 -0
  67. package/src/components/CardTitleWithReload.jsx +20 -0
  68. package/src/components/DeleteActionButton.jsx +31 -0
  69. package/src/main.jsx +14 -0
  70. package/src/pages/AgentsPage.jsx +647 -0
  71. package/src/pages/AiosUsersPage.jsx +151 -0
  72. package/src/pages/DashboardPage.jsx +72 -0
  73. package/src/pages/LoginPage.jsx +41 -0
  74. package/src/pages/SettingsPage.jsx +431 -0
  75. package/src/pages/SkillsPage.jsx +175 -0
  76. package/src/pages/SystemLogsPage.jsx +349 -0
  77. package/src/pages/SystemsPage.jsx +498 -0
  78. package/src/pages/TemplatesPage.jsx +207 -0
  79. package/src/pages/UserManagementPage.jsx +25 -0
  80. package/src/pages/UsersPage.jsx +192 -0
  81. package/src/pages/system-logs/SystemLogsTabs.jsx +362 -0
  82. package/src/styles.css +222 -0
  83. package/src/utils/format.js +63 -0
  84. package/test/.reports/fast-2026-05-25T08-32-39-420Z.json +299 -0
  85. package/test/integration/common.js +208 -0
  86. package/test/integration/fast.js +135 -0
  87. package/test/integration/full.js +306 -0
  88. package/test/run-browser-e2e.js +212 -0
  89. package/test/run-jasmine.js +21 -0
  90. package/test/setup.js +1 -0
  91. package/vite.config.js +12 -0
@@ -0,0 +1,308 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ import { badRequest, internalError, notFound } from "../utils/errors.js";
4
+
5
+ const MAX_COOKIE_LENGTH = 16 * 1024;
6
+
7
+ function normalizeProvider(value) {
8
+ const provider = String(value || "").trim().toLowerCase();
9
+ if (provider !== "hzg" && provider !== "phx") {
10
+ throw badRequest("provider 必须是 hzg 或 phx");
11
+ }
12
+ return provider;
13
+ }
14
+
15
+ function normalizeCookie(value) {
16
+ const cookie = String(value || "").trim();
17
+ if (cookie.length > MAX_COOKIE_LENGTH) {
18
+ throw badRequest(`cookie 长度不能超过 ${MAX_COOKIE_LENGTH} 字符`);
19
+ }
20
+ return cookie;
21
+ }
22
+
23
+ function parseJsonString(value, fallback = {}) {
24
+ if (typeof value !== "string" || !value.trim()) {
25
+ return fallback;
26
+ }
27
+
28
+ try {
29
+ return JSON.parse(value);
30
+ } catch {
31
+ return fallback;
32
+ }
33
+ }
34
+
35
+ function normalizeResponsePayload(value) {
36
+ if (typeof value !== "string" || !value.trim()) {
37
+ return {};
38
+ }
39
+
40
+ try {
41
+ return JSON.parse(value);
42
+ } catch {
43
+ return value;
44
+ }
45
+ }
46
+
47
+ function extractAgentTopics(remoteStateJson) {
48
+ const remote = parseJsonString(remoteStateJson, {});
49
+ return {
50
+ inboundTopic: String(remote?.inboundTopic || remote?.["inbound-topic"] || "").trim(),
51
+ outboundTopic: String(remote?.outboundTopic || remote?.["outbound-topic"] || "").trim()
52
+ };
53
+ }
54
+
55
+ function describeUnavailableAgentStatus(status) {
56
+ switch (status) {
57
+ case "disabled":
58
+ return "agent 已停用";
59
+ case "overlimit":
60
+ return "agent 已超出用量限额";
61
+ default:
62
+ return `agent 状态不是 normal,当前状态为 ${status || "unknown"}`;
63
+ }
64
+ }
65
+
66
+ export class ExternalService {
67
+ constructor({ db, agentService, systemService, hzgProviderClient, auditLogService }) {
68
+ this.db = db;
69
+ this.agentService = agentService;
70
+ this.systemService = systemService;
71
+ this.hzgProviderClient = hzgProviderClient;
72
+ this.auditLogService = auditLogService;
73
+ }
74
+
75
+ upsertSessionCookie(sessionId, provider, cookie) {
76
+ const now = new Date().toISOString();
77
+ const normalizedCookie = normalizeCookie(cookie);
78
+ this.db.prepare(`
79
+ INSERT INTO external_session_cookies (
80
+ session_id, provider, cookie, created_at, updated_at
81
+ ) VALUES (?, ?, ?, ?, ?)
82
+ ON CONFLICT(session_id, provider) DO UPDATE SET
83
+ cookie = excluded.cookie,
84
+ updated_at = excluded.updated_at
85
+ `).run(sessionId, provider, normalizedCookie, now, now);
86
+ }
87
+
88
+ listAgentsForUser(username) {
89
+ const normalizedUsername = String(username || "").trim();
90
+ if (!normalizedUsername) {
91
+ throw badRequest("userName 不能为空");
92
+ }
93
+
94
+ return this.agentService.listAgentDirectoryForUser(normalizedUsername);
95
+ }
96
+
97
+ getOrCreateSession({ userName, agentId, cookie }) {
98
+ const normalizedUserName = String(userName || "").trim();
99
+ const normalizedAgentId = String(agentId || "").trim();
100
+ const normalizedCookie = normalizeCookie(cookie);
101
+ if (!normalizedUserName || !normalizedAgentId) {
102
+ throw badRequest("userName 和 agentId 不能为空");
103
+ }
104
+
105
+ const agent = this.db.prepare(`
106
+ SELECT id, slug, status, remote_state_json
107
+ FROM agents
108
+ WHERE slug = ?
109
+ `).get(normalizedAgentId);
110
+ if (!agent) {
111
+ throw notFound(`数字员工不存在:${normalizedAgentId}`);
112
+ }
113
+ if (agent.status !== "normal") {
114
+ throw internalError(`无法创建外部会话:${describeUnavailableAgentStatus(agent.status)},agentId=${normalizedAgentId}`, {
115
+ agentId: normalizedAgentId,
116
+ status: agent.status
117
+ });
118
+ }
119
+
120
+ const hasPermission = this.db.prepare(`
121
+ SELECT 1
122
+ FROM agent_permissions ap
123
+ JOIN aios_users d ON d.id = ap.aios_user_id
124
+ WHERE ap.agent_id = ? AND d.username = ?
125
+ `).get(agent.id, normalizedUserName);
126
+ if (!hasPermission) {
127
+ throw notFound(`用户 ${normalizedUserName} 无权访问数字员工 ${normalizedAgentId}`);
128
+ }
129
+
130
+ const aiosUser = this.db.prepare(`
131
+ SELECT id
132
+ FROM aios_users
133
+ WHERE username = ?
134
+ `).get(normalizedUserName);
135
+ if (!aiosUser) {
136
+ throw notFound(`用户 ${normalizedUserName} 无权访问数字员工 ${normalizedAgentId}`);
137
+ }
138
+
139
+ const { inboundTopic, outboundTopic } = extractAgentTopics(agent.remote_state_json);
140
+ const existing = this.db.prepare(`
141
+ SELECT session_id
142
+ FROM external_sessions
143
+ WHERE aios_user_id = ? AND agent_id = ?
144
+ `).get(aiosUser.id, agent.id);
145
+ if (existing?.session_id) {
146
+ if (normalizedCookie) {
147
+ this.upsertSessionCookie(existing.session_id, "hzg", normalizedCookie);
148
+ }
149
+ return {
150
+ sessionId: existing.session_id,
151
+ inboundTopic,
152
+ outboundTopic
153
+ };
154
+ }
155
+
156
+ const sessionId = `s-${randomUUID()}`;
157
+ const now = new Date().toISOString();
158
+ this.db.prepare(`
159
+ INSERT INTO external_sessions (
160
+ session_id, aios_user_id, agent_id, created_at, updated_at
161
+ ) VALUES (?, ?, ?, ?, ?)
162
+ `).run(sessionId, aiosUser.id, agent.id, now, now);
163
+
164
+ if (normalizedCookie) {
165
+ this.upsertSessionCookie(sessionId, "hzg", normalizedCookie);
166
+ }
167
+
168
+ this.auditLogService.write({
169
+ userId: null,
170
+ username: normalizedUserName,
171
+ action: "创建外部会话",
172
+ detail: `用户 ${normalizedUserName} 为数字员工 ${agent.slug} 创建会话 ${sessionId},agent状态=${agent.status}`
173
+ });
174
+
175
+ return {
176
+ sessionId,
177
+ inboundTopic,
178
+ outboundTopic
179
+ };
180
+ }
181
+
182
+ async getCookie(sessionId, provider, applicationName = "") {
183
+ const normalizedSessionId = String(sessionId || "").trim();
184
+ const normalizedProvider = normalizeProvider(provider);
185
+ const normalizedApplicationName = String(applicationName || "").trim().toLowerCase();
186
+ if (!normalizedSessionId) {
187
+ throw badRequest("sessionId 不能为空");
188
+ }
189
+
190
+ const existing = this.db.prepare(`
191
+ SELECT cookie
192
+ FROM external_session_cookies
193
+ WHERE session_id = ? AND provider = ?
194
+ `).get(normalizedSessionId, normalizedProvider);
195
+ if (existing?.cookie) {
196
+ return {
197
+ provider: normalizedProvider,
198
+ cookie: existing.cookie
199
+ };
200
+ }
201
+
202
+ const session = this.db.prepare(`
203
+ SELECT
204
+ es.session_id,
205
+ ag.slug AS agent_slug
206
+ FROM external_sessions es
207
+ JOIN agents ag ON ag.id = es.agent_id
208
+ WHERE es.session_id = ?
209
+ `).get(normalizedSessionId);
210
+ if (!session) {
211
+ throw notFound(`会话不存在:${normalizedSessionId}`);
212
+ }
213
+
214
+ if (normalizedProvider !== "hzg") {
215
+ throw notFound(`未找到 provider=${normalizedProvider} 的 Cookie`);
216
+ }
217
+
218
+ const system = normalizedApplicationName
219
+ ? this.systemService.findSystemForInvocation(normalizedProvider, normalizedApplicationName)
220
+ : this.systemService.findLatestActiveSystemByProvider(normalizedProvider);
221
+ if (!system) {
222
+ throw notFound(`未找到 provider=${normalizedProvider} 的可用业务系统`);
223
+ }
224
+
225
+ const cookie = await this.hzgProviderClient.createSessionCookie(system.base_url, normalizedSessionId);
226
+ this.upsertSessionCookie(normalizedSessionId, normalizedProvider, cookie);
227
+
228
+ return {
229
+ provider: normalizedProvider,
230
+ cookie
231
+ };
232
+ }
233
+
234
+ getContext(sessionId) {
235
+ const normalizedSessionId = String(sessionId || "").trim();
236
+ if (!normalizedSessionId) {
237
+ throw badRequest("sessionId 不能为空");
238
+ }
239
+
240
+ const session = this.db.prepare(`
241
+ SELECT
242
+ es.session_id,
243
+ d.username AS user_name,
244
+ ag.slug AS agent_id,
245
+ ag.agent_name,
246
+ ag.remote_state_json
247
+ FROM external_sessions es
248
+ JOIN aios_users d ON d.id = es.aios_user_id
249
+ JOIN agents ag ON ag.id = es.agent_id
250
+ WHERE es.session_id = ?
251
+ `).get(normalizedSessionId);
252
+ if (!session) {
253
+ throw notFound(`会话不存在:${normalizedSessionId}`);
254
+ }
255
+
256
+ const { inboundTopic, outboundTopic } = extractAgentTopics(session.remote_state_json);
257
+ return {
258
+ inboundTopic,
259
+ outboundTopic,
260
+ agentName: String(session.agent_name || "").trim(),
261
+ agentId: String(session.agent_id || "").trim(),
262
+ userName: String(session.user_name || "").trim()
263
+ };
264
+ }
265
+
266
+ async recordInvocation(payload) {
267
+ const sessionId = String(payload?.sessionId || "").trim();
268
+ const provider = normalizeProvider(payload?.provider);
269
+ const applicationName = String(payload?.applicationName || "").trim().toLowerCase();
270
+ const commandName = String(payload?.commandName || "").trim();
271
+ if (!sessionId || !applicationName || !commandName) {
272
+ throw badRequest("sessionId、applicationName、commandName 不能为空");
273
+ }
274
+
275
+ const session = this.db.prepare(`
276
+ SELECT
277
+ es.session_id,
278
+ d.username AS user_name,
279
+ ag.slug AS agent_slug
280
+ FROM external_sessions es
281
+ JOIN aios_users d ON d.id = es.aios_user_id
282
+ JOIN agents ag ON ag.id = es.agent_id
283
+ WHERE es.session_id = ?
284
+ `).get(sessionId);
285
+ if (!session) {
286
+ throw notFound(`会话不存在:${sessionId}`);
287
+ }
288
+
289
+ const system = this.systemService.findSystemForInvocation(provider, applicationName);
290
+ if (!system) {
291
+ throw notFound(`未找到业务系统:${provider}/${applicationName}`);
292
+ }
293
+
294
+ await this.systemService.recordInvocation({
295
+ trace_id: randomUUID(),
296
+ agent_slug: session.agent_slug,
297
+ session_id: sessionId,
298
+ provider: system.provider || provider,
299
+ application_name: applicationName,
300
+ command_name: commandName,
301
+ request_payload: parseJsonString(payload?.paramaters, {}),
302
+ response_payload: normalizeResponsePayload(payload?.response),
303
+ response_time_ms: Number(payload?.durationInMS || 0),
304
+ success: Boolean(payload?.isOK),
305
+ error_message: payload?.errorMessage ? String(payload.errorMessage) : null
306
+ });
307
+ }
308
+ }
@@ -0,0 +1,86 @@
1
+ function normalizeRemoteName(...values) {
2
+ for (const value of values) {
3
+ const text = String(value || "").trim();
4
+ if (text) {
5
+ return text;
6
+ }
7
+ }
8
+
9
+ return "";
10
+ }
11
+
12
+ export class KernelResetService {
13
+ constructor({ rpcClient }) {
14
+ this.rpcClient = rpcClient;
15
+ }
16
+
17
+ async listAgentIds() {
18
+ const remote = await this.rpcClient.call("agent.list", {});
19
+ const items = Array.isArray(remote?.items) ? remote.items : [];
20
+ return items
21
+ .map((item) => normalizeRemoteName(item?.agentId, item?.id, item?.name))
22
+ .filter(Boolean);
23
+ }
24
+
25
+ async listOntologyIds() {
26
+ const remote = await this.rpcClient.call("ontology.list", {});
27
+ const items = Array.isArray(remote?.items) ? remote.items : [];
28
+ return items
29
+ .filter((item) => !item?.["is-built-in"])
30
+ .map((item) => normalizeRemoteName(item?.name, item?.applicationName, item?.id, item?.ontologyName))
31
+ .filter(Boolean);
32
+ }
33
+
34
+ async listTemplateNames() {
35
+ const remote = await this.rpcClient.call("agent.template.list", {});
36
+ const items = Array.isArray(remote?.items) ? remote.items : [];
37
+ return items
38
+ .filter((item) => !item?.["is-built-in"])
39
+ .map((item) => normalizeRemoteName(item?.templateName, item?.name))
40
+ .filter(Boolean);
41
+ }
42
+
43
+ async deleteAgents(agentIds) {
44
+ for (const agentId of agentIds) {
45
+ await this.rpcClient.call("agent.delete", { agentId });
46
+ }
47
+ }
48
+
49
+ async deleteOntologies(ontologyIds) {
50
+ for (const systemId of ontologyIds) {
51
+ await this.rpcClient.call("apps.delete", { systemId });
52
+ }
53
+ }
54
+
55
+ async deleteTemplates(templateNames) {
56
+ for (const templateName of templateNames) {
57
+ await this.rpcClient.call("agent.template.delete", { templateName });
58
+ }
59
+ }
60
+
61
+ async planReset() {
62
+ const agentIds = await this.listAgentIds();
63
+ const ontologyIds = await this.listOntologyIds();
64
+ const templateNames = await this.listTemplateNames();
65
+
66
+ return {
67
+ deletedAgents: agentIds,
68
+ deletedOntologies: ontologyIds,
69
+ deletedTemplates: templateNames
70
+ };
71
+ }
72
+
73
+ async resetKernel({ dryRun = false } = {}) {
74
+ const plan = await this.planReset();
75
+ if (!dryRun) {
76
+ await this.deleteAgents(plan.deletedAgents);
77
+ await this.deleteOntologies(plan.deletedOntologies);
78
+ await this.deleteTemplates(plan.deletedTemplates);
79
+ }
80
+
81
+ return {
82
+ ...plan,
83
+ dryRun
84
+ };
85
+ }
86
+ }