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,580 @@
1
+ import AdmZip from "adm-zip";
2
+
3
+ import { jsonParse } from "../db/index.js";
4
+ import { badRequest, conflict, notFound } from "../utils/errors.js";
5
+
6
+ const APPLICATION_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
+ const VALID_SCHEMES = new Set(["http", "https"]);
8
+
9
+ function normalizeText(value) {
10
+ return String(value || "").trim();
11
+ }
12
+
13
+ function normalizePort(value) {
14
+ const port = Number(value);
15
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
16
+ throw badRequest("端口号必须是 1-65535 的整数");
17
+ }
18
+ return port;
19
+ }
20
+
21
+ function normalizeScheme(value) {
22
+ const scheme = normalizeText(value).toLowerCase();
23
+ if (!VALID_SCHEMES.has(scheme)) {
24
+ throw badRequest("协议仅支持 http 或 https");
25
+ }
26
+ return scheme;
27
+ }
28
+
29
+ function normalizeHost(value) {
30
+ const host = normalizeText(value);
31
+ if (!host) {
32
+ throw badRequest("主机名不能为空");
33
+ }
34
+ if (host.includes("/") || host.includes("\\") || host.includes("?") || host.includes("#")) {
35
+ throw badRequest("主机名格式不合法");
36
+ }
37
+ return host;
38
+ }
39
+
40
+ function normalizeApplicationName(value) {
41
+ const applicationName = normalizeText(value).toLowerCase();
42
+ if (!applicationName) {
43
+ throw badRequest("应用名不能为空");
44
+ }
45
+ if (!APPLICATION_NAME_PATTERN.test(applicationName)) {
46
+ throw badRequest("应用名需符合 Ubuntu 目录名规则:仅允许小写字母、数字和中划线,且不能以中划线开头或结尾");
47
+ }
48
+ return applicationName;
49
+ }
50
+
51
+ function hasOwn(payload, key) {
52
+ return Object.prototype.hasOwnProperty.call(payload || {}, key);
53
+ }
54
+
55
+ function buildBaseUrl(scheme, host, port, applicationName) {
56
+ return `${scheme}://${host}:${port}/${applicationName}`;
57
+ }
58
+
59
+ function decorateSystemRow(row) {
60
+ if (!row) {
61
+ return null;
62
+ }
63
+
64
+ const baseUrl = buildBaseUrl(row.scheme, row.host, row.port, row.application_name);
65
+ return {
66
+ ...row,
67
+ is_builtin: Boolean(row.is_builtin),
68
+ ontology_name: row.application_name,
69
+ system_id: row.application_name,
70
+ service_endpoint: baseUrl,
71
+ session_endpoint: baseUrl,
72
+ base_url: baseUrl,
73
+ last_connectivity_test_result: jsonParse(row.last_connectivity_test_result_json, {})
74
+ };
75
+ }
76
+
77
+ export class SystemService {
78
+ constructor({ db, objectStorage, rpcClient }) {
79
+ this.db = db;
80
+ this.objectStorage = objectStorage;
81
+ this.rpcClient = rpcClient;
82
+ }
83
+
84
+ listSystems() {
85
+ return this.db.prepare(`
86
+ SELECT
87
+ s.*,
88
+ a.original_name AS ontology_file_name
89
+ FROM business_systems s
90
+ LEFT JOIN artifacts a ON a.id = s.ontology_artifact_id
91
+ ORDER BY s.updated_at DESC
92
+ `).all().map(decorateSystemRow);
93
+ }
94
+
95
+ getSystemById(id) {
96
+ return decorateSystemRow(
97
+ this.db.prepare(`
98
+ SELECT
99
+ s.*,
100
+ a.original_name AS ontology_file_name
101
+ FROM business_systems s
102
+ LEFT JOIN artifacts a ON a.id = s.ontology_artifact_id
103
+ WHERE s.id = ?
104
+ `).get(id)
105
+ );
106
+ }
107
+
108
+ getInvocationBaseUrlByApplicationName(applicationName) {
109
+ const normalizedApplicationName = normalizeApplicationName(applicationName);
110
+ const row = this.db.prepare(`
111
+ SELECT *
112
+ FROM business_systems
113
+ WHERE application_name = ? AND status = 'active'
114
+ `).get(normalizedApplicationName);
115
+ if (!row) {
116
+ throw notFound(`未找到业务系统:${normalizedApplicationName}`);
117
+ }
118
+
119
+ return {
120
+ application_name: row.application_name,
121
+ provider: row.provider,
122
+ baseUrl: buildBaseUrl(row.scheme, row.host, row.port, row.application_name),
123
+ scheme: row.scheme,
124
+ host: row.host,
125
+ port: row.port,
126
+ status: row.status
127
+ };
128
+ }
129
+
130
+ findLatestActiveSystemByProvider(provider) {
131
+ const row = this.db.prepare(`
132
+ SELECT *
133
+ FROM business_systems
134
+ WHERE provider = ? AND status = 'active'
135
+ ORDER BY updated_at DESC, id DESC
136
+ LIMIT 1
137
+ `).get(normalizeText(provider).toLowerCase());
138
+ return decorateSystemRow(row);
139
+ }
140
+
141
+ validateUniqueApplicationName(applicationName, excludeId = null) {
142
+ const existing = excludeId === null
143
+ ? this.db.prepare(`
144
+ SELECT id
145
+ FROM business_systems
146
+ WHERE application_name = ?
147
+ `).get(applicationName)
148
+ : this.db.prepare(`
149
+ SELECT id
150
+ FROM business_systems
151
+ WHERE application_name = ? AND id <> ?
152
+ `).get(applicationName, excludeId);
153
+
154
+ if (existing) {
155
+ throw conflict(`应用名已存在:${applicationName}`);
156
+ }
157
+ }
158
+
159
+ normalizeCreatePayload(payload) {
160
+ const provider = normalizeText(payload.provider).toLowerCase();
161
+ const applicationName = normalizeApplicationName(payload.application_name);
162
+ if (!provider || !applicationName) {
163
+ throw badRequest("提供方和应用名不能为空");
164
+ }
165
+
166
+ return {
167
+ provider,
168
+ application_name: applicationName,
169
+ description: normalizeText(payload.description),
170
+ scheme: normalizeScheme(payload.scheme),
171
+ host: normalizeHost(payload.host),
172
+ port: normalizePort(payload.port),
173
+ status: normalizeText(payload.status) === "disabled" ? "disabled" : "active"
174
+ };
175
+ }
176
+
177
+ normalizeUpdatePayload(payload, current) {
178
+ const provider = hasOwn(payload, "provider") ? normalizeText(payload.provider).toLowerCase() : current.provider;
179
+ const applicationName = hasOwn(payload, "application_name")
180
+ ? normalizeApplicationName(payload.application_name)
181
+ : current.application_name;
182
+ if (!provider || !applicationName) {
183
+ throw badRequest("提供方和应用名不能为空");
184
+ }
185
+
186
+ return {
187
+ provider,
188
+ application_name: applicationName,
189
+ description: hasOwn(payload, "description") ? normalizeText(payload.description) : current.description,
190
+ scheme: hasOwn(payload, "scheme") ? normalizeScheme(payload.scheme) : current.scheme,
191
+ host: hasOwn(payload, "host") ? normalizeHost(payload.host) : current.host,
192
+ port: hasOwn(payload, "port") ? normalizePort(payload.port) : current.port,
193
+ status: hasOwn(payload, "status")
194
+ ? (normalizeText(payload.status) === "disabled" ? "disabled" : "active")
195
+ : current.status
196
+ };
197
+ }
198
+
199
+ getArtifactStorageInfo(artifactId) {
200
+ if (!artifactId) {
201
+ return null;
202
+ }
203
+
204
+ return this.db.prepare(`
205
+ SELECT bucket, object_key
206
+ FROM artifacts
207
+ WHERE id = ?
208
+ `).get(artifactId) || null;
209
+ }
210
+
211
+ async listRemoteApplicationNames() {
212
+ const remote = await this.rpcClient.call("apps.list", {});
213
+ const items = Array.isArray(remote?.items) ? remote.items : [];
214
+ const names = new Set();
215
+
216
+ for (const item of items) {
217
+ const applicationName = normalizeText(
218
+ item?.applicationName || item?.id || item?.name || item?.ontologyName
219
+ ).toLowerCase();
220
+ if (applicationName) {
221
+ names.add(applicationName);
222
+ }
223
+ }
224
+
225
+ return names;
226
+ }
227
+
228
+ async deleteRemoteOntologiesIfPresent(applicationNames, { ignoreDeleteErrors = false } = {}) {
229
+ const normalizedNames = Array.from(new Set(
230
+ applicationNames
231
+ .map((value) => normalizeText(value).toLowerCase())
232
+ .filter(Boolean)
233
+ ));
234
+ if (normalizedNames.length === 0) {
235
+ return {
236
+ deletedApplicationNames: []
237
+ };
238
+ }
239
+
240
+ const remoteNames = await this.listRemoteApplicationNames();
241
+ const deletedApplicationNames = [];
242
+
243
+ for (const applicationName of normalizedNames) {
244
+ if (!remoteNames.has(applicationName)) {
245
+ continue;
246
+ }
247
+
248
+ try {
249
+ await this.rpcClient.call("apps.delete", { systemId: applicationName });
250
+ deletedApplicationNames.push(applicationName);
251
+ } catch (error) {
252
+ if (!ignoreDeleteErrors) {
253
+ throw error;
254
+ }
255
+ }
256
+ }
257
+
258
+ return { deletedApplicationNames };
259
+ }
260
+
261
+ async createSystem({ payload, file, createdBy }) {
262
+ const normalized = this.normalizeCreatePayload(payload);
263
+ this.validateUniqueApplicationName(normalized.application_name);
264
+
265
+ let artifactId = null;
266
+ let bucket = null;
267
+ let objectKey = null;
268
+ if (file) {
269
+ const artifact = await this.persistOntologyArtifact({ file, createdBy });
270
+ artifactId = artifact.id;
271
+ bucket = artifact.bucket;
272
+ objectKey = artifact.objectKey;
273
+ }
274
+
275
+ const { deletedApplicationNames } = await this.deleteRemoteOntologiesIfPresent([
276
+ normalized.application_name
277
+ ]);
278
+
279
+ const baseUrl = buildBaseUrl(
280
+ normalized.scheme,
281
+ normalized.host,
282
+ normalized.port,
283
+ normalized.application_name
284
+ );
285
+ await this.rpcClient.call("apps.create", {
286
+ systemId: normalized.application_name,
287
+ name: normalized.application_name,
288
+ provider: normalized.provider,
289
+ ontologyName: normalized.application_name,
290
+ description: normalized.description,
291
+ applicationEndpoint: baseUrl,
292
+ sessionServiceEndpoint: baseUrl,
293
+ status: normalized.status,
294
+ bucket,
295
+ objectKey,
296
+ replaceOntology: Boolean(bucket && objectKey)
297
+ });
298
+
299
+ const now = new Date().toISOString();
300
+ const result = this.db.prepare(`
301
+ INSERT INTO business_systems (
302
+ provider, application_name, description, ontology_artifact_id,
303
+ scheme, host, port, status, last_connectivity_test_status, last_connectivity_test_result_json,
304
+ created_at, updated_at
305
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
306
+ `).run(
307
+ normalized.provider,
308
+ normalized.application_name,
309
+ normalized.description,
310
+ artifactId,
311
+ normalized.scheme,
312
+ normalized.host,
313
+ normalized.port,
314
+ normalized.status,
315
+ "unknown",
316
+ JSON.stringify({}),
317
+ now,
318
+ now
319
+ );
320
+
321
+ return {
322
+ system: this.getSystemById(result.lastInsertRowid),
323
+ kernelOntologyDeleted: deletedApplicationNames.length > 0,
324
+ deletedKernelOntologyNames: deletedApplicationNames
325
+ };
326
+ }
327
+
328
+ async persistOntologyArtifact({ file, createdBy }) {
329
+ const zip = new AdmZip(file.buffer);
330
+ const names = zip
331
+ .getEntries()
332
+ .filter((entry) => !entry.isDirectory)
333
+ .map((entry) => entry.entryName.replace(/^\/+/, ""));
334
+ if (!names.includes("index.md")) {
335
+ throw badRequest("Ontology 压缩包第一层必须包含 index.md");
336
+ }
337
+
338
+ const artifact = await this.objectStorage.uploadAdminArtifact({ kind: "ontology", file });
339
+ const result = this.db.prepare(`
340
+ INSERT INTO artifacts (
341
+ kind, bucket, object_key, original_name, mime_type, byte_size, created_by, created_at
342
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
343
+ `).run(
344
+ "ontology",
345
+ artifact.bucket,
346
+ artifact.objectKey,
347
+ file.originalname,
348
+ file.mimetype || "application/octet-stream",
349
+ file.size,
350
+ createdBy,
351
+ new Date().toISOString()
352
+ );
353
+
354
+ return {
355
+ id: result.lastInsertRowid,
356
+ ...artifact
357
+ };
358
+ }
359
+
360
+ async updateSystem(id, { payload, file, createdBy }) {
361
+ const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
362
+ if (!current) {
363
+ throw notFound("业务系统不存在");
364
+ }
365
+
366
+ const normalized = this.normalizeUpdatePayload(payload, current);
367
+ this.validateUniqueApplicationName(normalized.application_name, id);
368
+
369
+ let artifactId = current.ontology_artifact_id;
370
+ let artifact;
371
+ if (file) {
372
+ artifact = await this.persistOntologyArtifact({ file, createdBy });
373
+ artifactId = artifact.id;
374
+ }
375
+
376
+ const existingArtifact = artifact || this.getArtifactStorageInfo(artifactId);
377
+
378
+ const baseUrl = buildBaseUrl(
379
+ normalized.scheme,
380
+ normalized.host,
381
+ normalized.port,
382
+ normalized.application_name
383
+ );
384
+ const previousSystemId = current.application_name;
385
+ const { deletedApplicationNames } = await this.deleteRemoteOntologiesIfPresent([
386
+ previousSystemId,
387
+ normalized.application_name
388
+ ], { ignoreDeleteErrors: true });
389
+
390
+ await this.rpcClient.call("apps.create", {
391
+ systemId: normalized.application_name,
392
+ name: normalized.application_name,
393
+ provider: normalized.provider,
394
+ ontologyName: normalized.application_name,
395
+ description: normalized.description,
396
+ applicationEndpoint: baseUrl,
397
+ sessionServiceEndpoint: baseUrl,
398
+ status: normalized.status,
399
+ bucket: existingArtifact?.bucket,
400
+ objectKey: existingArtifact?.object_key || existingArtifact?.objectKey,
401
+ replaceOntology: Boolean(existingArtifact?.bucket && (existingArtifact?.object_key || existingArtifact?.objectKey))
402
+ });
403
+
404
+ this.db.prepare(`
405
+ UPDATE business_systems
406
+ SET provider = ?, application_name = ?, description = ?, ontology_artifact_id = ?,
407
+ scheme = ?, host = ?, port = ?, status = ?, updated_at = ?
408
+ WHERE id = ?
409
+ `).run(
410
+ normalized.provider,
411
+ normalized.application_name,
412
+ normalized.description,
413
+ artifactId,
414
+ normalized.scheme,
415
+ normalized.host,
416
+ normalized.port,
417
+ normalized.status,
418
+ new Date().toISOString(),
419
+ id
420
+ );
421
+
422
+ return {
423
+ system: this.getSystemById(id),
424
+ kernelOntologyDeleted: deletedApplicationNames.length > 0,
425
+ deletedKernelOntologyNames: deletedApplicationNames
426
+ };
427
+ }
428
+
429
+ async setSystemStatus(id, status) {
430
+ const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
431
+ if (!current) {
432
+ throw notFound("业务系统不存在");
433
+ }
434
+
435
+ const normalizedStatus = normalizeText(status) === "disabled" ? "disabled" : "active";
436
+ await this.rpcClient.call(normalizedStatus === "active" ? "apps.enable" : "apps.disable", {
437
+ systemId: current.application_name
438
+ });
439
+ this.db.prepare(`
440
+ UPDATE business_systems
441
+ SET status = ?, updated_at = ?
442
+ WHERE id = ?
443
+ `).run(normalizedStatus, new Date().toISOString(), id);
444
+
445
+ return this.getSystemById(id);
446
+ }
447
+
448
+ async deleteSystem(id) {
449
+ const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
450
+ if (!current) {
451
+ throw notFound("业务系统不存在");
452
+ }
453
+
454
+ if (current.is_builtin) {
455
+ throw conflict("内置 ontology 不允许删除");
456
+ }
457
+
458
+ await this.rpcClient.call("apps.delete", { systemId: current.application_name });
459
+ this.db.prepare("DELETE FROM business_systems WHERE id = ?").run(id);
460
+ return { ok: true };
461
+ }
462
+
463
+ async testConnectivity(id) {
464
+ const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
465
+ if (!current) {
466
+ throw notFound("业务系统不存在");
467
+ }
468
+
469
+ const baseUrl = buildBaseUrl(current.scheme, current.host, current.port, current.application_name);
470
+ const response = await fetch(baseUrl, { method: "GET" });
471
+ const ok = response.ok;
472
+ const result = {
473
+ ok,
474
+ baseUrl,
475
+ service: { status: response.status, url: baseUrl },
476
+ tested_at: new Date().toISOString()
477
+ };
478
+
479
+ this.db.prepare(`
480
+ UPDATE business_systems
481
+ SET last_connectivity_test_status = ?, last_connectivity_test_result_json = ?, updated_at = ?
482
+ WHERE id = ?
483
+ `).run(ok ? "ok" : "failed", JSON.stringify(result), new Date().toISOString(), id);
484
+
485
+ return result;
486
+ }
487
+
488
+ findSystemForInvocation(provider, applicationName) {
489
+ return decorateSystemRow(this.db.prepare(`
490
+ SELECT *
491
+ FROM business_systems
492
+ WHERE provider = ? AND application_name = ? AND status = 'active'
493
+ `).get(normalizeText(provider).toLowerCase(), normalizeApplicationName(applicationName)));
494
+ }
495
+
496
+ async recordInvocation(log) {
497
+ this.db.prepare(`
498
+ INSERT INTO system_invocation_logs (
499
+ trace_id, agent_slug, session_id, provider, application_name,
500
+ command_name, request_payload_json, response_payload_json, response_time_ms,
501
+ success, error_message, created_at
502
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
503
+ `).run(
504
+ log.trace_id,
505
+ log.agent_slug || null,
506
+ log.session_id || null,
507
+ log.provider,
508
+ log.application_name,
509
+ log.command_name,
510
+ JSON.stringify(log.request_payload ?? {}),
511
+ JSON.stringify(log.response_payload ?? {}),
512
+ log.response_time_ms,
513
+ log.success ? 1 : 0,
514
+ log.error_message || null,
515
+ log.created_at || new Date().toISOString()
516
+ );
517
+
518
+ try {
519
+ await this.rpcClient.call("apps.log.record", {
520
+ systemId: log.application_name,
521
+ agentId: log.agent_slug,
522
+ conversationId: log.session_id,
523
+ provider: log.provider,
524
+ applicationName: log.application_name,
525
+ commandName: log.command_name,
526
+ inputParams: log.request_payload,
527
+ responseTimeMs: log.response_time_ms,
528
+ success: log.success,
529
+ responseContent: log.response_payload
530
+ });
531
+ } catch {}
532
+ }
533
+
534
+ listLogs({ applicationName = "", agentSlug = "" } = {}) {
535
+ return this.db.prepare(`
536
+ SELECT *
537
+ FROM system_invocation_logs
538
+ WHERE (? = '' OR application_name = ?)
539
+ AND (? = '' OR agent_slug = ?)
540
+ ORDER BY created_at DESC
541
+ LIMIT 200
542
+ `).all(
543
+ normalizeText(applicationName).toLowerCase(),
544
+ normalizeText(applicationName).toLowerCase(),
545
+ normalizeText(agentSlug),
546
+ normalizeText(agentSlug)
547
+ ).map((row) => ({
548
+ ...row,
549
+ system_id: row.application_name,
550
+ request_payload: jsonParse(row.request_payload_json, {}),
551
+ response_payload: jsonParse(row.response_payload_json, {})
552
+ }));
553
+ }
554
+
555
+ listStats({ applicationName = "", agentSlug = "" } = {}) {
556
+ return this.db.prepare(`
557
+ SELECT
558
+ application_name,
559
+ provider,
560
+ command_name,
561
+ COUNT(*) AS call_count,
562
+ SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS success_count,
563
+ ROUND(AVG(response_time_ms), 1) AS avg_response_ms
564
+ FROM system_invocation_logs
565
+ WHERE (? = '' OR application_name = ?)
566
+ AND (? = '' OR agent_slug = ?)
567
+ GROUP BY application_name, provider, command_name
568
+ ORDER BY call_count DESC, avg_response_ms ASC
569
+ `).all(
570
+ normalizeText(applicationName).toLowerCase(),
571
+ normalizeText(applicationName).toLowerCase(),
572
+ normalizeText(agentSlug),
573
+ normalizeText(agentSlug)
574
+ ).map((row) => ({
575
+ ...row,
576
+ system_id: row.application_name,
577
+ success_rate: row.call_count ? Number(((row.success_count / row.call_count) * 100).toFixed(1)) : 0
578
+ }));
579
+ }
580
+ }