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,662 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "aios-web-agent-create-test-"));
6
+ process.env.AIOS_WEB_DATA_DIR = dataDir;
7
+ const { AgentService } = await import("../src/services/agent-service.js");
8
+
9
+ function createDb({ failAgentInsert = false } = {}) {
10
+ const insertedRows = [];
11
+ const permissions = [];
12
+ const aiosUsers = new Map([
13
+ ["zhangsan", { id: 11, username: "zhangsan" }],
14
+ ["lisi", { id: 12, username: "lisi" }]
15
+ ]);
16
+ const skills = new Map();
17
+
18
+ return {
19
+ insertedRows,
20
+ permissions,
21
+ prepare(sql) {
22
+ if (sql.includes("SELECT 1 FROM agents WHERE slug = ?")) {
23
+ return {
24
+ get: () => null
25
+ };
26
+ }
27
+
28
+ if (sql.includes("SELECT * FROM agents WHERE id = ?")) {
29
+ return {
30
+ get: () => ({
31
+ id: 1,
32
+ slug: "agent-a",
33
+ agent_name: "Agent A",
34
+ description: "demo",
35
+ docs_content: "",
36
+ template_name: "default",
37
+ status: "normal",
38
+ usage_snapshot_json: "{}",
39
+ tags_json: "[]",
40
+ daily_limit: -1
41
+ })
42
+ };
43
+ }
44
+
45
+ if (sql.includes("SELECT * FROM agents ORDER BY updated_at DESC")) {
46
+ return {
47
+ all: () => insertedRows
48
+ };
49
+ }
50
+
51
+ if (sql.includes("SELECT d.id, d.username")) {
52
+ return {
53
+ all: (agentId) => permissions
54
+ .filter((item) => item.agent_id === agentId)
55
+ .map((item) => [...aiosUsers.values()].find((user) => user.id === item.aios_user_id))
56
+ .filter(Boolean)
57
+ };
58
+ }
59
+
60
+ if (sql.includes("SELECT s.slug")) {
61
+ return {
62
+ all: () => []
63
+ };
64
+ }
65
+
66
+ if (sql.includes("INSERT INTO aios_users")) {
67
+ return {
68
+ run: (username) => {
69
+ if (!aiosUsers.has(username)) {
70
+ aiosUsers.set(username, { id: aiosUsers.size + 11, username });
71
+ }
72
+ return { changes: 1 };
73
+ }
74
+ };
75
+ }
76
+
77
+ if (sql.includes("SELECT id") && sql.includes("FROM aios_users")) {
78
+ return {
79
+ get: (username) => {
80
+ const user = aiosUsers.get(username);
81
+ return user ? { id: user.id } : null;
82
+ },
83
+ all: () => [...aiosUsers.values()]
84
+ };
85
+ }
86
+
87
+ if (sql.includes("DELETE FROM agent_permissions WHERE agent_id = ?")) {
88
+ return {
89
+ run: (agentId) => {
90
+ for (let index = permissions.length - 1; index >= 0; index -= 1) {
91
+ if (permissions[index].agent_id === agentId) {
92
+ permissions.splice(index, 1);
93
+ }
94
+ }
95
+ return { changes: 1 };
96
+ }
97
+ };
98
+ }
99
+
100
+ if (sql.includes("INSERT INTO agent_permissions (agent_id, aios_user_id)")) {
101
+ return {
102
+ run: (agentId, aiosUserId) => {
103
+ const user = [...aiosUsers.values()].find((item) => item.id === aiosUserId);
104
+ if (user) {
105
+ permissions.push({
106
+ agent_id: agentId,
107
+ aios_user_id: aiosUserId
108
+ });
109
+ }
110
+ return { changes: 1 };
111
+ }
112
+ };
113
+ }
114
+
115
+ if (sql.includes("DELETE FROM agent_skill_bindings WHERE agent_id = ?")) {
116
+ return {
117
+ run: () => ({ changes: 1 })
118
+ };
119
+ }
120
+
121
+ if (sql.includes("SELECT id FROM skills WHERE slug = ?")) {
122
+ return {
123
+ get: (slug) => skills.get(slug) || null
124
+ };
125
+ }
126
+
127
+ if (sql.includes("INSERT INTO agent_skill_bindings (agent_id, skill_id)")) {
128
+ return {
129
+ run: () => ({ changes: 1 })
130
+ };
131
+ }
132
+
133
+ if (sql.includes("INSERT INTO agents")) {
134
+ return {
135
+ run: (...params) => {
136
+ if (failAgentInsert) {
137
+ throw new Error("db insert failed");
138
+ }
139
+ insertedRows.splice(0, insertedRows.length, {
140
+ id: 1,
141
+ slug: params[0],
142
+ agent_name: params[1],
143
+ description: params[2],
144
+ docs_content: params[3],
145
+ template_name: params[4],
146
+ status: params[5],
147
+ tags_json: params[6],
148
+ daily_limit: params[7],
149
+ remote_state_json: params[8],
150
+ created_at: params[9],
151
+ updated_at: params[10],
152
+ usage_snapshot_json: "{}"
153
+ });
154
+ return { lastInsertRowid: 1 };
155
+ }
156
+ };
157
+ }
158
+
159
+ if (sql.includes("UPDATE agents")) {
160
+ return {
161
+ run: (...params) => {
162
+ if (!insertedRows[0]) {
163
+ insertedRows.push({
164
+ id: 1,
165
+ slug: "agent-a",
166
+ template_name: "default",
167
+ usage_snapshot_json: "{}",
168
+ remote_state_json: "{}"
169
+ });
170
+ }
171
+ if (sql.includes("SET agent_name = ?, status = ?, remote_state_json = ?, updated_at = ?")) {
172
+ insertedRows[0] = {
173
+ ...insertedRows[0],
174
+ id: 1,
175
+ slug: "agent-a",
176
+ agent_name: params[0],
177
+ status: params[1],
178
+ remote_state_json: params[2],
179
+ updated_at: params[3]
180
+ };
181
+ return { changes: 1 };
182
+ }
183
+ insertedRows[0] = {
184
+ ...insertedRows[0],
185
+ id: 1,
186
+ slug: "agent-a",
187
+ agent_name: params[0],
188
+ description: params[1],
189
+ docs_content: params[2],
190
+ status: params[3],
191
+ tags_json: params[4],
192
+ daily_limit: params[5],
193
+ updated_at: params[6]
194
+ };
195
+ return { changes: 1 };
196
+ }
197
+ };
198
+ }
199
+
200
+ if (sql.includes("DELETE FROM agents WHERE id = ?")) {
201
+ return {
202
+ run: (agentId) => {
203
+ const index = insertedRows.findIndex((item) => Number(item.id) === Number(agentId));
204
+ if (index >= 0) {
205
+ insertedRows.splice(index, 1);
206
+ }
207
+ return { changes: index >= 0 ? 1 : 0 };
208
+ }
209
+ };
210
+ }
211
+
212
+ return {
213
+ get: () => null,
214
+ all: () => [],
215
+ run: () => ({ lastInsertRowid: 1 })
216
+ };
217
+ }
218
+ };
219
+ }
220
+
221
+ it("rejects model configuration during agent creation", async () => {
222
+ const rpcCalls = [];
223
+ const service = new AgentService({
224
+ db: createDb(),
225
+ rpcClient: {
226
+ async call(action, params) {
227
+ rpcCalls.push({ action, params });
228
+ if (action === "agent.get") {
229
+ return {
230
+ agentId: "agent-a",
231
+ name: "Agent A",
232
+ status: "normal",
233
+ inboundTopic: "topic/in/agent-a",
234
+ outboundTopic: "topic/out/agent-a"
235
+ };
236
+ }
237
+ return { ok: true };
238
+ }
239
+ },
240
+ objectStorage: {}
241
+ });
242
+
243
+ await expectAsync(service.createAgent({
244
+ slug: "agent-a",
245
+ template_name: "default",
246
+ model_primary: "gpt-4.1-mini"
247
+ })).toBeRejectedWithError("当前版本不支持在创建时配置模型");
248
+
249
+ expect(rpcCalls).toEqual([]);
250
+ });
251
+
252
+ it("rejects invalid slug during agent creation", async () => {
253
+ const service = new AgentService({
254
+ db: createDb(),
255
+ rpcClient: {
256
+ async call() {
257
+ return { ok: true };
258
+ }
259
+ },
260
+ objectStorage: {}
261
+ });
262
+
263
+ await expectAsync(service.createAgent({
264
+ slug: "Agent_A",
265
+ template_name: "default"
266
+ })).toBeRejectedWithError(/slug 规则/);
267
+ });
268
+
269
+ it("stores agent_name and synthesized topics during agent creation", async () => {
270
+ const rpcCalls = [];
271
+ const db = createDb();
272
+ const service = new AgentService({
273
+ db,
274
+ rpcClient: {
275
+ isConfigured() {
276
+ return true;
277
+ },
278
+ async call(action, params) {
279
+ rpcCalls.push({ action, params });
280
+ if (action === "agent.get") {
281
+ return {
282
+ agentId: "agent-a",
283
+ name: "Agent A",
284
+ status: "normal",
285
+ inboundTopic: "topic/in/agent-a",
286
+ outboundTopic: "topic/out/agent-a"
287
+ };
288
+ }
289
+ return { ok: true };
290
+ }
291
+ },
292
+ objectStorage: {},
293
+ env: {
294
+ mqtt: {
295
+ agentInboundTopicTemplate: "topic/in/{agentId}",
296
+ agentOutboundTopicTemplate: "topic/out/{agentId}"
297
+ }
298
+ }
299
+ });
300
+
301
+ const created = await service.createAgent({
302
+ slug: "agent-a",
303
+ agent_name: "Agent A",
304
+ template_name: "default"
305
+ });
306
+
307
+ expect(created.status).toBe("normal");
308
+ expect(created.health).toBe("normal");
309
+ expect(created.agent_name).toBe("Agent A");
310
+ expect(db.insertedRows[0]?.status).toBe("normal");
311
+ expect(created.remote_state).toEqual({
312
+ agentId: "agent-a",
313
+ name: "Agent A",
314
+ inboundTopic: "topic/in/agent-a",
315
+ outboundTopic: "topic/out/agent-a",
316
+ });
317
+ expect(rpcCalls.map((item) => item.action)).toEqual(["agent.create"]);
318
+ expect(rpcCalls[0].params).toEqual(jasmine.objectContaining({
319
+ agentId: "agent-a",
320
+ name: "Agent A",
321
+ agentName: "Agent A",
322
+ templateName: "default",
323
+ restart: true
324
+ }));
325
+ });
326
+
327
+ it("does not insert the agent before agent.create succeeds", async () => {
328
+ const db = createDb();
329
+ const observedInsertedRowCounts = [];
330
+ const service = new AgentService({
331
+ db,
332
+ rpcClient: {
333
+ async call(action) {
334
+ if (action === "agent.create") {
335
+ observedInsertedRowCounts.push(db.insertedRows.length);
336
+ }
337
+ return { ok: true };
338
+ }
339
+ },
340
+ objectStorage: {},
341
+ env: {
342
+ mqtt: {
343
+ agentInboundTopicTemplate: "topic/in/{agentId}",
344
+ agentOutboundTopicTemplate: "topic/out/{agentId}"
345
+ }
346
+ }
347
+ });
348
+
349
+ const created = await service.createAgent({
350
+ slug: "agent-a",
351
+ agent_name: "Agent A",
352
+ template_name: "default"
353
+ });
354
+
355
+ expect(created.slug).toBe("agent-a");
356
+ expect(created.status).toBe("normal");
357
+ expect(observedInsertedRowCounts).toEqual([0]);
358
+ expect(db.insertedRows.length).toBe(1);
359
+ });
360
+
361
+ it("creates agent-user bindings from users field", async () => {
362
+ const db = createDb();
363
+ const service = new AgentService({
364
+ db,
365
+ rpcClient: {
366
+ async call() {
367
+ return { ok: true };
368
+ }
369
+ },
370
+ objectStorage: {},
371
+ env: {
372
+ mqtt: {
373
+ agentInboundTopicTemplate: "topic/in/{agentId}",
374
+ agentOutboundTopicTemplate: "topic/out/{agentId}"
375
+ }
376
+ }
377
+ });
378
+
379
+ const created = await service.createAgent({
380
+ slug: "agent-a",
381
+ template_name: "default",
382
+ users: ["zhangsan", "lisi"]
383
+ });
384
+
385
+ expect(created.permissions).toEqual([
386
+ { id: 11, username: "zhangsan" },
387
+ { id: 12, username: "lisi" }
388
+ ]);
389
+ });
390
+
391
+ it("does not insert a local agent record when agent.create fails", async () => {
392
+ const db = createDb();
393
+ const service = new AgentService({
394
+ db,
395
+ rpcClient: {
396
+ async call(action) {
397
+ if (action === "agent.create") {
398
+ throw new Error("create failed");
399
+ }
400
+ return { ok: true };
401
+ }
402
+ },
403
+ objectStorage: {},
404
+ env: {
405
+ mqtt: {
406
+ agentInboundTopicTemplate: "topic/in/{agentId}",
407
+ agentOutboundTopicTemplate: "topic/out/{agentId}"
408
+ }
409
+ }
410
+ });
411
+
412
+ await expectAsync(service.createAgent({
413
+ slug: "agent-a",
414
+ agent_name: "Agent A",
415
+ template_name: "default"
416
+ })).toBeRejectedWithError("create failed");
417
+
418
+ expect(db.insertedRows).toEqual([]);
419
+ });
420
+
421
+ it("does not insert a local agent record when agent.create times out", async () => {
422
+ const db = createDb();
423
+ const service = new AgentService({
424
+ db,
425
+ rpcClient: {
426
+ async call(action) {
427
+ if (action === "agent.create") {
428
+ const error = new Error("管理动作超时:agent.create");
429
+ error.details = { timeout: true, action };
430
+ throw error;
431
+ }
432
+ return { ok: true };
433
+ }
434
+ },
435
+ objectStorage: {},
436
+ env: {
437
+ mqtt: {
438
+ agentInboundTopicTemplate: "topic/in/{agentId}",
439
+ agentOutboundTopicTemplate: "topic/out/{agentId}"
440
+ }
441
+ }
442
+ });
443
+
444
+ await expectAsync(service.createAgent({
445
+ slug: "agent-a",
446
+ agent_name: "Agent A",
447
+ template_name: "default"
448
+ })).toBeRejectedWithError("管理动作超时:agent.create");
449
+
450
+ expect(db.insertedRows).toEqual([]);
451
+ });
452
+
453
+ it("deletes remote agent when docs update fails before local insert", async () => {
454
+ const db = createDb();
455
+ const rpcCalls = [];
456
+ const service = new AgentService({
457
+ db,
458
+ rpcClient: {
459
+ async call(action, params) {
460
+ rpcCalls.push({ action, params });
461
+ if (action === "agent.docs.update") {
462
+ throw new Error("docs failed");
463
+ }
464
+ return { ok: true };
465
+ }
466
+ },
467
+ objectStorage: {},
468
+ env: {
469
+ mqtt: {
470
+ agentInboundTopicTemplate: "topic/in/{agentId}",
471
+ agentOutboundTopicTemplate: "topic/out/{agentId}"
472
+ }
473
+ }
474
+ });
475
+
476
+ await expectAsync(service.createAgent({
477
+ slug: "agent-a",
478
+ agent_name: "Agent A",
479
+ template_name: "default",
480
+ docs_content: "hello"
481
+ })).toBeRejectedWithError("docs failed");
482
+
483
+ expect(db.insertedRows).toEqual([]);
484
+ expect(rpcCalls.map((item) => item.action)).toEqual([
485
+ "agent.create",
486
+ "agent.docs.update",
487
+ "agent.delete"
488
+ ]);
489
+ expect(rpcCalls[2].params).toEqual({
490
+ agentId: "agent-a",
491
+ restart: true
492
+ });
493
+ });
494
+
495
+ it("deletes remote agent when local insert fails after agent.create succeeds", async () => {
496
+ const db = createDb({ failAgentInsert: true });
497
+ const rpcCalls = [];
498
+ const service = new AgentService({
499
+ db,
500
+ rpcClient: {
501
+ async call(action, params) {
502
+ rpcCalls.push({ action, params });
503
+ return { ok: true };
504
+ }
505
+ },
506
+ objectStorage: {},
507
+ env: {
508
+ mqtt: {
509
+ agentInboundTopicTemplate: "topic/in/{agentId}",
510
+ agentOutboundTopicTemplate: "topic/out/{agentId}"
511
+ }
512
+ }
513
+ });
514
+
515
+ await expectAsync(service.createAgent({
516
+ slug: "agent-a",
517
+ agent_name: "Agent A",
518
+ template_name: "default"
519
+ })).toBeRejectedWithError("db insert failed");
520
+
521
+ expect(db.insertedRows).toEqual([]);
522
+ expect(rpcCalls.map((item) => item.action)).toEqual([
523
+ "agent.create",
524
+ "agent.delete"
525
+ ]);
526
+ expect(rpcCalls[1].params).toEqual({
527
+ agentId: "agent-a",
528
+ restart: true
529
+ });
530
+ });
531
+
532
+ it("rejects model configuration during agent update", async () => {
533
+ const rpcCalls = [];
534
+ const service = new AgentService({
535
+ db: createDb(),
536
+ rpcClient: {
537
+ async call(action, params) {
538
+ rpcCalls.push({ action, params });
539
+ return { ok: true };
540
+ }
541
+ },
542
+ objectStorage: {}
543
+ });
544
+
545
+ await expectAsync(service.updateAgent(1, {
546
+ model_primary: "gpt-4.1-mini"
547
+ })).toBeRejectedWithError("当前版本不支持在编辑时配置模型");
548
+
549
+ expect(rpcCalls).toEqual([]);
550
+ });
551
+
552
+ it("updates agent-user bindings from users field", async () => {
553
+ const db = createDb();
554
+ db.permissions.push({
555
+ agent_id: 1,
556
+ aios_user_id: 11
557
+ });
558
+
559
+ const service = new AgentService({
560
+ db,
561
+ rpcClient: {
562
+ async call() {
563
+ return { ok: true };
564
+ }
565
+ },
566
+ objectStorage: {}
567
+ });
568
+
569
+ const updated = await service.updateAgent(1, {
570
+ users: ["lisi"]
571
+ });
572
+
573
+ expect(updated.permissions).toEqual([
574
+ { id: 12, username: "lisi" }
575
+ ]);
576
+ });
577
+
578
+ it("keeps agent-user bindings when update payload omits assignment fields", async () => {
579
+ const db = createDb();
580
+ db.permissions.push({
581
+ agent_id: 1,
582
+ aios_user_id: 11
583
+ });
584
+
585
+ const service = new AgentService({
586
+ db,
587
+ rpcClient: {
588
+ async call() {
589
+ return { ok: true };
590
+ }
591
+ },
592
+ objectStorage: {}
593
+ });
594
+
595
+ const updated = await service.updateAgent(1, {
596
+ agent_name: "Agent A Updated"
597
+ });
598
+
599
+ expect(updated.agent_name).toBe("Agent A Updated");
600
+ expect(updated.permissions).toEqual([
601
+ { id: 11, username: "zhangsan" }
602
+ ]);
603
+ });
604
+
605
+ it("deletes local record when remote agent is already missing", async () => {
606
+ const db = createDb();
607
+ db.insertedRows.push({
608
+ id: 1,
609
+ slug: "agent-a",
610
+ agent_name: "Agent A",
611
+ description: "",
612
+ docs_content: "",
613
+ template_name: "default",
614
+ status: "normal",
615
+ tags_json: "[]",
616
+ daily_limit: -1,
617
+ usage_snapshot_json: "{}",
618
+ remote_state_json: "{}",
619
+ created_at: "2026-05-26T00:00:00.000Z",
620
+ updated_at: "2026-05-26T00:00:00.000Z"
621
+ });
622
+
623
+ const service = new AgentService({
624
+ db,
625
+ rpcClient: {
626
+ async call(action) {
627
+ if (action === "agent.delete") {
628
+ const error = new Error("agent not found: agent-a");
629
+ error.status = 404;
630
+ error.code = "not_found";
631
+ throw error;
632
+ }
633
+ return { ok: true };
634
+ }
635
+ },
636
+ objectStorage: {}
637
+ });
638
+
639
+ const result = await service.deleteAgent(1);
640
+
641
+ expect(result).toEqual({ ok: true });
642
+ expect(db.insertedRows).toEqual([]);
643
+ });
644
+
645
+ it("imports AIOS users from newline separated content", () => {
646
+ const db = createDb();
647
+ const service = new AgentService({
648
+ db,
649
+ rpcClient: {},
650
+ objectStorage: {}
651
+ });
652
+
653
+ const result = service.importAiosUsers("zhangsan\nwangwu\nzhaoliu\n");
654
+
655
+ expect(result.total).toBe(3);
656
+ expect(result.created).toBe(2);
657
+ expect(result.items).toEqual([
658
+ { username: "zhangsan", existed: true },
659
+ { username: "wangwu", existed: false },
660
+ { username: "zhaoliu", existed: false }
661
+ ]);
662
+ });