@exulu/backend 1.32.1 → 1.34.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/dist/index.cjs CHANGED
@@ -48,7 +48,7 @@ __export(index_exports, {
48
48
  ExuluUtils: () => ExuluUtils,
49
49
  ExuluVariables: () => ExuluVariables,
50
50
  db: () => db2,
51
- logMetadata: () => logMetadata
51
+ logMetadata: () => logMetadata2
52
52
  });
53
53
  module.exports = __toCommonJS(index_exports);
54
54
  var import_config = require("dotenv/config");
@@ -58,10 +58,10 @@ var import_redis = require("redis");
58
58
 
59
59
  // src/bullmq/server.ts
60
60
  var redisServer = {
61
- host: `${process.env.REDIS_HOST}`,
62
- port: process.env.REDIS_PORT,
61
+ host: process.env.REDIS_HOST ?? "",
62
+ port: process.env.REDIS_PORT ?? "",
63
63
  password: process.env.REDIS_PASSWORD || void 0,
64
- username: process.env.REDIS_USER || void 0
64
+ username: process.env.REDIS_USER || ""
65
65
  };
66
66
 
67
67
  // src/redis/client.ts
@@ -74,7 +74,7 @@ async function redisClient() {
74
74
  if (!client["exulu"]) {
75
75
  try {
76
76
  let url = "";
77
- if (redisServer.username) {
77
+ if (redisServer.password) {
78
78
  url = `redis://${redisServer.username}:${redisServer.password}@${redisServer.host}:${redisServer.port}`;
79
79
  } else {
80
80
  url = `redis://${redisServer.host}:${redisServer.port}`;
@@ -279,7 +279,7 @@ var bullmqDecorator = async ({
279
279
  embedder,
280
280
  workflow,
281
281
  processor,
282
- eval
282
+ evaluation
283
283
  ];
284
284
  if (types.filter((type2) => type2).length > 1) {
285
285
  throw new Error("Cannot have multiple types in the same job, must be one of the following: embedder, workflow, processor or eval.");
@@ -434,7 +434,7 @@ var mapType = (t, type, name, defaultValue, unique) => {
434
434
  if (unique) t.unique(name);
435
435
  return;
436
436
  }
437
- throw new Error("Invalid type: " + type);
437
+ throw new Error("Invalid field type for database: " + type);
438
438
  };
439
439
 
440
440
  // src/registry/utils/sanitize-name.ts
@@ -824,6 +824,10 @@ var agentSessionsSchema = {
824
824
  name: "project",
825
825
  type: "uuid",
826
826
  required: false
827
+ },
828
+ {
829
+ name: "metadata",
830
+ type: "json"
827
831
  }
828
832
  ]
829
833
  };
@@ -1375,6 +1379,69 @@ var platformConfigurationsSchema = {
1375
1379
  }
1376
1380
  ]
1377
1381
  };
1382
+ var promptLibrarySchema = {
1383
+ type: "prompt_library",
1384
+ name: {
1385
+ plural: "prompt_library",
1386
+ singular: "prompt_library_item"
1387
+ },
1388
+ RBAC: true,
1389
+ fields: [
1390
+ {
1391
+ name: "name",
1392
+ type: "text",
1393
+ required: true
1394
+ },
1395
+ {
1396
+ name: "description",
1397
+ type: "text"
1398
+ },
1399
+ {
1400
+ name: "content",
1401
+ type: "longText",
1402
+ required: true
1403
+ },
1404
+ {
1405
+ name: "tags",
1406
+ type: "json"
1407
+ },
1408
+ {
1409
+ name: "usage_count",
1410
+ type: "number",
1411
+ default: 0
1412
+ },
1413
+ {
1414
+ name: "favorite_count",
1415
+ type: "number",
1416
+ default: 0
1417
+ },
1418
+ {
1419
+ name: "assigned_agents",
1420
+ type: "json"
1421
+ }
1422
+ ]
1423
+ };
1424
+ var promptFavoritesSchema = {
1425
+ type: "prompt_favorites",
1426
+ name: {
1427
+ plural: "prompt_favorites",
1428
+ singular: "prompt_favorite"
1429
+ },
1430
+ fields: [
1431
+ {
1432
+ name: "user_id",
1433
+ type: "number",
1434
+ required: true,
1435
+ index: true
1436
+ },
1437
+ {
1438
+ name: "prompt_id",
1439
+ type: "uuid",
1440
+ required: true,
1441
+ index: true
1442
+ }
1443
+ ]
1444
+ };
1378
1445
  var addCoreFields = (schema) => {
1379
1446
  schema.fields.forEach((field) => {
1380
1447
  if (field.type === "file") {
@@ -1418,7 +1485,9 @@ var coreSchemas = {
1418
1485
  rbacSchema: () => addCoreFields(rbacSchema),
1419
1486
  workflowTemplatesSchema: () => addCoreFields(workflowTemplatesSchema),
1420
1487
  platformConfigurationsSchema: () => addCoreFields(platformConfigurationsSchema),
1421
- jobResultsSchema: () => addCoreFields(jobResultsSchema)
1488
+ jobResultsSchema: () => addCoreFields(jobResultsSchema),
1489
+ promptLibrarySchema: () => addCoreFields(promptLibrarySchema),
1490
+ promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema)
1422
1491
  };
1423
1492
  }
1424
1493
  };
@@ -1481,11 +1550,11 @@ var bullmq = {
1481
1550
  if (!data.inputs) {
1482
1551
  throw new Error(`Missing property "inputs" in data for job ${id}.`);
1483
1552
  }
1484
- if (data.type !== "embedder" && data.type !== "workflow" && data.type !== "processor" && data.type !== "eval_run" && data.type !== "eval_function") {
1485
- throw new Error(`Property "type" in data for job ${id} must be of value "embedder", "workflow", "processor", "eval_run" or "eval_function".`);
1553
+ if (data.type !== "embedder" && data.type !== "workflow" && data.type !== "processor" && data.type !== "eval_run" && data.type !== "eval_function" && data.type !== "source") {
1554
+ throw new Error(`Property "type" in data for job ${id} must be of value "embedder", "workflow", "processor", "eval_run", "eval_function" or "source".`);
1486
1555
  }
1487
- if (!data.workflow && !data.embedder && !data.processor && !data.eval_run_id && !data.eval_functions?.length) {
1488
- throw new Error(`Either a workflow, embedder, processor, eval_run or eval_functions must be set for job ${id}.`);
1556
+ if (!data.workflow && !data.embedder && !data.processor && !data.eval_run_id && !data.eval_functions?.length && !data.source) {
1557
+ throw new Error(`Either a workflow, embedder, processor, eval_run, eval_functions or source must be set for job ${id}.`);
1489
1558
  }
1490
1559
  }
1491
1560
  };
@@ -1878,6 +1947,7 @@ input FilterOperatorJSON {
1878
1947
  eq: JSON
1879
1948
  ne: JSON
1880
1949
  in: [JSON]
1950
+ contains: JSON
1881
1951
  }
1882
1952
 
1883
1953
  input SortBy {
@@ -1991,6 +2061,7 @@ var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRba
1991
2061
  };
1992
2062
  function createMutations(table, agents, contexts, tools, config) {
1993
2063
  const tableNamePlural = table.name.plural.toLowerCase();
2064
+ const tableNameSingular = table.name.singular.toLowerCase();
1994
2065
  const validateWriteAccess = async (id, context) => {
1995
2066
  try {
1996
2067
  const { db: db3, req, user } = context;
@@ -2302,56 +2373,131 @@ function createMutations(table, agents, contexts, tools, config) {
2302
2373
  return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2303
2374
  }
2304
2375
  };
2305
- if (table.type === "items" && table.fields.some((field) => field.processor?.execute)) {
2306
- mutations[`${tableNamePlural}ProcessItemField`] = async (_, args, context, info) => {
2376
+ if (table.type === "items") {
2377
+ if (table.fields.some((field) => field.processor?.execute)) {
2378
+ mutations[`${tableNameSingular}ProcessItemField`] = async (_, args, context, info) => {
2379
+ if (!context.user?.super_admin) {
2380
+ throw new Error("You are not authorized to process fields via API, user must be super admin.");
2381
+ }
2382
+ const exists = contexts.find((context2) => context2.id === table.id);
2383
+ if (!exists) {
2384
+ throw new Error(`Context ${table.id} not found.`);
2385
+ }
2386
+ if (!args.field) {
2387
+ throw new Error("Field argument missing, the field argument is required.");
2388
+ }
2389
+ if (!args.item) {
2390
+ throw new Error("Item argument missing, the item argument is required.");
2391
+ }
2392
+ const name = args.field?.replace("_s3key", "");
2393
+ console.log("[EXULU] name", name);
2394
+ console.log("[EXULU] fields", exists.fields.map((field2) => field2.name));
2395
+ const field = exists.fields.find((field2) => field2.name === name);
2396
+ if (!field) {
2397
+ throw new Error(`Field ${name} not found in context ${exists.id}.`);
2398
+ }
2399
+ if (!field.processor) {
2400
+ throw new Error(`Processor not set for field ${args.field} in context ${exists.id}.`);
2401
+ }
2402
+ const { db: db3 } = context;
2403
+ let query = db3.from(tableNamePlural).select("*").where({ id: args.item });
2404
+ query = applyAccessControl(table, context.user, query);
2405
+ const item = await query.first();
2406
+ if (!item) {
2407
+ throw new Error("Item not found, or your user does not have access to it.");
2408
+ }
2409
+ const { job, result } = await exists.processField(
2410
+ "api",
2411
+ context.user.id,
2412
+ context.user.role?.id,
2413
+ {
2414
+ ...item,
2415
+ field: args.field
2416
+ },
2417
+ config
2418
+ );
2419
+ return {
2420
+ message: job ? "Processing job scheduled." : "Item processed successfully.",
2421
+ result,
2422
+ job
2423
+ };
2424
+ };
2425
+ }
2426
+ mutations[`${tableNameSingular}ExecuteSource`] = async (_, args, context, info) => {
2427
+ console.log("[EXULU] Executing source", args);
2307
2428
  if (!context.user?.super_admin) {
2308
- throw new Error("You are not authorized to process fields via API, user must be super admin.");
2429
+ throw new Error("You are not authorized to execute sources via API, user must be super admin.");
2430
+ }
2431
+ if (!args.source) {
2432
+ throw new Error("Source argument missing, the source argument is required.");
2309
2433
  }
2310
2434
  const exists = contexts.find((context2) => context2.id === table.id);
2311
2435
  if (!exists) {
2312
2436
  throw new Error(`Context ${table.id} not found.`);
2313
2437
  }
2314
- if (!args.field) {
2315
- throw new Error("Field argument missing, the field argument is required.");
2316
- }
2317
- if (!args.item) {
2318
- throw new Error("Item argument missing, the item argument is required.");
2319
- }
2320
- const name = args.field?.replace("_s3key", "");
2321
- console.log("[EXULU] name", name);
2322
- console.log("[EXULU] fields", exists.fields.map((field2) => field2.name));
2323
- const field = exists.fields.find((field2) => field2.name === name);
2324
- if (!field) {
2325
- throw new Error(`Field ${name} not found in context ${exists.id}.`);
2438
+ const source = exists.sources.find((source2) => source2.id === args.source);
2439
+ if (!source) {
2440
+ throw new Error(`Source ${args.source} not found in context ${exists.id}.`);
2326
2441
  }
2327
- if (!field.processor) {
2328
- throw new Error(`Processor not set for field ${args.field} in context ${exists.id}.`);
2442
+ if (source?.config?.queue) {
2443
+ console.log("[EXULU] Executing source function in queue mode");
2444
+ const queue = await source.config.queue;
2445
+ if (!queue) {
2446
+ throw new Error(`Queue not found for source ${source.id}.`);
2447
+ }
2448
+ const job = await queue.queue?.add(source.id, {
2449
+ source: source.id,
2450
+ context: exists.id,
2451
+ type: "source",
2452
+ inputs: args.inputs,
2453
+ user: context.user.id,
2454
+ role: context.user.role?.id
2455
+ });
2456
+ console.log("[EXULU] Source function job scheduled", job.id);
2457
+ return {
2458
+ message: "Job scheduled for source execution.",
2459
+ jobs: [job?.id],
2460
+ items: []
2461
+ };
2329
2462
  }
2330
- const { db: db3 } = context;
2331
- let query = db3.from(tableNamePlural).select("*").where({ id: args.item });
2332
- query = applyAccessControl(table, context.user, query);
2333
- const item = await query.first();
2334
- if (!item) {
2335
- throw new Error("Item not found, or your user does not have access to it.");
2463
+ console.log("[EXULU] Executing source function directly");
2464
+ const result = await source.execute(args.inputs);
2465
+ let jobs = [];
2466
+ let items = [];
2467
+ for (const item of result) {
2468
+ const { item: createdItem, job } = await exists.createItem(
2469
+ item,
2470
+ config,
2471
+ context.user.id,
2472
+ context.user.role?.id
2473
+ );
2474
+ if (job) {
2475
+ jobs.push(job);
2476
+ console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`);
2477
+ }
2478
+ if (createdItem.id) {
2479
+ items.push(createdItem.id);
2480
+ console.log(`[EXULU] created item through source update job ${createdItem.id}`);
2481
+ }
2336
2482
  }
2337
- const { job, result } = await exists.processField(
2338
- "api",
2339
- context.user.id,
2340
- context.user.role?.id,
2341
- {
2342
- ...item,
2343
- field: args.field
2344
- },
2345
- config
2346
- );
2483
+ await updateStatistic({
2484
+ name: "count",
2485
+ label: source.id,
2486
+ type: STATISTICS_TYPE_ENUM.SOURCE_UPDATE,
2487
+ trigger: "api",
2488
+ count: 1,
2489
+ user: context?.user?.id,
2490
+ role: context?.user?.role?.id
2491
+ });
2347
2492
  return {
2348
- message: job ? "Processing job scheduled." : "Item processed successfully.",
2349
- result,
2350
- job
2493
+ message: "Items created successfully.",
2494
+ jobs,
2495
+ items
2351
2496
  };
2352
- }, mutations[`${tableNamePlural}GenerateChunks`] = async (_, args, context, info) => {
2497
+ };
2498
+ mutations[`${tableNameSingular}GenerateChunks`] = async (_, args, context, info) => {
2353
2499
  if (!context.user?.super_admin) {
2354
- throw new Error("You are not authorized to delete chunks via API, user must be super admin.");
2500
+ throw new Error("You are not authorized to generate chunks via API, user must be super admin.");
2355
2501
  }
2356
2502
  const { db: db3 } = await postgresClient();
2357
2503
  const exists = contexts.find((context2) => context2.id === table.id);
@@ -2377,7 +2523,7 @@ function createMutations(table, agents, contexts, tools, config) {
2377
2523
  jobs: jobs2.slice(0, 100)
2378
2524
  };
2379
2525
  }
2380
- query = applyFilters(query, args.where);
2526
+ query = applyFilters(query, args.where, table);
2381
2527
  const items = await query;
2382
2528
  if (items.length === 0) {
2383
2529
  throw new Error("No items found to generate chunks for.");
@@ -2396,11 +2542,12 @@ function createMutations(table, agents, contexts, tools, config) {
2396
2542
  }
2397
2543
  }
2398
2544
  return {
2399
- message: "Chunks deleted successfully.",
2545
+ message: "Chunks generated successfully.",
2400
2546
  items: items.length,
2401
2547
  jobs: jobs.slice(0, 100)
2402
2548
  };
2403
- }, mutations[`${tableNamePlural}DeleteChunks`] = async (_, args, context, info) => {
2549
+ };
2550
+ mutations[`${tableNameSingular}DeleteChunks`] = async (_, args, context, info) => {
2404
2551
  if (!context.user?.super_admin) {
2405
2552
  throw new Error("You are not authorized to delete chunks via API, user must be super admin.");
2406
2553
  }
@@ -2411,7 +2558,7 @@ function createMutations(table, agents, contexts, tools, config) {
2411
2558
  }
2412
2559
  let query = db3.from(getTableName(id)).select("id");
2413
2560
  if (args.where) {
2414
- query = applyFilters(query, args.where);
2561
+ query = applyFilters(query, args.where, table);
2415
2562
  }
2416
2563
  const items = await query;
2417
2564
  if (items.length === 0) {
@@ -2468,18 +2615,38 @@ var applyAccessControl = (table, user, query) => {
2468
2615
  }
2469
2616
  return query;
2470
2617
  };
2471
- var converOperatorToQuery = (query, fieldName, operators) => {
2618
+ var converOperatorToQuery = (query, fieldName, operators, table) => {
2619
+ const field = table?.fields.find((f) => f.name === fieldName);
2620
+ const isJsonField = field?.type === "json";
2472
2621
  if (operators.eq !== void 0) {
2473
- query = query.where(fieldName, operators.eq);
2622
+ if (isJsonField) {
2623
+ query = query.whereRaw(`?? = ?::jsonb`, [fieldName, JSON.stringify(operators.eq)]);
2624
+ } else {
2625
+ query = query.where(fieldName, operators.eq);
2626
+ }
2474
2627
  }
2475
2628
  if (operators.ne !== void 0) {
2476
- query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2629
+ if (isJsonField) {
2630
+ query = query.whereRaw(`?? IS DISTINCT FROM ?::jsonb`, [fieldName, JSON.stringify(operators.ne)]);
2631
+ } else {
2632
+ query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2633
+ }
2477
2634
  }
2478
2635
  if (operators.in !== void 0) {
2479
- query = query.whereIn(fieldName, operators.in);
2636
+ if (isJsonField) {
2637
+ const conditions = operators.in.map((val) => `?? = ?::jsonb`).join(" OR ");
2638
+ const bindings = operators.in.flatMap((val) => [fieldName, JSON.stringify(val)]);
2639
+ query = query.whereRaw(`(${conditions})`, bindings);
2640
+ } else {
2641
+ query = query.whereIn(fieldName, operators.in);
2642
+ }
2480
2643
  }
2481
2644
  if (operators.contains !== void 0) {
2482
- query = query.where(fieldName, "like", `%${operators.contains}%`);
2645
+ if (isJsonField) {
2646
+ query = query.whereRaw(`?? @> ?::jsonb`, [fieldName, JSON.stringify(operators.contains)]);
2647
+ } else {
2648
+ query = query.where(fieldName, "like", `%${operators.contains}%`);
2649
+ }
2483
2650
  }
2484
2651
  if (operators.lte !== void 0) {
2485
2652
  query = query.where(fieldName, "<=", operators.lte);
@@ -2741,21 +2908,21 @@ var finalizeRequestedFields = async ({
2741
2908
  }
2742
2909
  return result;
2743
2910
  };
2744
- var applyFilters = (query, filters) => {
2911
+ var applyFilters = (query, filters, table) => {
2745
2912
  filters.forEach((filter) => {
2746
2913
  Object.entries(filter).forEach(([fieldName, operators]) => {
2747
2914
  if (operators) {
2748
2915
  if (operators.and !== void 0) {
2749
2916
  operators.and.forEach((operator) => {
2750
- query = converOperatorToQuery(query, fieldName, operator);
2917
+ query = converOperatorToQuery(query, fieldName, operator, table);
2751
2918
  });
2752
2919
  }
2753
2920
  if (operators.or !== void 0) {
2754
2921
  operators.or.forEach((operator) => {
2755
- query = converOperatorToQuery(query, fieldName, operator);
2922
+ query = converOperatorToQuery(query, fieldName, operator, table);
2756
2923
  });
2757
2924
  }
2758
- query = converOperatorToQuery(query, fieldName, operators);
2925
+ query = converOperatorToQuery(query, fieldName, operators, table);
2759
2926
  }
2760
2927
  });
2761
2928
  });
@@ -2795,7 +2962,7 @@ function createQueries(table, agents, tools, contexts) {
2795
2962
  const requestedFields = getRequestedFields(info);
2796
2963
  const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2797
2964
  let query = db3.from(tableNamePlural).select(sanitizedFields);
2798
- query = applyFilters(query, filters);
2965
+ query = applyFilters(query, filters, table);
2799
2966
  query = applyAccessControl(table, context.user, query);
2800
2967
  query = applySorting(query, sort);
2801
2968
  let result = await query.first();
@@ -2808,7 +2975,7 @@ function createQueries(table, agents, tools, contexts) {
2808
2975
  throw new Error("Limit cannot be greater than 500.");
2809
2976
  }
2810
2977
  let countQuery = db3(tableNamePlural);
2811
- countQuery = applyFilters(countQuery, filters);
2978
+ countQuery = applyFilters(countQuery, filters, table);
2812
2979
  countQuery = applyAccessControl(table, context.user, countQuery);
2813
2980
  const countResult = await countQuery.count("* as count");
2814
2981
  const itemCount = Number(countResult[0]?.count || 0);
@@ -2817,7 +2984,7 @@ function createQueries(table, agents, tools, contexts) {
2817
2984
  const hasPreviousPage = currentPage > 1;
2818
2985
  const hasNextPage = currentPage < pageCount - 1;
2819
2986
  let dataQuery = db3(tableNamePlural);
2820
- dataQuery = applyFilters(dataQuery, filters);
2987
+ dataQuery = applyFilters(dataQuery, filters, table);
2821
2988
  dataQuery = applyAccessControl(table, context.user, dataQuery);
2822
2989
  const requestedFields = getRequestedFields(info);
2823
2990
  dataQuery = applySorting(dataQuery, sort);
@@ -2842,7 +3009,7 @@ function createQueries(table, agents, tools, contexts) {
2842
3009
  const { filters = [], groupBy } = args;
2843
3010
  const { db: db3 } = context;
2844
3011
  let query = db3(tableNamePlural);
2845
- query = applyFilters(query, filters);
3012
+ query = applyFilters(query, filters, table);
2846
3013
  query = applyAccessControl(table, context.user, query);
2847
3014
  if (groupBy) {
2848
3015
  query = query.select(groupBy).groupBy(groupBy);
@@ -2942,11 +3109,11 @@ var vectorSearch = async ({
2942
3109
  const mainTable = getTableName(id);
2943
3110
  const chunksTable = getChunksTableName(id);
2944
3111
  let countQuery = db3(mainTable);
2945
- countQuery = applyFilters(countQuery, filters);
3112
+ countQuery = applyFilters(countQuery, filters, table);
2946
3113
  countQuery = applyAccessControl(table, user, countQuery);
2947
3114
  const columns = await db3(mainTable).columnInfo();
2948
3115
  let itemsQuery = db3(mainTable).select(Object.keys(columns).map((column) => mainTable + "." + column));
2949
- itemsQuery = applyFilters(itemsQuery, filters);
3116
+ itemsQuery = applyFilters(itemsQuery, filters, table);
2950
3117
  itemsQuery = applyAccessControl(table, user, itemsQuery);
2951
3118
  itemsQuery = applySorting(itemsQuery, sort);
2952
3119
  if (queryRewriter) {
@@ -3372,6 +3539,7 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
3372
3539
  if (table.type === "items") {
3373
3540
  mutationDefs += `
3374
3541
  ${tableNameSingular}GenerateChunks(where: [Filter${tableNameSingularUpperCaseFirst}]): ${tableNameSingular}GenerateChunksReturnPayload
3542
+ ${tableNameSingular}ExecuteSource(source: ID!, inputs: JSON!): ${tableNameSingular}ExecuteSourceReturnPayload
3375
3543
  ${tableNameSingular}DeleteChunks(where: [Filter${tableNameSingularUpperCaseFirst}]): ${tableNameSingular}DeleteChunksReturnPayload
3376
3544
  `;
3377
3545
  if (processorFields?.length > 0) {
@@ -3386,6 +3554,12 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
3386
3554
  jobs: [String!]
3387
3555
  }
3388
3556
 
3557
+ type ${tableNameSingular}ExecuteSourceReturnPayload {
3558
+ message: String!
3559
+ jobs: [String!]
3560
+ items: [String!]
3561
+ }
3562
+
3389
3563
  type ${tableNameSingular}ProcessItemFieldReturnPayload {
3390
3564
  message: String!
3391
3565
  result: String!
@@ -3497,7 +3671,7 @@ type PageInfo {
3497
3671
  toolCategories: [String!]!
3498
3672
  `;
3499
3673
  typeDefs += `
3500
- jobs(queue: QueueEnum!, statusses: [JobStateEnum!]): JobPaginationResult
3674
+ jobs(queue: QueueEnum!, statusses: [JobStateEnum!], page: Int, limit: Int): JobPaginationResult
3501
3675
  `;
3502
3676
  resolvers.Query["providers"] = async (_, args, context, info) => {
3503
3677
  const requestedFields = getRequestedFields(info);
@@ -3696,10 +3870,11 @@ type PageInfo {
3696
3870
  if (!client2) {
3697
3871
  throw new Error("Redis client not created properly");
3698
3872
  }
3873
+ console.log("[EXULU] Jobs pagination args", args);
3699
3874
  const {
3700
3875
  jobs,
3701
3876
  count
3702
- } = await getJobsByQueueAndName(
3877
+ } = await getJobsByQueueName(
3703
3878
  args.queue,
3704
3879
  args.statusses,
3705
3880
  args.page || 1,
@@ -3728,25 +3903,46 @@ type PageInfo {
3728
3903
  itemCount: count,
3729
3904
  currentPage: args.page || 1,
3730
3905
  hasPreviousPage: args.page && args.page > 1 ? true : false,
3731
- hasNextPage: args.page && args.page < Math.ceil(jobs.length / (args.limit || 100)) ? true : false
3906
+ hasNextPage: args.page && args.page < Math.ceil(count / (args.limit || 100)) ? true : false
3732
3907
  }
3733
3908
  };
3734
3909
  };
3735
3910
  resolvers.Query["contexts"] = async (_, args, context, info) => {
3736
- const data = contexts.map((context2) => ({
3737
- id: context2.id,
3738
- name: context2.name,
3739
- description: context2.description,
3740
- embedder: context2.embedder?.name || void 0,
3741
- slug: "/contexts/" + context2.id,
3742
- active: context2.active,
3743
- fields: context2.fields.map((field) => {
3911
+ const data = await Promise.all(contexts.map(async (context2) => {
3912
+ const sources = await Promise.all(context2.sources.map(async (source) => {
3913
+ let queueName = void 0;
3914
+ if (source.config) {
3915
+ const config2 = await source.config.queue;
3916
+ queueName = config2?.queue?.name || void 0;
3917
+ }
3744
3918
  return {
3745
- ...field,
3746
- name: sanitizeName(field.name),
3747
- label: field.name?.replace("_s3key", "")
3919
+ id: source.id,
3920
+ name: source.name,
3921
+ description: source.description,
3922
+ config: {
3923
+ schedule: source.config?.schedule,
3924
+ queue: queueName,
3925
+ retries: source.config?.retries,
3926
+ backoff: source.config?.backoff
3927
+ }
3748
3928
  };
3749
- })
3929
+ }));
3930
+ return {
3931
+ id: context2.id,
3932
+ name: context2.name,
3933
+ description: context2.description,
3934
+ embedder: context2.embedder?.name || void 0,
3935
+ slug: "/contexts/" + context2.id,
3936
+ active: context2.active,
3937
+ sources,
3938
+ fields: context2.fields.map((field) => {
3939
+ return {
3940
+ ...field,
3941
+ name: sanitizeName(field.name),
3942
+ label: field.name?.replace("_s3key", "")
3943
+ };
3944
+ })
3945
+ };
3750
3946
  }));
3751
3947
  const requestedFields = getRequestedFields(info);
3752
3948
  return {
@@ -3764,6 +3960,24 @@ type PageInfo {
3764
3960
  if (!data) {
3765
3961
  return null;
3766
3962
  }
3963
+ const sources = await Promise.all(data.sources.map(async (source) => {
3964
+ let queueName = void 0;
3965
+ if (source.config) {
3966
+ const config2 = await source.config.queue;
3967
+ queueName = config2?.queue?.name || void 0;
3968
+ }
3969
+ return {
3970
+ id: source.id,
3971
+ name: source.name,
3972
+ description: source.description,
3973
+ config: {
3974
+ schedule: source.config?.schedule,
3975
+ queue: queueName,
3976
+ retries: source.config?.retries,
3977
+ backoff: source.config?.backoff
3978
+ }
3979
+ };
3980
+ }));
3767
3981
  const clean = {
3768
3982
  id: data.id,
3769
3983
  name: data.name,
@@ -3771,6 +3985,7 @@ type PageInfo {
3771
3985
  embedder: data.embedder?.name || void 0,
3772
3986
  slug: "/contexts/" + data.id,
3773
3987
  active: data.active,
3988
+ sources,
3774
3989
  fields: await Promise.all(data.fields.map(async (field) => {
3775
3990
  const label = field.name?.replace("_s3key", "");
3776
3991
  if (field.type === "file" && !field.name.endsWith("_s3key")) {
@@ -3987,6 +4202,26 @@ type Context {
3987
4202
  active: Boolean
3988
4203
  fields: JSON
3989
4204
  configuration: JSON
4205
+ sources: [ContextSource!]
4206
+ }
4207
+
4208
+ type ContextSource {
4209
+ id: String!
4210
+ name: String!
4211
+ description: String!
4212
+ config: ContextSourceConfig!
4213
+ }
4214
+
4215
+ type ContextSourceConfig {
4216
+ schedule: String
4217
+ queue: String
4218
+ retries: Int
4219
+ backoff: ContextSourceBackoff
4220
+ }
4221
+
4222
+ type ContextSourceBackoff {
4223
+ type: String
4224
+ delay: Int
3990
4225
  }
3991
4226
 
3992
4227
  type RunEvalReturnPayload {
@@ -4072,15 +4307,18 @@ var validateCreateOrRemoveSuperAdminPermission = async (tableNamePlural, input,
4072
4307
  }
4073
4308
  }
4074
4309
  };
4075
- async function getJobsByQueueAndName(queueName, statusses, page, limit) {
4310
+ async function getJobsByQueueName(queueName, statusses, page, limit) {
4076
4311
  const queue = queues.list.get(queueName);
4077
4312
  if (!queue) {
4078
4313
  throw new Error(`Queue ${queueName} not found`);
4079
4314
  }
4080
4315
  const config = await queue.use();
4081
4316
  const startIndex = (page || 1) - 1;
4082
- const endIndex = startIndex + (limit || 100);
4317
+ const endIndex = startIndex - 1 + (limit || 100);
4318
+ console.log("[EXULU] Jobs pagination startIndex", startIndex);
4319
+ console.log("[EXULU] Jobs pagination endIndex", endIndex);
4083
4320
  const jobs = await config.queue.getJobs(statusses || [], startIndex, endIndex, false);
4321
+ console.log("[EXULU] Jobs pagination jobs", jobs?.length);
4084
4322
  const counts = await config.queue.getJobCounts(...statusses || []);
4085
4323
  let total = 0;
4086
4324
  if (counts) {
@@ -4651,7 +4889,7 @@ function sanitizeToolName(name) {
4651
4889
  }
4652
4890
  return sanitized;
4653
4891
  }
4654
- var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig) => {
4892
+ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID) => {
4655
4893
  if (!currentTools) return {};
4656
4894
  if (!allExuluTools) return {};
4657
4895
  const sanitizedTools = currentTools ? currentTools.map((tool2) => ({
@@ -4661,101 +4899,109 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
4661
4899
  console.log("[EXULU] Sanitized tools", sanitizedTools.map((x) => x.name + " (" + x.id + ")"));
4662
4900
  return {
4663
4901
  ...sanitizedTools?.reduce(
4664
- (prev, cur) => ({
4665
- ...prev,
4666
- [cur.name]: {
4667
- ...cur.tool,
4668
- async *execute(inputs, options) {
4669
- if (!cur.tool?.execute) {
4670
- console.error("[EXULU] Tool execute function is undefined.", cur.tool);
4671
- throw new Error("Tool execute function is undefined.");
4672
- }
4673
- let config = configs?.find((config2) => config2.id === cur.id);
4674
- if (config) {
4675
- config = await hydrateVariables(config || []);
4676
- }
4677
- let upload = void 0;
4678
- if (exuluConfig?.fileUploads?.s3endpoint && exuluConfig?.fileUploads?.s3key && exuluConfig?.fileUploads?.s3secret && exuluConfig?.fileUploads?.s3Bucket) {
4679
- s3Client2 ??= new import_client_s32.S3Client({
4680
- region: exuluConfig?.fileUploads?.s3region,
4681
- ...exuluConfig?.fileUploads?.s3endpoint && {
4682
- forcePathStyle: true,
4683
- endpoint: exuluConfig?.fileUploads?.s3endpoint
4684
- },
4685
- credentials: {
4686
- accessKeyId: exuluConfig?.fileUploads?.s3key ?? "",
4687
- secretAccessKey: exuluConfig?.fileUploads?.s3secret ?? ""
4688
- }
4689
- });
4690
- upload = async ({
4691
- name,
4692
- data,
4693
- type,
4694
- tags
4695
- }) => {
4696
- const mime = getMimeType(type);
4697
- const prefix = exuluConfig?.fileUploads?.s3prefix ? `${exuluConfig.fileUploads.s3prefix.replace(/\/$/, "")}/` : "";
4698
- const key = `${prefix}${user}/${generateS3Key(name)}${type}`;
4699
- const command = new import_client_s32.PutObjectCommand({
4700
- Bucket: exuluConfig?.fileUploads?.s3Bucket,
4701
- Key: key,
4702
- Body: data,
4703
- ContentType: mime
4902
+ (prev, cur) => {
4903
+ let config = configs?.find((config2) => config2.id === cur.id);
4904
+ const userDefinedConfigDescription = config?.config.find((config2) => config2.name === "description")?.value;
4905
+ const defaultConfigDescription = config?.config.find((config2) => config2.name === "description")?.default;
4906
+ const toolDescription = cur.description;
4907
+ const description = userDefinedConfigDescription || defaultConfigDescription || toolDescription;
4908
+ return {
4909
+ ...prev,
4910
+ [cur.name]: {
4911
+ ...cur.tool,
4912
+ description,
4913
+ async *execute(inputs, options) {
4914
+ if (!cur.tool?.execute) {
4915
+ console.error("[EXULU] Tool execute function is undefined.", cur.tool);
4916
+ throw new Error("Tool execute function is undefined.");
4917
+ }
4918
+ if (config) {
4919
+ config = await hydrateVariables(config || []);
4920
+ }
4921
+ let upload = void 0;
4922
+ if (exuluConfig?.fileUploads?.s3endpoint && exuluConfig?.fileUploads?.s3key && exuluConfig?.fileUploads?.s3secret && exuluConfig?.fileUploads?.s3Bucket) {
4923
+ s3Client2 ??= new import_client_s32.S3Client({
4924
+ region: exuluConfig?.fileUploads?.s3region,
4925
+ ...exuluConfig?.fileUploads?.s3endpoint && {
4926
+ forcePathStyle: true,
4927
+ endpoint: exuluConfig?.fileUploads?.s3endpoint
4928
+ },
4929
+ credentials: {
4930
+ accessKeyId: exuluConfig?.fileUploads?.s3key ?? "",
4931
+ secretAccessKey: exuluConfig?.fileUploads?.s3secret ?? ""
4932
+ }
4704
4933
  });
4705
- try {
4706
- const response2 = await s3Client2.send(command);
4707
- console.log(response2);
4708
- return response2;
4709
- } catch (caught) {
4710
- if (caught instanceof import_client_s32.S3ServiceException && caught.name === "EntityTooLarge") {
4711
- console.error(
4712
- `Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. The object was too large. To upload objects larger than 5GB, use the S3 console (160GB max) or the multipart upload API (5TB max).`
4713
- );
4714
- } else if (caught instanceof import_client_s32.S3ServiceException) {
4715
- console.error(
4716
- `Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. ${caught.name}: ${caught.message}`
4717
- );
4718
- } else {
4719
- throw caught;
4934
+ upload = async ({
4935
+ name,
4936
+ data,
4937
+ type,
4938
+ tags
4939
+ }) => {
4940
+ const mime = getMimeType(type);
4941
+ const prefix = exuluConfig?.fileUploads?.s3prefix ? `${exuluConfig.fileUploads.s3prefix.replace(/\/$/, "")}/` : "";
4942
+ const key = `${prefix}${user}/${generateS3Key(name)}${type}`;
4943
+ const command = new import_client_s32.PutObjectCommand({
4944
+ Bucket: exuluConfig?.fileUploads?.s3Bucket,
4945
+ Key: key,
4946
+ Body: data,
4947
+ ContentType: mime
4948
+ });
4949
+ try {
4950
+ const response2 = await s3Client2.send(command);
4951
+ console.log(response2);
4952
+ return response2;
4953
+ } catch (caught) {
4954
+ if (caught instanceof import_client_s32.S3ServiceException && caught.name === "EntityTooLarge") {
4955
+ console.error(
4956
+ `[EXULU] Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. The object was too large. To upload objects larger than 5GB, use the S3 console (160GB max) or the multipart upload API (5TB max).`
4957
+ );
4958
+ } else if (caught instanceof import_client_s32.S3ServiceException) {
4959
+ console.error(
4960
+ `[EXULU] Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. ${caught.name}: ${caught.message}`
4961
+ );
4962
+ } else {
4963
+ throw caught;
4964
+ }
4720
4965
  }
4721
- }
4722
- };
4723
- }
4724
- const contextsMap = contexts?.reduce((acc, curr) => {
4725
- acc[curr.id] = curr;
4726
- return acc;
4727
- }, {});
4728
- console.log("[EXULU] Config", config);
4729
- const response = await cur.tool.execute({
4730
- ...inputs,
4731
- // Convert config to object format if a config object
4732
- // is available, after we added the .value property
4733
- // by hydrating it from the variables table.
4734
- providerapikey,
4735
- allExuluTools,
4736
- currentTools,
4737
- user,
4738
- contexts: contextsMap,
4739
- upload,
4740
- config: config ? config.config.reduce((acc, curr) => {
4741
- acc[curr.name] = curr.value;
4966
+ };
4967
+ }
4968
+ const contextsMap = contexts?.reduce((acc, curr) => {
4969
+ acc[curr.id] = curr;
4742
4970
  return acc;
4743
- }, {}) : {}
4744
- }, options);
4745
- await updateStatistic({
4746
- name: "count",
4747
- label: cur.name,
4748
- type: STATISTICS_TYPE_ENUM.TOOL_CALL,
4749
- trigger: "agent",
4750
- count: 1,
4751
- user: user?.id,
4752
- role: user?.role?.id
4753
- });
4754
- yield response;
4755
- return response;
4971
+ }, {});
4972
+ console.log("[EXULU] Config", config);
4973
+ const response = await cur.tool.execute({
4974
+ ...inputs,
4975
+ sessionID,
4976
+ // Convert config to object format if a config object
4977
+ // is available, after we added the .value property
4978
+ // by hydrating it from the variables table.
4979
+ providerapikey,
4980
+ allExuluTools,
4981
+ currentTools,
4982
+ user,
4983
+ contexts: contextsMap,
4984
+ upload,
4985
+ config: config ? config.config.reduce((acc, curr) => {
4986
+ acc[curr.name] = curr.value;
4987
+ return acc;
4988
+ }, {}) : {}
4989
+ }, options);
4990
+ await updateStatistic({
4991
+ name: "count",
4992
+ label: cur.name,
4993
+ type: STATISTICS_TYPE_ENUM.TOOL_CALL,
4994
+ trigger: "agent",
4995
+ count: 1,
4996
+ user: user?.id,
4997
+ role: user?.role?.id
4998
+ });
4999
+ yield response;
5000
+ return response;
5001
+ }
4756
5002
  }
4757
- }
4758
- }),
5003
+ };
5004
+ },
4759
5005
  {}
4760
5006
  )
4761
5007
  // askForConfirmation
@@ -4764,6 +5010,9 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
4764
5010
  var hydrateVariables = async (tool2) => {
4765
5011
  const { db: db3 } = await postgresClient();
4766
5012
  const promises = tool2.config.map(async (toolConfig) => {
5013
+ if (!toolConfig.variable) {
5014
+ return toolConfig;
5015
+ }
4767
5016
  const variableName = toolConfig.variable;
4768
5017
  const variable = await db3.from("variables").where({ name: variableName }).first();
4769
5018
  if (!variable) {
@@ -4776,6 +5025,7 @@ var hydrateVariables = async (tool2) => {
4776
5025
  value = bytes.toString(import_crypto_js2.default.enc.Utf8);
4777
5026
  }
4778
5027
  toolConfig.value = value;
5028
+ return toolConfig;
4779
5029
  });
4780
5030
  await Promise.all(promises);
4781
5031
  console.log("[EXULU] Variable values retrieved and added to tool config.");
@@ -5033,7 +5283,8 @@ var ExuluAgent2 = class {
5033
5283
  providerapikey,
5034
5284
  contexts,
5035
5285
  user,
5036
- exuluConfig
5286
+ exuluConfig,
5287
+ session
5037
5288
  ),
5038
5289
  stopWhen: [(0, import_ai.stepCountIs)(2)]
5039
5290
  });
@@ -5091,7 +5342,8 @@ var ExuluAgent2 = class {
5091
5342
  providerapikey,
5092
5343
  contexts,
5093
5344
  user,
5094
- exuluConfig
5345
+ exuluConfig,
5346
+ session
5095
5347
  ),
5096
5348
  stopWhen: [(0, import_ai.stepCountIs)(2)]
5097
5349
  });
@@ -5204,7 +5456,8 @@ var ExuluAgent2 = class {
5204
5456
  providerapikey,
5205
5457
  contexts,
5206
5458
  user,
5207
- exuluConfig
5459
+ exuluConfig,
5460
+ session
5208
5461
  ),
5209
5462
  onError: (error) => {
5210
5463
  console.error("[EXULU] chat stream error.", error);
@@ -5229,6 +5482,15 @@ var getAgentMessages = async ({ session, user, limit, page }) => {
5229
5482
  const messages = await query;
5230
5483
  return messages;
5231
5484
  };
5485
+ var getSession = async ({ sessionID }) => {
5486
+ const { db: db3 } = await postgresClient();
5487
+ console.log("[EXULU] getting session for session ID: " + sessionID);
5488
+ const session = await db3.from("agent_sessions").where({ id: sessionID }).first();
5489
+ if (!session) {
5490
+ throw new Error("Session not found for session ID: " + sessionID);
5491
+ }
5492
+ return session;
5493
+ };
5232
5494
  var saveChat = async ({ session, user, messages }) => {
5233
5495
  const { db: db3 } = await postgresClient();
5234
5496
  const promises = messages.map((message) => {
@@ -5516,6 +5778,9 @@ var ExuluContext = class {
5516
5778
  };
5517
5779
  };
5518
5780
  createItem = async (item, config, user, role, upsert) => {
5781
+ if (upsert && (!item.id && !item.external_id)) {
5782
+ throw new Error("Item id or external id is required for upsert.");
5783
+ }
5519
5784
  const { db: db3 } = await postgresClient();
5520
5785
  const mutation = db3.from(getTableName(
5521
5786
  this.id
@@ -5943,6 +6208,8 @@ var {
5943
6208
  variablesSchema: variablesSchema2,
5944
6209
  workflowTemplatesSchema: workflowTemplatesSchema2,
5945
6210
  rbacSchema: rbacSchema2,
6211
+ promptLibrarySchema: promptLibrarySchema2,
6212
+ promptFavoritesSchema: promptFavoritesSchema2,
5946
6213
  statisticsSchema: statisticsSchema2
5947
6214
  } = coreSchemas.get();
5948
6215
  var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tracer, queues2) => {
@@ -5973,6 +6240,8 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tr
5973
6240
  agentsSchema2(),
5974
6241
  projectsSchema2(),
5975
6242
  jobResultsSchema2(),
6243
+ promptLibrarySchema2(),
6244
+ promptFavoritesSchema2(),
5976
6245
  evalRunsSchema2(),
5977
6246
  platformConfigurationsSchema2(),
5978
6247
  evalSetsSchema2(),
@@ -5996,25 +6265,13 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tr
5996
6265
  import_express3.default.json({ limit: REQUEST_SIZE_LIMIT }),
5997
6266
  (0, import_express5.expressMiddleware)(server, {
5998
6267
  context: async ({ req }) => {
5999
- console.info("[EXULU] Incoming graphql request", {
6000
- message: "Incoming Request",
6001
- method: req.method,
6002
- path: req.path,
6003
- requestId: "req-" + Date.now(),
6004
- ipAddress: req.ip,
6005
- userAgent: req.get("User-Agent"),
6006
- headers: {
6007
- "authorization": req.headers["authorization"],
6008
- "exulu-api-key": req.headers["exulu-api-key"],
6009
- "origin": req.headers["origin"],
6010
- "...": "..."
6011
- }
6012
- });
6013
6268
  const authenticationResult = await requestValidators.authenticate(req);
6014
6269
  if (!authenticationResult.user?.id) {
6270
+ console.error("[EXULU] Authentication failed", authenticationResult);
6015
6271
  throw new Error(authenticationResult.message);
6016
6272
  }
6017
6273
  const { db: db3 } = await postgresClient();
6274
+ console.log("[EXULU] Graphql call");
6018
6275
  return {
6019
6276
  req,
6020
6277
  db: db3,
@@ -6628,7 +6885,7 @@ var import_ai3 = require("ai");
6628
6885
  var import_crypto_js4 = __toESM(require("crypto-js"), 1);
6629
6886
 
6630
6887
  // src/registry/log-metadata.ts
6631
- function logMetadata(id, additionalMetadata) {
6888
+ function logMetadata2(id, additionalMetadata) {
6632
6889
  return {
6633
6890
  __logMetadata: true,
6634
6891
  id,
@@ -6639,16 +6896,6 @@ function logMetadata(id, additionalMetadata) {
6639
6896
  // src/registry/workers.ts
6640
6897
  var redisConnection;
6641
6898
  var createWorkers = async (agents, queues2, config, contexts, evals, tools, tracer) => {
6642
- console.log(`
6643
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
6644
- \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
6645
- \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
6646
- \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
6647
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
6648
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
6649
- Intelligence Management Platform - Workers
6650
-
6651
- `);
6652
6899
  console.log("[EXULU] creating workers for " + queues2?.length + " queues.");
6653
6900
  console.log("[EXULU] queues", queues2.map((q) => q.queue.name));
6654
6901
  if (!redisServer.host || !redisServer.port) {
@@ -6675,7 +6922,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6675
6922
  const worker = new import_bullmq4.Worker(
6676
6923
  `${queue.queue.name}`,
6677
6924
  async (bullmqJob) => {
6678
- console.log("[EXULU] starting execution for job", logMetadata(bullmqJob.name, {
6925
+ console.log("[EXULU] starting execution for job", logMetadata2(bullmqJob.name, {
6679
6926
  name: bullmqJob.name,
6680
6927
  status: await bullmqJob.getState(),
6681
6928
  type: bullmqJob.data.type
@@ -6692,7 +6939,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6692
6939
  try {
6693
6940
  bullmq.validate(bullmqJob.id, data);
6694
6941
  if (data.type === "embedder") {
6695
- console.log("[EXULU] running an embedder job.", logMetadata(bullmqJob.name));
6942
+ console.log("[EXULU] running an embedder job.", logMetadata2(bullmqJob.name));
6696
6943
  const label = `embedder-${bullmqJob.name}`;
6697
6944
  await db3.from("job_results").insert({
6698
6945
  job_id: bullmqJob.id,
@@ -6722,7 +6969,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6722
6969
  };
6723
6970
  }
6724
6971
  if (data.type === "processor") {
6725
- console.log("[EXULU] running a processor job.", logMetadata(bullmqJob.name));
6972
+ console.log("[EXULU] running a processor job.", logMetadata2(bullmqJob.name));
6726
6973
  const label = `processor-${bullmqJob.name}`;
6727
6974
  await db3.from("job_results").insert({
6728
6975
  job_id: bullmqJob.id,
@@ -6769,10 +7016,11 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6769
7016
  };
6770
7017
  }
6771
7018
  if (data.type === "eval_run") {
6772
- console.log("[EXULU] running an eval run job.", logMetadata(bullmqJob.name));
7019
+ console.log("[EXULU] running an eval run job.", logMetadata2(bullmqJob.name));
6773
7020
  const label = `eval-run-${data.eval_run_id}-${data.test_case_id}`;
6774
7021
  const existingResult = await db3.from("job_results").where({ label }).first();
6775
7022
  if (existingResult) {
7023
+ console.log("[EXULU] found existing job result, so ");
6776
7024
  await db3.from("job_results").where({ label }).update({
6777
7025
  job_id: bullmqJob.id,
6778
7026
  label,
@@ -6817,7 +7065,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6817
7065
  resolve(messages2);
6818
7066
  break;
6819
7067
  } catch (error) {
6820
- console.error(`[EXULU] error processing UI messages flow for agent ${agentInstance.name} (${agentInstance.id}).`, logMetadata(bullmqJob.name, {
7068
+ console.error(`[EXULU] error processing UI messages flow for agent ${agentInstance.name} (${agentInstance.id}).`, logMetadata2(bullmqJob.name, {
6821
7069
  error: error instanceof Error ? error.message : String(error)
6822
7070
  }));
6823
7071
  attempts++;
@@ -6874,9 +7122,11 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6874
7122
  test_case_id: testCase.id,
6875
7123
  eval_run_id: evalRun.id,
6876
7124
  eval_function_id: evalFunction.id,
7125
+ eval_function_name: evalFunction.name,
7126
+ eval_function_config: evalFunction.config || {},
6877
7127
  result: result2 || 0
6878
7128
  };
6879
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata(bullmqJob.name, {
7129
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
6880
7130
  result: result2 || 0
6881
7131
  }));
6882
7132
  evalFunctionResults.push(evalFunctionResult);
@@ -6895,34 +7145,44 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6895
7145
  result: result2 || 0
6896
7146
  };
6897
7147
  evalFunctionResults.push(evalFunctionResult);
6898
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata(bullmqJob.name, {
7148
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
6899
7149
  result: result2 || 0
6900
7150
  }));
6901
7151
  }
6902
7152
  }
6903
7153
  const scores = evalFunctionResults.map((result2) => result2.result);
7154
+ console.log("[EXULU] Exulu eval run scores for test case: " + testCase.id, scores);
6904
7155
  let score = 0;
6905
- switch (data.scoring_method) {
7156
+ switch (data.scoring_method?.toLowerCase()) {
6906
7157
  case "median":
7158
+ console.log("[EXULU] Calculating median score");
6907
7159
  score = getMedian(scores);
6908
7160
  break;
6909
7161
  case "average":
7162
+ console.log("[EXULU] Calculating average score");
6910
7163
  score = getAverage(scores);
6911
7164
  break;
6912
7165
  case "sum":
7166
+ console.log("[EXULU] Calculating sum score");
6913
7167
  score = getSum(scores);
6914
7168
  break;
7169
+ default:
7170
+ console.log("[EXULU] Calculating average score");
7171
+ score = getAverage(scores);
6915
7172
  }
6916
7173
  return {
6917
7174
  result: score,
6918
7175
  metadata: {
6919
- ...evalFunctionResults,
7176
+ messages,
7177
+ function_results: [
7178
+ ...evalFunctionResults
7179
+ ],
6920
7180
  ...metadata
6921
7181
  }
6922
7182
  };
6923
7183
  }
6924
7184
  if (data.type === "eval_function") {
6925
- console.log("[EXULU] running an eval function job.", logMetadata(bullmqJob.name));
7185
+ console.log("[EXULU] running an eval function job.", logMetadata2(bullmqJob.name));
6926
7186
  if (data.eval_functions?.length !== 1) {
6927
7187
  throw new Error(`Expected 1 eval function for eval function job, got ${data.eval_functions?.length}.`);
6928
7188
  }
@@ -6968,7 +7228,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6968
7228
  inputMessages,
6969
7229
  evalFunction.config || {}
6970
7230
  );
6971
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result}`, logMetadata(bullmqJob.name, {
7231
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result}`, logMetadata2(bullmqJob.name, {
6972
7232
  result: result || 0
6973
7233
  }));
6974
7234
  }
@@ -6978,7 +7238,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6978
7238
  };
6979
7239
  }
6980
7240
  if (data.type === "source") {
6981
- console.log("[EXULU] running a source job.", logMetadata(bullmqJob.name));
7241
+ console.log("[EXULU] running a source job.", logMetadata2(bullmqJob.name));
6982
7242
  if (!data.source) {
6983
7243
  throw new Error(`No source id set for source job.`);
6984
7244
  }
@@ -7000,18 +7260,27 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7000
7260
  const { item: createdItem, job } = await context.createItem(item, config, data.user, data.role);
7001
7261
  if (job) {
7002
7262
  jobs.push(job);
7003
- console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`, logMetadata(bullmqJob.name, {
7263
+ console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`, logMetadata2(bullmqJob.name, {
7004
7264
  item: createdItem,
7005
7265
  job
7006
7266
  }));
7007
7267
  }
7008
7268
  if (createdItem.id) {
7009
7269
  items.push(createdItem.id);
7010
- console.log(`[EXULU] created item through source update job ${createdItem.id}`, logMetadata(bullmqJob.name, {
7270
+ console.log(`[EXULU] created item through source update job ${createdItem.id}`, logMetadata2(bullmqJob.name, {
7011
7271
  item: createdItem
7012
7272
  }));
7013
7273
  }
7014
7274
  }
7275
+ await updateStatistic({
7276
+ name: "count",
7277
+ label: source.id,
7278
+ type: STATISTICS_TYPE_ENUM.SOURCE_UPDATE,
7279
+ trigger: "api",
7280
+ count: 1,
7281
+ user: data?.user,
7282
+ role: data?.role
7283
+ });
7015
7284
  return {
7016
7285
  result,
7017
7286
  metadata: {
@@ -7042,7 +7311,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7042
7311
  }
7043
7312
  );
7044
7313
  worker.on("completed", async (job, returnvalue) => {
7045
- console.log(`[EXULU] completed job ${job.id}.`, logMetadata(job.name, {
7314
+ console.log(`[EXULU] completed job ${job.id}.`, logMetadata2(job.name, {
7046
7315
  result: returnvalue
7047
7316
  }));
7048
7317
  const { db: db3 } = await postgresClient();
@@ -7062,7 +7331,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7062
7331
  });
7063
7332
  return;
7064
7333
  }
7065
- console.error(`[EXULU] job failed.`, job?.name ? logMetadata(job.name, {
7334
+ console.error(`[EXULU] job failed.`, job?.name ? logMetadata2(job.name, {
7066
7335
  error: error instanceof Error ? error.message : String(error)
7067
7336
  }) : error);
7068
7337
  });
@@ -7070,7 +7339,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7070
7339
  console.error(`[EXULU] worker error.`, error);
7071
7340
  });
7072
7341
  worker.on("progress", (job, progress) => {
7073
- console.log(`[EXULU] job progress ${job.id}.`, logMetadata(job.name, {
7342
+ console.log(`[EXULU] job progress ${job.id}.`, logMetadata2(job.name, {
7074
7343
  progress
7075
7344
  }));
7076
7345
  });
@@ -7449,6 +7718,92 @@ var ExuluMCP = class {
7449
7718
  });
7450
7719
  server.tools[tool2.id] = tool2.name;
7451
7720
  }
7721
+ const getListOfPromptTemplatesName = "getListOfPromptTemplates";
7722
+ if (!server.tools[getListOfPromptTemplatesName]) {
7723
+ server.mcp.registerTool(getListOfPromptTemplatesName, {
7724
+ title: "Get List of Prompt Templates",
7725
+ description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
7726
+ inputSchema: {
7727
+ inputs: import_zod2.z.object({})
7728
+ }
7729
+ }, async ({ inputs }, args) => {
7730
+ console.log("[EXULU] Getting list of prompt templates for agent", agentInstance.id);
7731
+ const { db: db4 } = await postgresClient();
7732
+ const prompts = await db4.from("prompt_library").select("id", "name", "description").whereRaw("assigned_agents @> ?::jsonb", [JSON.stringify(agentInstance.id)]).orderBy("updatedAt", "desc");
7733
+ console.log("[EXULU] Found", prompts.length, "prompt templates");
7734
+ return {
7735
+ content: [{
7736
+ type: "text",
7737
+ text: JSON.stringify({
7738
+ prompts: prompts.map((p) => ({
7739
+ id: p.id,
7740
+ name: p.name,
7741
+ description: p.description || "No description provided"
7742
+ })),
7743
+ count: prompts.length
7744
+ }, null, 2)
7745
+ }],
7746
+ structuredContent: {
7747
+ prompts: prompts.map((p) => ({
7748
+ id: p.id,
7749
+ name: p.name,
7750
+ description: p.description || "No description provided"
7751
+ })),
7752
+ count: prompts.length
7753
+ }
7754
+ };
7755
+ });
7756
+ }
7757
+ const getPromptTemplateDetailsName = "getPromptTemplateDetails";
7758
+ if (!server.tools[getPromptTemplateDetailsName]) {
7759
+ server.mcp.registerTool(getPromptTemplateDetailsName, {
7760
+ title: "Get Prompt Template Details",
7761
+ description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
7762
+ inputSchema: {
7763
+ inputs: import_zod2.z.object({
7764
+ id: import_zod2.z.string().describe("The ID of the prompt template to retrieve")
7765
+ })
7766
+ }
7767
+ }, async ({ inputs }, args) => {
7768
+ console.log("[EXULU] Getting prompt template details for ID", inputs.id);
7769
+ const { db: db4 } = await postgresClient();
7770
+ const prompt = await db4.from("prompt_library").select("id", "name", "description", "content", "createdAt", "updatedAt", "usage_count", "favorite_count").where({ id: inputs.id }).first();
7771
+ if (!prompt) {
7772
+ throw new Error(`Prompt template with ID ${inputs.id} not found`);
7773
+ }
7774
+ const isAssignedToAgent = await db4.from("prompt_library").select("id").where({ id: inputs.id }).whereRaw("assigned_agents @> ?::jsonb", [JSON.stringify(agentInstance.id)]).first();
7775
+ console.log("[EXULU] Prompt template found:", prompt.name);
7776
+ return {
7777
+ content: [{
7778
+ type: "text",
7779
+ text: JSON.stringify({
7780
+ id: prompt.id,
7781
+ name: prompt.name,
7782
+ description: prompt.description || "No description provided",
7783
+ content: prompt.content,
7784
+ createdAt: prompt.createdAt,
7785
+ updatedAt: prompt.updatedAt,
7786
+ usageCount: prompt.usage_count || 0,
7787
+ favoriteCount: prompt.favorite_count || 0,
7788
+ isAssignedToThisAgent: !!isAssignedToAgent
7789
+ }, null, 2)
7790
+ }],
7791
+ structuredContent: {
7792
+ id: prompt.id,
7793
+ name: prompt.name,
7794
+ description: prompt.description || "No description provided",
7795
+ content: prompt.content,
7796
+ createdAt: prompt.createdAt,
7797
+ updatedAt: prompt.updatedAt,
7798
+ usageCount: prompt.usage_count || 0,
7799
+ favoriteCount: prompt.favorite_count || 0,
7800
+ isAssignedToThisAgent: !!isAssignedToAgent
7801
+ }
7802
+ };
7803
+ });
7804
+ }
7805
+ server.tools[getListOfPromptTemplatesName] = getListOfPromptTemplatesName;
7806
+ server.tools[getPromptTemplateDetailsName] = getPromptTemplateDetailsName;
7452
7807
  return server.mcp;
7453
7808
  };
7454
7809
  create = async ({ express: express3, allTools, allAgents, allContexts, config }) => {
@@ -8063,8 +8418,8 @@ var llmAsJudgeEval = () => {
8063
8418
  console.log("[EXULU] running llm as judge eval", { agent, backend, messages, testCase, config });
8064
8419
  let prompt = config?.prompt;
8065
8420
  if (!prompt) {
8066
- console.error("[EXULU] prompt is required.");
8067
- throw new Error("Prompt is required.");
8421
+ console.error("[EXULU] prompt is required for llm as judge eval but none is provided.");
8422
+ throw new Error("Prompt is required for llm as judge eval but none is provided.");
8068
8423
  }
8069
8424
  const lastMessage = messages[messages.length - 1]?.parts?.filter((part) => part.type === "text").map((part) => part.text).join("\n");
8070
8425
  console.log("[EXULU] last message", lastMessage);
@@ -8722,6 +9077,280 @@ var mathTools = [
8722
9077
  degreesToRadiansTool
8723
9078
  ];
8724
9079
 
9080
+ // src/templates/tools/todo/todowrite.txt
9081
+ var todowrite_default = `Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
9082
+ It also helps the user understand the progress of the task and overall progress of their requests.
9083
+
9084
+ ## When to Use This Tool
9085
+ Use this tool proactively in these scenarios:
9086
+
9087
+ 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
9088
+ 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
9089
+ 3. User explicitly requests todo list - When the user directly asks you to use the todo list
9090
+ 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
9091
+ 5. After receiving new instructions - Immediately capture user requirements as todos. Feel free to edit the todo list based on new information.
9092
+ 6. After completing a task - Mark it complete and add any new follow-up tasks
9093
+ 7. When you start working on a new task, mark the todo as in_progress. Ideally you should only have one todo as in_progress at a time. Complete existing tasks before starting new ones.
9094
+
9095
+ ## When NOT to Use This Tool
9096
+
9097
+ Skip using this tool when:
9098
+ 1. There is only a single, straightforward task
9099
+ 2. The task is trivial and tracking it provides no organizational benefit
9100
+ 3. The task can be completed in less than 3 trivial steps
9101
+ 4. The task is purely conversational or informational
9102
+
9103
+ NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
9104
+
9105
+ ## Examples of When to Use the Todo List
9106
+
9107
+ <example>
9108
+ User: I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when you're done!
9109
+ Assistant: I'll help add a dark mode toggle to your application settings. Let me create a todo list to track this implementation.
9110
+ *Creates todo list with the following items:*
9111
+ 1. Create dark mode toggle component in Settings page
9112
+ 2. Add dark mode state management (context/store)
9113
+ 3. Implement CSS-in-JS styles for dark theme
9114
+ 4. Update existing components to support theme switching
9115
+ 5. Run tests and build process, addressing any failures or errors that occur
9116
+ *Begins working on the first task*
9117
+
9118
+ <reasoning>
9119
+ The assistant used the todo list because:
9120
+ 1. Adding dark mode is a multi-step feature requiring UI, state management, and styling changes
9121
+ 2. The user explicitly requested tests and build be run afterward
9122
+ 3. The assistant inferred that tests and build need to pass by adding "Ensure tests and build succeed" as the final task
9123
+ </reasoning>
9124
+ </example>
9125
+
9126
+ <example>
9127
+ User: Help me rename the function getCwd to getCurrentWorkingDirectory across my project
9128
+ Assistant: Let me first search through your codebase to find all occurrences of 'getCwd'.
9129
+ *Uses grep or search tools to locate all instances of getCwd in the codebase*
9130
+ Assistant: I've found 15 instances of 'getCwd' across 8 different files. Let me create a todo list to track these changes.
9131
+ *Creates todo list with specific items for each file that needs updating*
9132
+
9133
+ <reasoning>
9134
+ The assistant used the todo list because:
9135
+ 1. First, the assistant searched to understand the scope of the task
9136
+ 2. Upon finding multiple occurrences across different files, it determined this was a complex task with multiple steps
9137
+ 3. The todo list helps ensure every instance is tracked and updated systematically
9138
+ 4. This approach prevents missing any occurrences and maintains code consistency
9139
+ </reasoning>
9140
+ </example>
9141
+
9142
+
9143
+ <example>
9144
+ User: I need to implement these features for my e-commerce site: user registration, product catalog, shopping cart, and checkout flow.
9145
+ Assistant: I'll help implement these features. First, let's add all the features to the todo list.
9146
+ *Creates a todo list breaking down each feature into specific tasks based on the project architecture*
9147
+ Assistant: Let's start with user registration. This will involve creating database models, API endpoints, and frontend forms that integrate with your existing authentication system.
9148
+
9149
+ <reasoning>
9150
+ The assistant used the todo list because:
9151
+ 1. The user provided multiple complex features to implement in a comma separated list
9152
+ 2. The todo list helps organize these large features into manageable tasks
9153
+ 3. This approach allows for tracking progress across the entire implementation
9154
+ </reasoning>
9155
+ </example>
9156
+
9157
+ <example>
9158
+ User: Can you help optimize my React application? It's rendering slowly and has performance issues.</user>
9159
+ Assistant: I'll help optimize your React application. First, let me examine your codebase to identify potential performance bottlenecks.
9160
+ *Reviews component structure, render patterns, state management, and data fetching*
9161
+ Assistant: After analyzing your codebase, I've identified several performance issues. Let me create a todo list to track our optimization efforts.
9162
+ *Creates todo list with items like: 1) Implement memoization for expensive calculations in ProductList, 2) Add virtualization for long lists in Dashboard, 3) Optimize image loading in Gallery component, 4) Fix state update loops in ShoppingCart, 5) Review bundle size and implement code splitting*
9163
+ Let's start by implementing memoization for the expensive calculations in your ProductList component.</assistant>
9164
+
9165
+ <reasoning>
9166
+ The assistant used the todo list because:
9167
+ 1. First, the assistant examined the codebase to identify specific performance issues
9168
+ 2. Based on this analysis, it identified multiple optimization opportunities
9169
+ 3. Performance optimization is a non-trivial task requiring multiple steps
9170
+ 4. The todo list helps methodically track improvements across different components
9171
+ 5. This systematic approach ensures all performance bottlenecks are addressed
9172
+ </reasoning>
9173
+ </example>
9174
+
9175
+ ## Examples of When NOT to Use the Todo List
9176
+
9177
+ <example>
9178
+ User: How do I print 'Hello World' in Python?
9179
+ Assistant: In Python, you can print "Hello World" with this simple code:
9180
+
9181
+ python
9182
+ print("Hello World")
9183
+
9184
+ This will output the text "Hello World" to the console when executed.</assistant>
9185
+
9186
+ <reasoning>
9187
+ The assistant did not use the todo list because this is a single, trivial task that can be completed in one step. There's no need to track multiple tasks or steps for such a straightforward request.
9188
+ </reasoning>
9189
+ </example>
9190
+
9191
+ <example>
9192
+ User: What does the git status command do?
9193
+ Assistant: The git status command shows the current state of your working directory and staging area. It displays which changes have been staged, which haven't, and which files aren't being tracked by Git. This is a fundamental command for understanding the current state of your repository before making commits.
9194
+
9195
+ <reasoning>
9196
+ The assistant did not use the todo list because this is an informational request with no actual coding task to complete. The user is simply asking for an explanation, not for the assistant to perform multiple steps or tasks.
9197
+ </reasoning>
9198
+ </example>
9199
+
9200
+ <example>
9201
+ User: Can you add a comment to the calculateTotal function to explain what it does?
9202
+ Assistant: Sure, let me add a comment to the calculateTotal function to explain what it does.
9203
+ * Uses the Edit tool to add a comment to the calculateTotal function *
9204
+
9205
+ <reasoning>
9206
+ The assistant did not use the todo list because this is a single, straightforward task confined to one location in the code. Adding a comment doesn't require tracking multiple steps or systematic organization.
9207
+ </reasoning>
9208
+ </example>
9209
+
9210
+ <example>
9211
+ User: Run npm install for me and tell me what happens.
9212
+ Assistant: I'll run the npm install command for you.
9213
+
9214
+ *Executes: npm install*
9215
+
9216
+ The command completed successfully. Here's the output:
9217
+ [Output of npm install command]
9218
+
9219
+ All dependencies have been installed according to your package.json file.
9220
+
9221
+ <reasoning>
9222
+ The assistant did not use the todo list because this is a single command execution with immediate results. There are no multiple steps to track or organize, making the todo list unnecessary for this straightforward task.
9223
+ </reasoning>
9224
+ </example>
9225
+
9226
+ ## Task States and Management
9227
+
9228
+ 1. **Task States**: Use these states to track progress:
9229
+ - pending: Task not yet started
9230
+ - in_progress: Currently working on (limit to ONE task at a time)
9231
+ - completed: Task finished successfully
9232
+ - cancelled: Task no longer needed
9233
+
9234
+ 2. **Task Management**:
9235
+ - Update task status in real-time as you work
9236
+ - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
9237
+ - Only have ONE task in_progress at any time
9238
+ - Complete current tasks before starting new ones
9239
+ - Cancel tasks that become irrelevant
9240
+
9241
+ 3. **Task Breakdown**:
9242
+ - Create specific, actionable items
9243
+ - Break complex tasks into smaller, manageable steps
9244
+ - Use clear, descriptive task names
9245
+
9246
+ When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
9247
+ `;
9248
+
9249
+ // src/templates/tools/todo/todoread.txt
9250
+ var todoread_default = `Use this tool to read the current to-do list for the session. This tool should be used proactively and frequently to ensure that you are aware of
9251
+ the status of the current task list. You should make use of this tool as often as possible, especially in the following situations:
9252
+ - At the beginning of conversations to see what's pending
9253
+ - Before starting new tasks to prioritize work
9254
+ - When the user asks about previous tasks or plans
9255
+ - Whenever you're uncertain about what to do next
9256
+ - After completing tasks to update your understanding of remaining work
9257
+ - After every few messages to ensure you're on track
9258
+
9259
+ Usage:
9260
+ - This tool takes in no parameters. So leave the input blank or empty. DO NOT include a dummy object, placeholder string or a key like "input" or "empty". LEAVE IT BLANK.
9261
+ - Returns a list of todo items with their status, priority, and content
9262
+ - Use this information to track progress and plan next steps
9263
+ - If no todos exist yet, an empty list will be returned`;
9264
+
9265
+ // src/templates/tools/todo/todo.ts
9266
+ var import_zod5 = __toESM(require("zod"), 1);
9267
+ var TodoSchema = import_zod5.default.object({
9268
+ content: import_zod5.default.string().describe("Brief description of the task"),
9269
+ status: import_zod5.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
9270
+ priority: import_zod5.default.string().describe("Priority level of the task: high, medium, low"),
9271
+ id: import_zod5.default.string().describe("Unique identifier for the todo item")
9272
+ });
9273
+ var TodoWriteTool = new ExuluTool2({
9274
+ id: "todo_write",
9275
+ name: "Todo Write",
9276
+ description: "Use this tool to write your todo list",
9277
+ type: "function",
9278
+ category: "todo",
9279
+ config: [{
9280
+ name: "description",
9281
+ description: "The description of the todo list, if set overwrites the default description.",
9282
+ default: todowrite_default
9283
+ }],
9284
+ inputSchema: import_zod5.default.object({
9285
+ todos: import_zod5.default.array(TodoSchema).describe("The updated todo list")
9286
+ }),
9287
+ execute: async (inputs) => {
9288
+ const { sessionID, todos, user } = inputs;
9289
+ if (!user) {
9290
+ throw new Error("No authenticated user available, a user is required for the todo write tool, this likely means the tool was called outside a session like in an MCP or API call instead of as part of an authenticated session.");
9291
+ }
9292
+ if (!sessionID) {
9293
+ throw new Error("Session ID is required for the todo write tool, this likely means the tool was called outside a session like in an MCP or API call instead of as part of a conversation.");
9294
+ }
9295
+ const session = await getSession({ sessionID });
9296
+ if (!session?.id) {
9297
+ throw new Error("Session with ID " + sessionID + " not found in the todo write tool.");
9298
+ }
9299
+ const hasAccessToSession = await checkRecordAccess(session, "read", user);
9300
+ if (!hasAccessToSession) {
9301
+ throw new Error("You don't have access to this session " + session.id + ".");
9302
+ }
9303
+ await updateTodos({
9304
+ session,
9305
+ todos
9306
+ });
9307
+ return {
9308
+ result: JSON.stringify(todos, null, 2)
9309
+ };
9310
+ }
9311
+ });
9312
+ var TodoReadTool = new ExuluTool2({
9313
+ id: "todo_read",
9314
+ name: "Todo Read",
9315
+ description: "Use this tool to read your todo list",
9316
+ inputSchema: import_zod5.default.object({}),
9317
+ type: "function",
9318
+ category: "todo",
9319
+ config: [{
9320
+ name: "description",
9321
+ description: "The description of the todo list, if set overwrites the default description.",
9322
+ default: todoread_default
9323
+ }],
9324
+ execute: async (inputs) => {
9325
+ const { sessionID } = inputs;
9326
+ let todos = await getTodos(sessionID);
9327
+ return {
9328
+ result: JSON.stringify(todos, null, 2)
9329
+ };
9330
+ }
9331
+ });
9332
+ async function updateTodos(input) {
9333
+ const metadata = input.session.metadata || {};
9334
+ metadata["todos"] = input.todos;
9335
+ const { db: db3 } = await postgresClient();
9336
+ await db3.from("agent_sessions").where({ id: input.session.id }).update({
9337
+ metadata
9338
+ });
9339
+ return input.session;
9340
+ }
9341
+ async function getTodos(sessionID) {
9342
+ const { db: db3 } = await postgresClient();
9343
+ const session = await db3.from("agent_sessions").where({ id: sessionID }).first();
9344
+ if (!session) {
9345
+ throw new Error("Session not found for session ID: " + sessionID);
9346
+ }
9347
+ return session.metadata?.todos || [];
9348
+ }
9349
+ var todoTools = [
9350
+ TodoWriteTool,
9351
+ TodoReadTool
9352
+ ];
9353
+
8725
9354
  // src/registry/index.ts
8726
9355
  var isDev = process.env.NODE_ENV !== "production";
8727
9356
  var consoleTransport = new import_winston2.default.transports.Console({
@@ -8794,6 +9423,7 @@ var ExuluApp = class {
8794
9423
  this._tools = [
8795
9424
  ...tools ?? [],
8796
9425
  ...mathTools,
9426
+ ...todoTools,
8797
9427
  // Add contexts as tools
8798
9428
  ...Object.values(contexts || {}).map((context) => context.tool())
8799
9429
  // Because agents are stored in the database, we add those as tools
@@ -8905,6 +9535,16 @@ var ExuluApp = class {
8905
9535
  bullmq = {
8906
9536
  workers: {
8907
9537
  create: async (queues2) => {
9538
+ console.log(`
9539
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
9540
+ \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
9541
+ \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
9542
+ \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
9543
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
9544
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
9545
+ Intelligence Management Platform - Workers
9546
+
9547
+ `);
8908
9548
  if (!this._config) {
8909
9549
  throw new Error("Config not initialized, make sure to call await ExuluApp.create() first when starting your server.");
8910
9550
  }
@@ -8926,16 +9566,26 @@ var ExuluApp = class {
8926
9566
  if (queues2) {
8927
9567
  filteredQueues = filteredQueues.filter((q) => queues2.includes(q.queue.name));
8928
9568
  }
8929
- const sources = Object.values(this._contexts ?? {}).flatMap((context) => ({
8930
- ...context.sources,
8931
- context: context.id
8932
- }));
9569
+ const contexts = Object.values(this._contexts ?? {});
9570
+ let sources = [];
9571
+ for (const context of contexts) {
9572
+ for (const source of context.sources) {
9573
+ sources.push({
9574
+ ...source,
9575
+ context: context.id
9576
+ });
9577
+ }
9578
+ }
8933
9579
  if (sources.length > 0) {
8934
9580
  console.log("[EXULU] Creating ContextSource schedulers for", sources.length, "sources.");
8935
9581
  for (const source of sources) {
8936
9582
  const queue = await source.config?.queue;
9583
+ if (!queue) {
9584
+ console.warn("[EXULU] No queue configured for source", source.name);
9585
+ continue;
9586
+ }
8937
9587
  if (queue) {
8938
- if (!source.config.schedule) {
9588
+ if (!source.config?.schedule) {
8939
9589
  throw new Error("Schedule is required for source when configuring a queue: " + source.name);
8940
9590
  }
8941
9591
  console.log("[EXULU] Creating ContextSource scheduler for", source.name, "in queue", queue.queue?.name);
@@ -8947,7 +9597,8 @@ var ExuluApp = class {
8947
9597
  data: {
8948
9598
  source: source.id,
8949
9599
  context: source.context,
8950
- type: "source"
9600
+ type: "source",
9601
+ inputs: {}
8951
9602
  },
8952
9603
  opts: {
8953
9604
  backoff: {
@@ -10233,7 +10884,9 @@ var {
10233
10884
  workflowTemplatesSchema: workflowTemplatesSchema3,
10234
10885
  rbacSchema: rbacSchema3,
10235
10886
  projectsSchema: projectsSchema3,
10236
- jobResultsSchema: jobResultsSchema3
10887
+ jobResultsSchema: jobResultsSchema3,
10888
+ promptLibrarySchema: promptLibrarySchema3,
10889
+ promptFavoritesSchema: promptFavoritesSchema3
10237
10890
  } = coreSchemas.get();
10238
10891
  var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
10239
10892
  for (const field of fields) {
@@ -10268,6 +10921,8 @@ var up = async function(knex) {
10268
10921
  statisticsSchema3(),
10269
10922
  projectsSchema3(),
10270
10923
  jobResultsSchema3(),
10924
+ promptLibrarySchema3(),
10925
+ promptFavoritesSchema3(),
10271
10926
  rbacSchema3(),
10272
10927
  agentsSchema3(),
10273
10928
  variablesSchema3(),
@@ -10370,20 +11025,39 @@ var execute = async ({ contexts }) => {
10370
11025
  await up(db3);
10371
11026
  await contextDatabases(contexts);
10372
11027
  console.log("[EXULU] Inserting default user and admin role.");
10373
- const existingRole = await db3.from("roles").where({ name: "admin" }).first();
10374
- let roleId;
10375
- if (!existingRole) {
10376
- console.log("[EXULU] Creating default admin role.");
11028
+ const existingAdminRole = await db3.from("roles").where({ name: "admin" }).first();
11029
+ const existingDefaultRole = await db3.from("roles").where({ name: "default" }).first();
11030
+ let adminRoleId;
11031
+ let defaultRoleId;
11032
+ if (!existingAdminRole) {
11033
+ console.log("[EXULU] Creating admin role.");
10377
11034
  const role = await db3.from("roles").insert({
10378
11035
  name: "admin",
10379
11036
  agents: "write",
11037
+ api: "write",
10380
11038
  workflows: "write",
10381
11039
  variables: "write",
10382
- users: "write"
11040
+ users: "write",
11041
+ evals: "write"
10383
11042
  }).returning("id");
10384
- roleId = role[0].id;
11043
+ adminRoleId = role[0].id;
10385
11044
  } else {
10386
- roleId = existingRole.id;
11045
+ adminRoleId = existingAdminRole.id;
11046
+ }
11047
+ if (!existingDefaultRole) {
11048
+ console.log("[EXULU] Creating default role.");
11049
+ const role = await db3.from("roles").insert({
11050
+ name: "default",
11051
+ agents: "write",
11052
+ api: "read",
11053
+ workflows: "read",
11054
+ variables: "read",
11055
+ users: "read",
11056
+ evals: "read"
11057
+ }).returning("id");
11058
+ defaultRoleId = role[0].id;
11059
+ } else {
11060
+ defaultRoleId = existingDefaultRole.id;
10387
11061
  }
10388
11062
  const existingUser = await db3.from("users").where({ email: "admin@exulu.com" }).first();
10389
11063
  if (!existingUser) {
@@ -10398,7 +11072,7 @@ var execute = async ({ contexts }) => {
10398
11072
  updatedAt: /* @__PURE__ */ new Date(),
10399
11073
  password,
10400
11074
  type: "user",
10401
- role: roleId
11075
+ role: adminRoleId
10402
11076
  });
10403
11077
  }
10404
11078
  const { key } = await generateApiKey("exulu", "api@exulu.com");