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.
- package/.env.json +21 -0
- package/README.md +257 -0
- package/data/management-console.db +0 -0
- package/data/management-console.db-shm +0 -0
- package/data/management-console.db-wal +0 -0
- package/dist/assets/index-CV_wjCAG.js +464 -0
- package/dist/assets/index-DfMPB0eV.css +1 -0
- package/dist/index.html +13 -0
- package/docs/spec.md +199 -0
- package/index.html +12 -0
- package/package.json +37 -0
- package/scripts/reset-kernel.js +59 -0
- package/scripts/reset-password.js +22 -0
- package/server/fakes.js +57 -0
- package/server/index.js +21 -0
- package/server/src/api/middleware/auth.js +29 -0
- package/server/src/api/middleware/internal.js +44 -0
- package/server/src/api/routes/index.js +677 -0
- package/server/src/app.js +90 -0
- package/server/src/background/index.js +106 -0
- package/server/src/background/protocol.js +15 -0
- package/server/src/config/env.js +90 -0
- package/server/src/db/index.js +501 -0
- package/server/src/infra/mqtt/management-rpc-client.js +213 -0
- package/server/src/infra/providers/hzg-provider-client.js +39 -0
- package/server/src/infra/s3/object-storage.js +97 -0
- package/server/src/services/agent-quota.js +54 -0
- package/server/src/services/agent-service.js +696 -0
- package/server/src/services/agent-status-sync-service.js +132 -0
- package/server/src/services/audit-log-service.js +39 -0
- package/server/src/services/auth-service.js +153 -0
- package/server/src/services/catalog-sync-service.js +712 -0
- package/server/src/services/external-service.js +308 -0
- package/server/src/services/kernel-reset-service.js +86 -0
- package/server/src/services/portal-service.js +555 -0
- package/server/src/services/system-service.js +580 -0
- package/server/src/services/topic-ping-service.js +282 -0
- package/server/src/utils/errors.js +36 -0
- package/server/src/utils/security.js +22 -0
- package/server/test/agent-service-alignment.test.js +316 -0
- package/server/test/agent-service-create.test.js +662 -0
- package/server/test/agent-status-sync-service.test.js +167 -0
- package/server/test/agent-update-audit.test.js +63 -0
- package/server/test/auth-middleware.test.js +71 -0
- package/server/test/background-services.test.js +160 -0
- package/server/test/catalog-sync-service.test.js +920 -0
- package/server/test/db-reset-migration.test.js +123 -0
- package/server/test/env-config.test.js +68 -0
- package/server/test/external-service.test.js +380 -0
- package/server/test/hzg-provider-client.test.js +50 -0
- package/server/test/internal-auth-middleware.test.js +66 -0
- package/server/test/kernel-reset-service.test.js +112 -0
- package/server/test/management-rpc-client.test.js +105 -0
- package/server/test/portal-service-access-tokens.test.js +121 -0
- package/server/test/portal-service-alignment.test.js +318 -0
- package/server/test/portal-service-management-logs.test.js +114 -0
- package/server/test/reset-kernel-cli.test.js +23 -0
- package/server/test/service-api-auth-middleware.test.js +59 -0
- package/server/test/system-service-alignment.test.js +265 -0
- package/server/test/topic-ping-service.test.js +182 -0
- package/server/test/usage-refresh-audit-route.test.js +82 -0
- package/src/App.jsx +1 -0
- package/src/api.js +1 -0
- package/src/app/App.jsx +346 -0
- package/src/app/api-client.js +112 -0
- package/src/components/AppShell.jsx +117 -0
- package/src/components/CardTitleWithReload.jsx +20 -0
- package/src/components/DeleteActionButton.jsx +31 -0
- package/src/main.jsx +14 -0
- package/src/pages/AgentsPage.jsx +647 -0
- package/src/pages/AiosUsersPage.jsx +151 -0
- package/src/pages/DashboardPage.jsx +72 -0
- package/src/pages/LoginPage.jsx +41 -0
- package/src/pages/SettingsPage.jsx +431 -0
- package/src/pages/SkillsPage.jsx +175 -0
- package/src/pages/SystemLogsPage.jsx +349 -0
- package/src/pages/SystemsPage.jsx +498 -0
- package/src/pages/TemplatesPage.jsx +207 -0
- package/src/pages/UserManagementPage.jsx +25 -0
- package/src/pages/UsersPage.jsx +192 -0
- package/src/pages/system-logs/SystemLogsTabs.jsx +362 -0
- package/src/styles.css +222 -0
- package/src/utils/format.js +63 -0
- package/test/.reports/fast-2026-05-25T08-32-39-420Z.json +299 -0
- package/test/integration/common.js +208 -0
- package/test/integration/fast.js +135 -0
- package/test/integration/full.js +306 -0
- package/test/run-browser-e2e.js +212 -0
- package/test/run-jasmine.js +21 -0
- package/test/setup.js +1 -0
- package/vite.config.js +12 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { AgentStatusSyncService } from "../src/services/agent-status-sync-service.js";
|
|
2
|
+
|
|
3
|
+
function createDb(rows = []) {
|
|
4
|
+
const statements = [];
|
|
5
|
+
const items = rows.map((row) => ({ ...row }));
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
statements,
|
|
9
|
+
items,
|
|
10
|
+
exec(sql) {
|
|
11
|
+
statements.push({ type: "exec", sql });
|
|
12
|
+
},
|
|
13
|
+
prepare(sql) {
|
|
14
|
+
if (sql.includes("SELECT *") && sql.includes("FROM agents")) {
|
|
15
|
+
return {
|
|
16
|
+
all: () => items
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (sql.includes("INSERT INTO agents")) {
|
|
21
|
+
return {
|
|
22
|
+
run: (
|
|
23
|
+
slug,
|
|
24
|
+
agentName,
|
|
25
|
+
description,
|
|
26
|
+
docsContent,
|
|
27
|
+
templateName,
|
|
28
|
+
status,
|
|
29
|
+
tagsJson,
|
|
30
|
+
dailyLimit,
|
|
31
|
+
usageSnapshotJson,
|
|
32
|
+
remoteStateJson,
|
|
33
|
+
createdAt,
|
|
34
|
+
updatedAt
|
|
35
|
+
) => {
|
|
36
|
+
items.push({
|
|
37
|
+
id: items.length + 1,
|
|
38
|
+
slug,
|
|
39
|
+
agent_name: agentName,
|
|
40
|
+
description,
|
|
41
|
+
docs_content: docsContent,
|
|
42
|
+
template_name: templateName,
|
|
43
|
+
status,
|
|
44
|
+
tags_json: tagsJson,
|
|
45
|
+
daily_limit: dailyLimit,
|
|
46
|
+
usage_snapshot_json: usageSnapshotJson,
|
|
47
|
+
remote_state_json: remoteStateJson,
|
|
48
|
+
created_at: createdAt,
|
|
49
|
+
updated_at: updatedAt
|
|
50
|
+
});
|
|
51
|
+
return { lastInsertRowid: items.length };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (sql.includes("DELETE FROM agents WHERE id = ?")) {
|
|
57
|
+
return {
|
|
58
|
+
run: (id) => {
|
|
59
|
+
const index = items.findIndex((item) => item.id === id);
|
|
60
|
+
if (index >= 0) {
|
|
61
|
+
items.splice(index, 1);
|
|
62
|
+
}
|
|
63
|
+
return { changes: index >= 0 ? 1 : 0 };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (sql.includes("UPDATE agents") && sql.includes("SET agent_name = ?, status = ?, remote_state_json = ?, updated_at = ?")) {
|
|
69
|
+
return {
|
|
70
|
+
run: (agentName, status, remoteStateJson, updatedAt, id) => {
|
|
71
|
+
const target = items.find((item) => item.id === id);
|
|
72
|
+
if (target) {
|
|
73
|
+
target.agent_name = agentName;
|
|
74
|
+
target.status = status;
|
|
75
|
+
target.remote_state_json = remoteStateJson;
|
|
76
|
+
target.updated_at = updatedAt;
|
|
77
|
+
}
|
|
78
|
+
statements.push({ type: "run", sql, args: [agentName, status, remoteStateJson, updatedAt, id] });
|
|
79
|
+
return { changes: target ? 1 : 0 };
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
all: () => [],
|
|
86
|
+
get: () => null,
|
|
87
|
+
run: () => ({ changes: 0, lastInsertRowid: 1 })
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
it("refreshes local agent status through agent.list", async () => {
|
|
94
|
+
const db = createDb([{
|
|
95
|
+
id: 1,
|
|
96
|
+
slug: "agent-a",
|
|
97
|
+
agent_name: "Agent A",
|
|
98
|
+
status: "normal",
|
|
99
|
+
remote_state_json: "{}"
|
|
100
|
+
}]);
|
|
101
|
+
const rpcCalls = [];
|
|
102
|
+
const service = new AgentStatusSyncService({
|
|
103
|
+
db,
|
|
104
|
+
rpcClient: {
|
|
105
|
+
isConfigured() {
|
|
106
|
+
return true;
|
|
107
|
+
},
|
|
108
|
+
async call(action, params) {
|
|
109
|
+
rpcCalls.push({ action, params });
|
|
110
|
+
return {
|
|
111
|
+
items: [{
|
|
112
|
+
agentId: "agent-a",
|
|
113
|
+
name: "Agent A",
|
|
114
|
+
status: "active",
|
|
115
|
+
inboundTopic: "topic/in/agent-a",
|
|
116
|
+
outboundTopic: "topic/out/agent-a"
|
|
117
|
+
}]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
auditLogService: {
|
|
122
|
+
write() {}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = await service.refreshStatuses({ trigger: "scheduled" });
|
|
127
|
+
|
|
128
|
+
expect(result.status).toBe("success");
|
|
129
|
+
expect(result.updated_agents).toBe(1);
|
|
130
|
+
expect(rpcCalls).toEqual([{
|
|
131
|
+
action: "agent.list",
|
|
132
|
+
params: {}
|
|
133
|
+
}]);
|
|
134
|
+
expect(db.items[0].status).toBe("normal");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("keeps local agents when status sync does not see them remotely", async () => {
|
|
138
|
+
const db = createDb([{
|
|
139
|
+
id: 1,
|
|
140
|
+
slug: "agent-a",
|
|
141
|
+
agent_name: "Agent A",
|
|
142
|
+
status: "normal",
|
|
143
|
+
remote_state_json: "{}"
|
|
144
|
+
}]);
|
|
145
|
+
const service = new AgentStatusSyncService({
|
|
146
|
+
db,
|
|
147
|
+
rpcClient: {
|
|
148
|
+
isConfigured() {
|
|
149
|
+
return true;
|
|
150
|
+
},
|
|
151
|
+
async call() {
|
|
152
|
+
return {
|
|
153
|
+
items: []
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
auditLogService: {
|
|
158
|
+
write() {}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = await service.refreshStatuses({ trigger: "scheduled" });
|
|
163
|
+
|
|
164
|
+
expect(result.status).toBe("success");
|
|
165
|
+
expect(result.updated_agents).toBe(0);
|
|
166
|
+
expect(db.items.map((item) => item.slug)).toEqual(["agent-a"]);
|
|
167
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const { buildAgentUpdateAudit } = await import("../src/api/routes/index.js");
|
|
2
|
+
|
|
3
|
+
function makeAgent(overrides = {}) {
|
|
4
|
+
return {
|
|
5
|
+
id: 1,
|
|
6
|
+
slug: "agent-a",
|
|
7
|
+
agent_name: "Agent A",
|
|
8
|
+
status: "active",
|
|
9
|
+
daily_limit: -1,
|
|
10
|
+
permissions: [
|
|
11
|
+
{ username: "zhangsan" },
|
|
12
|
+
{ username: "lisi" }
|
|
13
|
+
],
|
|
14
|
+
...overrides
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it("writes assignment-specific audit details with added and removed users", () => {
|
|
19
|
+
const audit = buildAgentUpdateAudit(makeAgent(), makeAgent({
|
|
20
|
+
permissions: [
|
|
21
|
+
{ username: "lisi" },
|
|
22
|
+
{ username: "wangwu" }
|
|
23
|
+
]
|
|
24
|
+
}), {
|
|
25
|
+
permission_usernames: ["lisi", "wangwu"]
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(audit).toEqual({
|
|
29
|
+
action: "编辑数字员工人员分配",
|
|
30
|
+
detail: "编辑数字员工人员分配:ID=1,名称=agent-a;添加:wangwu;移除:zhangsan"
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("keeps agent-edit audit action when only base agent fields change", () => {
|
|
35
|
+
const audit = buildAgentUpdateAudit(makeAgent(), makeAgent({
|
|
36
|
+
agent_name: "Agent A Updated"
|
|
37
|
+
}), {
|
|
38
|
+
agent_name: "Agent A Updated"
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(audit).toEqual({
|
|
42
|
+
action: "编辑数字员工",
|
|
43
|
+
detail: "编辑数字员工:ID=1,名称=agent-a"
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("includes assignment diff when base agent fields and assignments change together", () => {
|
|
48
|
+
const audit = buildAgentUpdateAudit(makeAgent(), makeAgent({
|
|
49
|
+
agent_name: "Agent A Updated",
|
|
50
|
+
permissions: [
|
|
51
|
+
{ username: "lisi" },
|
|
52
|
+
{ username: "wangwu" }
|
|
53
|
+
]
|
|
54
|
+
}), {
|
|
55
|
+
agent_name: "Agent A Updated",
|
|
56
|
+
permission_usernames: ["lisi", "wangwu"]
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(audit).toEqual({
|
|
60
|
+
action: "编辑数字员工",
|
|
61
|
+
detail: "编辑数字员工:ID=1,名称=agent-a;人员分配添加:wangwu;人员分配移除:zhangsan"
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createAuthMiddleware } from "../src/api/middleware/auth.js";
|
|
2
|
+
|
|
3
|
+
function runMiddleware(middleware, { headers = {}, remoteAddress = "" } = {}) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const req = {
|
|
6
|
+
headers,
|
|
7
|
+
socket: {
|
|
8
|
+
remoteAddress
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
middleware(req, {}, (error) => resolve({
|
|
13
|
+
error: error ?? null,
|
|
14
|
+
req
|
|
15
|
+
}));
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
it("injects local api user for localhost requests", async () => {
|
|
20
|
+
const middleware = createAuthMiddleware({
|
|
21
|
+
getLocalApiUser() {
|
|
22
|
+
return {
|
|
23
|
+
id: 1,
|
|
24
|
+
username: "aios",
|
|
25
|
+
role: "aios-admin"
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
getSessionUser() {
|
|
29
|
+
throw new Error("should not call token auth on localhost");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await runMiddleware(middleware, {
|
|
34
|
+
remoteAddress: "127.0.0.1"
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(result.error).toBeNull();
|
|
38
|
+
expect(result.req.authToken).toBe("localhost");
|
|
39
|
+
expect(result.req.currentUser).toEqual({
|
|
40
|
+
id: 1,
|
|
41
|
+
username: "aios",
|
|
42
|
+
role: "aios-admin"
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("requires bearer token for non-localhost requests", async () => {
|
|
47
|
+
const middleware = createAuthMiddleware({
|
|
48
|
+
getLocalApiUser() {
|
|
49
|
+
return null;
|
|
50
|
+
},
|
|
51
|
+
getSessionUser() {
|
|
52
|
+
return {
|
|
53
|
+
id: 1
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const missing = await runMiddleware(middleware, {
|
|
59
|
+
remoteAddress: "10.0.0.8"
|
|
60
|
+
});
|
|
61
|
+
expect(missing.error.status).toBe(401);
|
|
62
|
+
|
|
63
|
+
const ok = await runMiddleware(middleware, {
|
|
64
|
+
remoteAddress: "10.0.0.8",
|
|
65
|
+
headers: {
|
|
66
|
+
authorization: "Bearer token-1"
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
expect(ok.error).toBeNull();
|
|
70
|
+
expect(ok.req.authToken).toBe("token-1");
|
|
71
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { startBackgroundServices } from "../src/background/index.js";
|
|
2
|
+
|
|
3
|
+
it("runs startup sync and schedules the global one-minute task clock", async () => {
|
|
4
|
+
const timeouts = [];
|
|
5
|
+
const intervals = [];
|
|
6
|
+
const originalSetTimeout = globalThis.setTimeout;
|
|
7
|
+
const originalClearTimeout = globalThis.clearTimeout;
|
|
8
|
+
const originalSetInterval = globalThis.setInterval;
|
|
9
|
+
const originalClearInterval = globalThis.clearInterval;
|
|
10
|
+
globalThis.setTimeout = ((fn, delay) => {
|
|
11
|
+
const handle = { fn, delay };
|
|
12
|
+
timeouts.push(handle);
|
|
13
|
+
return handle;
|
|
14
|
+
});
|
|
15
|
+
globalThis.clearTimeout = (() => {});
|
|
16
|
+
globalThis.setInterval = ((fn, delay) => {
|
|
17
|
+
const handle = { fn, delay };
|
|
18
|
+
intervals.push(handle);
|
|
19
|
+
return handle;
|
|
20
|
+
});
|
|
21
|
+
globalThis.clearInterval = (() => {});
|
|
22
|
+
|
|
23
|
+
const calls = [];
|
|
24
|
+
const startupResolvers = [];
|
|
25
|
+
const createStartupTask = (label) => async ({ trigger }) => {
|
|
26
|
+
calls.push(`${label}:${trigger}`);
|
|
27
|
+
if (trigger !== "startup") {
|
|
28
|
+
return { status: "success" };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await new Promise((resolve) => {
|
|
32
|
+
startupResolvers.push(resolve);
|
|
33
|
+
});
|
|
34
|
+
return { status: "success" };
|
|
35
|
+
};
|
|
36
|
+
const background = startBackgroundServices({
|
|
37
|
+
env: {
|
|
38
|
+
mqtt: {}
|
|
39
|
+
},
|
|
40
|
+
services: {
|
|
41
|
+
rpcClient: {
|
|
42
|
+
async start() {
|
|
43
|
+
calls.push("rpc.start");
|
|
44
|
+
},
|
|
45
|
+
async stop() {
|
|
46
|
+
calls.push("rpc.stop");
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
catalogSyncService: {
|
|
50
|
+
syncAgentsTask: createStartupTask("sync-agents"),
|
|
51
|
+
syncSkillsTask: createStartupTask("sync-skills"),
|
|
52
|
+
syncTemplatesTask: createStartupTask("sync-templates"),
|
|
53
|
+
syncSystemsTask: createStartupTask("sync-systems"),
|
|
54
|
+
refreshAgentUsage: createStartupTask("usage")
|
|
55
|
+
},
|
|
56
|
+
agentStatusSyncService: {},
|
|
57
|
+
systemService: {},
|
|
58
|
+
hzgProviderClient: {}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
64
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
65
|
+
|
|
66
|
+
expect(calls).toContain("rpc.start");
|
|
67
|
+
expect(calls.filter((entry) => entry.endsWith(":startup"))).toEqual([
|
|
68
|
+
"sync-agents:startup"
|
|
69
|
+
]);
|
|
70
|
+
expect(startupResolvers.length).toBe(1);
|
|
71
|
+
expect(intervals.length).toBe(0);
|
|
72
|
+
|
|
73
|
+
for (const resolve of startupResolvers) {
|
|
74
|
+
resolve();
|
|
75
|
+
}
|
|
76
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
77
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
78
|
+
expect(calls.filter((entry) => entry.endsWith(":startup"))).toEqual([
|
|
79
|
+
"sync-agents:startup",
|
|
80
|
+
"usage:startup"
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
for (const resolve of startupResolvers.slice(1)) {
|
|
84
|
+
resolve();
|
|
85
|
+
}
|
|
86
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
87
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
88
|
+
for (const resolve of startupResolvers.slice(2)) {
|
|
89
|
+
resolve();
|
|
90
|
+
}
|
|
91
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
92
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
93
|
+
for (const resolve of startupResolvers.slice(3)) {
|
|
94
|
+
resolve();
|
|
95
|
+
}
|
|
96
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
97
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
98
|
+
for (const resolve of startupResolvers.slice(4)) {
|
|
99
|
+
resolve();
|
|
100
|
+
}
|
|
101
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
102
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
103
|
+
|
|
104
|
+
expect(calls.filter((entry) => entry.endsWith(":startup"))).toEqual([
|
|
105
|
+
"sync-agents:startup",
|
|
106
|
+
"usage:startup",
|
|
107
|
+
"sync-skills:startup",
|
|
108
|
+
"sync-templates:startup",
|
|
109
|
+
"sync-systems:startup"
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
expect(timeouts.length).toBe(1);
|
|
113
|
+
expect(timeouts[0].delay).toBe(5 * 60 * 1000);
|
|
114
|
+
expect(intervals.length).toBe(0);
|
|
115
|
+
|
|
116
|
+
const startupScheduler = timeouts[0];
|
|
117
|
+
await startupScheduler.fn();
|
|
118
|
+
|
|
119
|
+
expect(intervals.length).toBe(1);
|
|
120
|
+
expect(intervals[0].delay).toBe(60 * 1000);
|
|
121
|
+
|
|
122
|
+
const firstScheduledCalls = calls.filter((entry) => [
|
|
123
|
+
"usage:scheduled",
|
|
124
|
+
"sync-agents:scheduled",
|
|
125
|
+
"sync-skills:scheduled",
|
|
126
|
+
"sync-templates:scheduled",
|
|
127
|
+
"sync-systems:scheduled"
|
|
128
|
+
].includes(entry));
|
|
129
|
+
expect(firstScheduledCalls).toEqual([
|
|
130
|
+
"sync-agents:scheduled"
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
const scheduler = intervals[0];
|
|
134
|
+
await scheduler.fn();
|
|
135
|
+
await scheduler.fn();
|
|
136
|
+
await scheduler.fn();
|
|
137
|
+
await scheduler.fn();
|
|
138
|
+
|
|
139
|
+
const scheduledCalls = calls.filter((entry) => [
|
|
140
|
+
"usage:scheduled",
|
|
141
|
+
"sync-agents:scheduled",
|
|
142
|
+
"sync-skills:scheduled",
|
|
143
|
+
"sync-templates:scheduled",
|
|
144
|
+
"sync-systems:scheduled"
|
|
145
|
+
].includes(entry));
|
|
146
|
+
expect(scheduledCalls).toEqual([
|
|
147
|
+
"sync-agents:scheduled",
|
|
148
|
+
"usage:scheduled",
|
|
149
|
+
"sync-skills:scheduled",
|
|
150
|
+
"sync-templates:scheduled",
|
|
151
|
+
"sync-systems:scheduled"
|
|
152
|
+
]);
|
|
153
|
+
} finally {
|
|
154
|
+
await background.stop();
|
|
155
|
+
globalThis.setTimeout = originalSetTimeout;
|
|
156
|
+
globalThis.clearTimeout = originalClearTimeout;
|
|
157
|
+
globalThis.setInterval = originalSetInterval;
|
|
158
|
+
globalThis.clearInterval = originalClearInterval;
|
|
159
|
+
}
|
|
160
|
+
});
|