@elizaos/plugin-knowledge 1.0.0-beta.73 → 1.0.0-beta.76

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/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  convertPdfToTextFromBuffer,
3
3
  extractTextFromFileBuffer,
4
+ fetchUrlContent,
4
5
  isBinaryContentType,
5
6
  loadDocsFromPath
6
- } from "./chunk-LHJERZV7.js";
7
+ } from "./chunk-HUTF5APY.js";
7
8
 
8
9
  // src/index.ts
9
10
  import { logger as logger6 } from "@elizaos/core";
@@ -2973,83 +2974,163 @@ async function uploadKnowledgeHandler(req, res, runtime) {
2973
2974
  if (!service) {
2974
2975
  return sendError(res, 500, "SERVICE_NOT_FOUND", "KnowledgeService not found");
2975
2976
  }
2976
- const files = req.files;
2977
- if (!files || files.length === 0) {
2978
- return sendError(res, 400, "NO_FILES", "No files uploaded");
2977
+ const isMultipartRequest = req.files && Object.keys(req.files).length > 0;
2978
+ const isJsonRequest = !isMultipartRequest && req.body && (req.body.fileUrl || req.body.fileUrls);
2979
+ if (!isMultipartRequest && !isJsonRequest) {
2980
+ return sendError(res, 400, "INVALID_REQUEST", "Request must contain either files or URLs");
2979
2981
  }
2980
2982
  try {
2981
- const processingPromises = files.map(async (file, index) => {
2982
- let knowledgeId;
2983
- const originalFilename = file.originalname;
2984
- const worldId = req.body.worldId || runtime.agentId;
2985
- const filePath = file.path;
2986
- knowledgeId = req.body?.documentIds && req.body.documentIds[index] || req.body?.documentId || createUniqueUuid3(runtime, `knowledge-${originalFilename}-${Date.now()}`);
2987
- try {
2988
- const fileBuffer = await fs3.promises.readFile(filePath);
2989
- const fileExt = file.originalname.split(".").pop()?.toLowerCase() || "";
2990
- const filename = file.originalname;
2991
- const title = filename.replace(`.${fileExt}`, "");
2992
- const base64Content = fileBuffer.toString("base64");
2993
- const knowledgeItem = {
2994
- id: knowledgeId,
2995
- content: {
2996
- text: base64Content
2997
- },
2998
- metadata: {
2999
- type: MemoryType4.DOCUMENT,
3000
- timestamp: Date.now(),
3001
- source: "upload",
3002
- filename,
3003
- fileExt,
3004
- title,
3005
- path: originalFilename,
3006
- fileType: file.mimetype,
3007
- fileSize: file.size
3008
- }
3009
- };
3010
- const addKnowledgeOpts = {
3011
- clientDocumentId: knowledgeId,
3012
- // This is knowledgeItem.id
3013
- contentType: file.mimetype,
3014
- // Directly from multer file object
3015
- originalFilename,
3016
- // Directly from multer file object
3017
- content: base64Content,
3018
- // The base64 string of the file
3019
- worldId,
3020
- roomId: runtime.agentId,
3021
- // Or a more specific room ID if available
3022
- entityId: runtime.agentId
3023
- };
3024
- await service.addKnowledge(addKnowledgeOpts);
3025
- cleanupFile(filePath);
3026
- return {
3027
- id: knowledgeId,
3028
- filename: originalFilename,
3029
- type: file.mimetype,
3030
- size: file.size,
3031
- uploadedAt: Date.now(),
3032
- status: "success"
3033
- };
3034
- } catch (fileError) {
3035
- logger5.error(
3036
- `[KNOWLEDGE UPLOAD HANDLER] Error processing file ${file.originalname}: ${fileError}`
3037
- );
3038
- cleanupFile(filePath);
3039
- return {
3040
- id: knowledgeId,
3041
- filename: originalFilename,
3042
- status: "error_processing",
3043
- error: fileError.message
3044
- };
2983
+ if (isMultipartRequest) {
2984
+ const files = req.files;
2985
+ if (!files || files.length === 0) {
2986
+ return sendError(res, 400, "NO_FILES", "No files uploaded");
3045
2987
  }
3046
- });
3047
- const results = await Promise.all(processingPromises);
3048
- sendSuccess(res, results);
2988
+ const processingPromises = files.map(async (file, index) => {
2989
+ let knowledgeId;
2990
+ const originalFilename = file.originalname;
2991
+ const worldId = req.body.worldId || runtime.agentId;
2992
+ const filePath = file.path;
2993
+ knowledgeId = req.body?.documentIds && req.body.documentIds[index] || req.body?.documentId || createUniqueUuid3(runtime, `knowledge-${originalFilename}-${Date.now()}`);
2994
+ try {
2995
+ const fileBuffer = await fs3.promises.readFile(filePath);
2996
+ const fileExt = file.originalname.split(".").pop()?.toLowerCase() || "";
2997
+ const filename = file.originalname;
2998
+ const title = filename.replace(`.${fileExt}`, "");
2999
+ const base64Content = fileBuffer.toString("base64");
3000
+ const knowledgeItem = {
3001
+ id: knowledgeId,
3002
+ content: {
3003
+ text: base64Content
3004
+ },
3005
+ metadata: {
3006
+ type: MemoryType4.DOCUMENT,
3007
+ timestamp: Date.now(),
3008
+ source: "upload",
3009
+ filename,
3010
+ fileExt,
3011
+ title,
3012
+ path: originalFilename,
3013
+ fileType: file.mimetype,
3014
+ fileSize: file.size
3015
+ }
3016
+ };
3017
+ const addKnowledgeOpts = {
3018
+ clientDocumentId: knowledgeId,
3019
+ // This is knowledgeItem.id
3020
+ contentType: file.mimetype,
3021
+ // Directly from multer file object
3022
+ originalFilename,
3023
+ // Directly from multer file object
3024
+ content: base64Content,
3025
+ // The base64 string of the file
3026
+ worldId,
3027
+ roomId: runtime.agentId,
3028
+ // Or a more specific room ID if available
3029
+ entityId: runtime.agentId
3030
+ };
3031
+ await service.addKnowledge(addKnowledgeOpts);
3032
+ cleanupFile(filePath);
3033
+ return {
3034
+ id: knowledgeId,
3035
+ filename: originalFilename,
3036
+ type: file.mimetype,
3037
+ size: file.size,
3038
+ uploadedAt: Date.now(),
3039
+ status: "success"
3040
+ };
3041
+ } catch (fileError) {
3042
+ logger5.error(
3043
+ `[KNOWLEDGE UPLOAD HANDLER] Error processing file ${file.originalname}: ${fileError}`
3044
+ );
3045
+ cleanupFile(filePath);
3046
+ return {
3047
+ id: knowledgeId,
3048
+ filename: originalFilename,
3049
+ status: "error_processing",
3050
+ error: fileError.message
3051
+ };
3052
+ }
3053
+ });
3054
+ const results = await Promise.all(processingPromises);
3055
+ sendSuccess(res, results);
3056
+ } else if (isJsonRequest) {
3057
+ const fileUrls = Array.isArray(req.body.fileUrls) ? req.body.fileUrls : req.body.fileUrl ? [req.body.fileUrl] : [];
3058
+ if (fileUrls.length === 0) {
3059
+ return sendError(res, 400, "MISSING_URL", "File URL is required");
3060
+ }
3061
+ const processingPromises = fileUrls.map(async (fileUrl) => {
3062
+ try {
3063
+ const knowledgeId = createUniqueUuid3(runtime, fileUrl);
3064
+ const urlObject = new URL(fileUrl);
3065
+ const pathSegments = urlObject.pathname.split("/");
3066
+ const encodedFilename = pathSegments[pathSegments.length - 1] || "document.pdf";
3067
+ const originalFilename = decodeURIComponent(encodedFilename);
3068
+ logger5.info(`[KNOWLEDGE URL HANDLER] Fetching content from URL: ${fileUrl}`);
3069
+ const { content, contentType: fetchedContentType } = await fetchUrlContent(fileUrl);
3070
+ let contentType = fetchedContentType;
3071
+ if (contentType === "application/octet-stream") {
3072
+ const fileExtension = originalFilename.split(".").pop()?.toLowerCase();
3073
+ if (fileExtension) {
3074
+ if (["pdf"].includes(fileExtension)) {
3075
+ contentType = "application/pdf";
3076
+ } else if (["txt", "text"].includes(fileExtension)) {
3077
+ contentType = "text/plain";
3078
+ } else if (["md", "markdown"].includes(fileExtension)) {
3079
+ contentType = "text/markdown";
3080
+ } else if (["doc", "docx"].includes(fileExtension)) {
3081
+ contentType = "application/msword";
3082
+ } else if (["html", "htm"].includes(fileExtension)) {
3083
+ contentType = "text/html";
3084
+ } else if (["json"].includes(fileExtension)) {
3085
+ contentType = "application/json";
3086
+ } else if (["xml"].includes(fileExtension)) {
3087
+ contentType = "application/xml";
3088
+ }
3089
+ }
3090
+ }
3091
+ const addKnowledgeOpts = {
3092
+ clientDocumentId: knowledgeId,
3093
+ contentType,
3094
+ originalFilename,
3095
+ content,
3096
+ // Use the base64 encoded content from the URL
3097
+ worldId: runtime.agentId,
3098
+ roomId: runtime.agentId,
3099
+ entityId: runtime.agentId,
3100
+ // Store the source URL in metadata
3101
+ metadata: {
3102
+ url: fileUrl
3103
+ }
3104
+ };
3105
+ logger5.debug(`[KNOWLEDGE URL HANDLER] Processing knowledge from URL: ${fileUrl} (type: ${contentType})`);
3106
+ const result = await service.addKnowledge(addKnowledgeOpts);
3107
+ return {
3108
+ id: result.clientDocumentId,
3109
+ fileUrl,
3110
+ filename: originalFilename,
3111
+ message: "Knowledge created successfully",
3112
+ createdAt: Date.now(),
3113
+ fragmentCount: result.fragmentCount,
3114
+ status: "success"
3115
+ };
3116
+ } catch (urlError) {
3117
+ logger5.error(`[KNOWLEDGE URL HANDLER] Error processing URL ${fileUrl}: ${urlError}`);
3118
+ return {
3119
+ fileUrl,
3120
+ status: "error_processing",
3121
+ error: urlError.message
3122
+ };
3123
+ }
3124
+ });
3125
+ const results = await Promise.all(processingPromises);
3126
+ sendSuccess(res, results);
3127
+ }
3049
3128
  } catch (error) {
3050
- logger5.error("[KNOWLEDGE UPLOAD HANDLER] Error uploading knowledge:", error);
3051
- cleanupFiles(files);
3052
- sendError(res, 500, "UPLOAD_ERROR", "Failed to upload knowledge", error.message);
3129
+ logger5.error("[KNOWLEDGE HANDLER] Error processing knowledge:", error);
3130
+ if (isMultipartRequest) {
3131
+ cleanupFiles(req.files);
3132
+ }
3133
+ sendError(res, 500, "PROCESSING_ERROR", "Failed to process knowledge", error.message);
3053
3134
  }
3054
3135
  }
3055
3136
  async function getKnowledgeDocumentsHandler(req, res, runtime) {
@@ -3066,22 +3147,42 @@ async function getKnowledgeDocumentsHandler(req, res, runtime) {
3066
3147
  const limit = req.query.limit ? Number.parseInt(req.query.limit, 10) : 20;
3067
3148
  const before = req.query.before ? Number.parseInt(req.query.before, 10) : Date.now();
3068
3149
  const includeEmbedding = req.query.includeEmbedding === "true";
3150
+ const fileUrls = req.query.fileUrls ? typeof req.query.fileUrls === "string" && req.query.fileUrls.includes(",") ? req.query.fileUrls.split(",") : [req.query.fileUrls] : null;
3069
3151
  const memories = await service.getMemories({
3070
3152
  tableName: "documents",
3071
3153
  count: limit,
3072
3154
  end: before
3073
3155
  });
3074
- const cleanMemories = includeEmbedding ? memories : memories.map((memory) => ({
3156
+ let filteredMemories = memories;
3157
+ if (fileUrls && fileUrls.length > 0) {
3158
+ const urlBasedIds = fileUrls.map((url) => createUniqueUuid3(runtime, url));
3159
+ filteredMemories = memories.filter(
3160
+ (memory) => urlBasedIds.includes(memory.id) || // If the ID corresponds directly
3161
+ // Or if the URL is stored in the metadata (check if it exists)
3162
+ memory.metadata && "url" in memory.metadata && typeof memory.metadata.url === "string" && fileUrls.includes(memory.metadata.url)
3163
+ );
3164
+ logger5.debug(`[KNOWLEDGE GET HANDLER] Filtered documents by URLs: ${fileUrls.length} URLs, found ${filteredMemories.length} matching documents`);
3165
+ }
3166
+ const cleanMemories = includeEmbedding ? filteredMemories : filteredMemories.map((memory) => ({
3075
3167
  ...memory,
3076
3168
  embedding: void 0
3077
3169
  }));
3078
- sendSuccess(res, { memories: cleanMemories });
3170
+ sendSuccess(res, {
3171
+ memories: cleanMemories,
3172
+ urlFiltered: fileUrls ? true : false,
3173
+ totalFound: cleanMemories.length,
3174
+ totalRequested: fileUrls ? fileUrls.length : 0
3175
+ });
3079
3176
  } catch (error) {
3080
3177
  logger5.error("[KNOWLEDGE GET HANDLER] Error retrieving documents:", error);
3081
3178
  sendError(res, 500, "RETRIEVAL_ERROR", "Failed to retrieve documents", error.message);
3082
3179
  }
3083
3180
  }
3084
3181
  async function deleteKnowledgeDocumentHandler(req, res, runtime) {
3182
+ logger5.debug(`[KNOWLEDGE DELETE HANDLER] Received DELETE request:
3183
+ - path: ${req.path}
3184
+ - params: ${JSON.stringify(req.params)}
3185
+ `);
3085
3186
  const service = runtime.getService(KnowledgeService.serviceType);
3086
3187
  if (!service) {
3087
3188
  return sendError(
@@ -3091,27 +3192,104 @@ async function deleteKnowledgeDocumentHandler(req, res, runtime) {
3091
3192
  "KnowledgeService not found for deleteKnowledgeDocumentHandler"
3092
3193
  );
3093
3194
  }
3094
- const knowledgeId = req.path.split("/documents/")[1];
3195
+ const knowledgeId = req.params.knowledgeId;
3095
3196
  if (!knowledgeId || knowledgeId.length < 36) {
3197
+ logger5.error(`[KNOWLEDGE DELETE HANDLER] Invalid knowledge ID format: ${knowledgeId}`);
3096
3198
  return sendError(res, 400, "INVALID_ID", "Invalid Knowledge ID format");
3097
3199
  }
3098
3200
  try {
3099
- await service.deleteMemory(knowledgeId);
3201
+ const typedKnowledgeId = knowledgeId;
3202
+ logger5.debug(`[KNOWLEDGE DELETE HANDLER] Attempting to delete document with ID: ${typedKnowledgeId}`);
3203
+ await service.deleteMemory(typedKnowledgeId);
3204
+ logger5.info(`[KNOWLEDGE DELETE HANDLER] Successfully deleted document with ID: ${typedKnowledgeId}`);
3100
3205
  sendSuccess(res, null, 204);
3101
3206
  } catch (error) {
3102
3207
  logger5.error(`[KNOWLEDGE DELETE HANDLER] Error deleting document ${knowledgeId}:`, error);
3103
3208
  sendError(res, 500, "DELETE_ERROR", "Failed to delete document", error.message);
3104
3209
  }
3105
3210
  }
3211
+ async function getKnowledgeByIdHandler(req, res, runtime) {
3212
+ logger5.debug(`[KNOWLEDGE GET BY ID HANDLER] Received GET request:
3213
+ - path: ${req.path}
3214
+ - params: ${JSON.stringify(req.params)}
3215
+ `);
3216
+ const service = runtime.getService(KnowledgeService.serviceType);
3217
+ if (!service) {
3218
+ return sendError(
3219
+ res,
3220
+ 500,
3221
+ "SERVICE_NOT_FOUND",
3222
+ "KnowledgeService not found for getKnowledgeByIdHandler"
3223
+ );
3224
+ }
3225
+ const knowledgeId = req.params.knowledgeId;
3226
+ if (!knowledgeId || knowledgeId.length < 36) {
3227
+ logger5.error(`[KNOWLEDGE GET BY ID HANDLER] Invalid knowledge ID format: ${knowledgeId}`);
3228
+ return sendError(res, 400, "INVALID_ID", "Invalid Knowledge ID format");
3229
+ }
3230
+ try {
3231
+ logger5.debug(`[KNOWLEDGE GET BY ID HANDLER] Retrieving document with ID: ${knowledgeId}`);
3232
+ const memories = await service.getMemories({
3233
+ tableName: "documents",
3234
+ count: 1e3
3235
+ });
3236
+ const typedKnowledgeId = knowledgeId;
3237
+ const document = memories.find((memory) => memory.id === typedKnowledgeId);
3238
+ if (!document) {
3239
+ return sendError(res, 404, "NOT_FOUND", `Knowledge with ID ${typedKnowledgeId} not found`);
3240
+ }
3241
+ const cleanDocument = {
3242
+ ...document,
3243
+ embedding: void 0
3244
+ };
3245
+ sendSuccess(res, { document: cleanDocument });
3246
+ } catch (error) {
3247
+ logger5.error(`[KNOWLEDGE GET BY ID HANDLER] Error retrieving document ${knowledgeId}:`, error);
3248
+ sendError(res, 500, "RETRIEVAL_ERROR", "Failed to retrieve document", error.message);
3249
+ }
3250
+ }
3106
3251
  async function knowledgePanelHandler(req, res, runtime) {
3252
+ const agentId = runtime.agentId;
3107
3253
  try {
3108
3254
  const currentDir = path3.dirname(new URL(import.meta.url).pathname);
3109
3255
  const frontendPath = path3.join(currentDir, "../dist/index.html");
3110
3256
  if (fs3.existsSync(frontendPath)) {
3111
3257
  const html = await fs3.promises.readFile(frontendPath, "utf8");
3258
+ const injectedHtml = html.replace(
3259
+ "<head>",
3260
+ `<head>
3261
+ <script>
3262
+ window.ELIZA_CONFIG = {
3263
+ agentId: '${agentId}',
3264
+ apiBase: '/api/agents/${agentId}/plugins/knowledge'
3265
+ };
3266
+ </script>`
3267
+ );
3112
3268
  res.writeHead(200, { "Content-Type": "text/html" });
3113
- res.end(html);
3269
+ res.end(injectedHtml);
3114
3270
  } else {
3271
+ let cssFile = "index.css";
3272
+ let jsFile = "index.js";
3273
+ const manifestPath = path3.join(currentDir, "../dist/manifest.json");
3274
+ if (fs3.existsSync(manifestPath)) {
3275
+ try {
3276
+ const manifestContent = await fs3.promises.readFile(manifestPath, "utf8");
3277
+ const manifest = JSON.parse(manifestContent);
3278
+ for (const [key, value] of Object.entries(manifest)) {
3279
+ if (typeof value === "object" && value !== null) {
3280
+ if (key.endsWith(".css") || value.file?.endsWith(".css")) {
3281
+ cssFile = value.file || key;
3282
+ }
3283
+ if (key.endsWith(".js") || value.file?.endsWith(".js")) {
3284
+ jsFile = value.file || key;
3285
+ }
3286
+ }
3287
+ }
3288
+ } catch (manifestError) {
3289
+ logger5.error("[KNOWLEDGE PANEL] Error reading manifest:", manifestError);
3290
+ }
3291
+ }
3292
+ logger5.debug(`[KNOWLEDGE PANEL] Using fallback with CSS: ${cssFile}, JS: ${jsFile}`);
3115
3293
  const html = `
3116
3294
  <!DOCTYPE html>
3117
3295
  <html lang="en">
@@ -3119,7 +3297,13 @@ async function knowledgePanelHandler(req, res, runtime) {
3119
3297
  <meta charset="UTF-8">
3120
3298
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
3121
3299
  <title>Knowledge</title>
3122
- <link rel="stylesheet" href="./assets/index-BlRATUqY.css">
3300
+ <script>
3301
+ window.ELIZA_CONFIG = {
3302
+ agentId: '${agentId}',
3303
+ apiBase: '/api/agents/${agentId}/plugins/knowledge'
3304
+ };
3305
+ </script>
3306
+ <link rel="stylesheet" href="./assets/${cssFile}">
3123
3307
  <style>
3124
3308
  body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 20px; }
3125
3309
  .container { max-width: 1200px; margin: 0 auto; }
@@ -3128,11 +3312,11 @@ async function knowledgePanelHandler(req, res, runtime) {
3128
3312
  </head>
3129
3313
  <body>
3130
3314
  <div class="container">
3131
- <div id="knowledge-root">
3315
+ <div id="root">
3132
3316
  <div class="loading">Loading Knowledge Library...</div>
3133
3317
  </div>
3134
3318
  </div>
3135
- <script type="module" src="./assets/index-7riujMow.js"></script>
3319
+ <script type="module" src="./assets/${jsFile}"></script>
3136
3320
  </body>
3137
3321
  </html>`;
3138
3322
  res.writeHead(200, { "Content-Type": "text/html" });
@@ -3184,6 +3368,26 @@ async function frontendAssetHandler(req, res, runtime) {
3184
3368
  sendError(res, 500, "ASSET_ERROR", `Failed to load asset ${req.url}`, error.message);
3185
3369
  }
3186
3370
  }
3371
+ async function getKnowledgeChunksHandler(req, res, runtime) {
3372
+ const service = runtime.getService(KnowledgeService.serviceType);
3373
+ if (!service) {
3374
+ return sendError(res, 500, "SERVICE_NOT_FOUND", "KnowledgeService not found");
3375
+ }
3376
+ try {
3377
+ const limit = req.query.limit ? Number.parseInt(req.query.limit, 10) : 100;
3378
+ const before = req.query.before ? Number.parseInt(req.query.before, 10) : Date.now();
3379
+ const chunks = await service.getMemories({
3380
+ tableName: "knowledge",
3381
+ // or whatever table stores the chunks
3382
+ count: limit,
3383
+ end: before
3384
+ });
3385
+ sendSuccess(res, { chunks });
3386
+ } catch (error) {
3387
+ logger5.error("[KNOWLEDGE CHUNKS GET HANDLER] Error retrieving chunks:", error);
3388
+ sendError(res, 500, "RETRIEVAL_ERROR", "Failed to retrieve knowledge chunks", error.message);
3389
+ }
3390
+ }
3187
3391
  var knowledgeRoutes = [
3188
3392
  {
3189
3393
  type: "GET",
@@ -3199,7 +3403,7 @@ var knowledgeRoutes = [
3199
3403
  },
3200
3404
  {
3201
3405
  type: "POST",
3202
- path: "/upload",
3406
+ path: "/documents",
3203
3407
  handler: uploadKnowledgeHandler,
3204
3408
  isMultipart: true
3205
3409
  },
@@ -3208,10 +3412,20 @@ var knowledgeRoutes = [
3208
3412
  path: "/documents",
3209
3413
  handler: getKnowledgeDocumentsHandler
3210
3414
  },
3415
+ {
3416
+ type: "GET",
3417
+ path: "/documents/:knowledgeId",
3418
+ handler: getKnowledgeByIdHandler
3419
+ },
3211
3420
  {
3212
3421
  type: "DELETE",
3213
- path: "/documents/*",
3422
+ path: "/documents/:knowledgeId",
3214
3423
  handler: deleteKnowledgeDocumentHandler
3424
+ },
3425
+ {
3426
+ type: "GET",
3427
+ path: "/knowledges",
3428
+ handler: getKnowledgeChunksHandler
3215
3429
  }
3216
3430
  ];
3217
3431
 
@@ -3260,7 +3474,7 @@ var knowledgePlugin = {
3260
3474
  try {
3261
3475
  const service = runtime.getService(KnowledgeService.serviceType);
3262
3476
  if (service instanceof KnowledgeService) {
3263
- const { loadDocsFromPath: loadDocsFromPath2 } = await import("./docs-loader-25N4HXDV.js");
3477
+ const { loadDocsFromPath: loadDocsFromPath2 } = await import("./docs-loader-2S6H7F4L.js");
3264
3478
  const result = await loadDocsFromPath2(service, runtime.agentId);
3265
3479
  if (result.successful > 0) {
3266
3480
  logger6.info(`Loaded ${result.successful} documents from docs folder on startup`);