strapi-plugin-ai-sdk 0.6.9 → 0.7.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.
@@ -31,7 +31,7 @@ function _interopNamespace(e) {
31
31
  const fs__namespace = /* @__PURE__ */ _interopNamespace(fs$1);
32
32
  const path__namespace = /* @__PURE__ */ _interopNamespace(path$1);
33
33
  function toSnakeCase(str) {
34
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
34
+ return str.replace(/:/g, "__").replace(/-/g, "_").replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
35
35
  }
36
36
  function createMcpServer(strapi) {
37
37
  const plugin = strapi.plugin("ai-sdk");
@@ -457,7 +457,7 @@ async function sendEmail(strapi, params) {
457
457
  subject: params.subject
458
458
  };
459
459
  }
460
- const CONTENT_TYPE$5 = "plugin::ai-sdk.memory";
460
+ const CONTENT_TYPE$7 = "plugin::ai-sdk.memory";
461
461
  const saveMemorySchema = zod.z.object({
462
462
  content: zod.z.string().describe('A short, factual statement to remember about the user (e.g. "User prefers dark mode", "Company name is Acme Corp")'),
463
463
  category: zod.z.string().optional().describe('Category for the memory (e.g. "preference", "project", "personal", "general"). Defaults to "general".')
@@ -468,7 +468,7 @@ async function saveMemory(strapi, params, context) {
468
468
  return { success: false, message: "Cannot save memory: user context not available." };
469
469
  }
470
470
  try {
471
- await strapi.documents(CONTENT_TYPE$5).create({
471
+ await strapi.documents(CONTENT_TYPE$7).create({
472
472
  data: {
473
473
  content: params.content,
474
474
  category: params.category || "general",
@@ -481,7 +481,7 @@ async function saveMemory(strapi, params, context) {
481
481
  return { success: false, message: `Failed to save memory: ${detail}` };
482
482
  }
483
483
  }
484
- const CONTENT_TYPE$4 = "plugin::ai-sdk.memory";
484
+ const CONTENT_TYPE$6 = "plugin::ai-sdk.memory";
485
485
  const recallMemoriesSchema = zod.z.object({
486
486
  query: zod.z.string().optional().describe("Optional search term to filter memories by content"),
487
487
  category: zod.z.string().optional().describe('Optional category to filter by (e.g. "preference", "project", "personal")')
@@ -499,7 +499,7 @@ async function recallMemories(strapi, params, context) {
499
499
  if (params.query) {
500
500
  filters.content = { $containsi: params.query };
501
501
  }
502
- const memories = await strapi.documents(CONTENT_TYPE$4).findMany({
502
+ const memories = await strapi.documents(CONTENT_TYPE$6).findMany({
503
503
  filters,
504
504
  fields: ["content", "category"],
505
505
  sort: { createdAt: "desc" }
@@ -622,7 +622,7 @@ async function uploadMedia(strapi, params) {
622
622
  usage: `To link this file to a content type field, use writeContent with: { "fieldName": ${uploadedFile.id} }`
623
623
  };
624
624
  }
625
- const CONTENT_TYPE$3 = "plugin::ai-sdk.public-memory";
625
+ const CONTENT_TYPE$5 = "plugin::ai-sdk.public-memory";
626
626
  const recallPublicMemoriesSchema = zod.z.object({
627
627
  query: zod.z.string().optional().describe("Optional search term to filter public memories by content"),
628
628
  category: zod.z.string().optional().describe('Optional category to filter by (e.g. "faq", "product", "policy")')
@@ -633,7 +633,7 @@ async function recallPublicMemories(strapi, params) {
633
633
  const filters = {};
634
634
  if (params.category) filters.category = params.category;
635
635
  if (params.query) filters.content = { $containsi: params.query };
636
- const memories = await strapi.documents(CONTENT_TYPE$3).findMany({
636
+ const memories = await strapi.documents(CONTENT_TYPE$5).findMany({
637
637
  filters,
638
638
  fields: ["content", "category"],
639
639
  sort: { createdAt: "desc" }
@@ -867,6 +867,211 @@ async function aggregateContent(strapi, params) {
867
867
  throw new Error(`Unknown operation: ${operation}`);
868
868
  }
869
869
  }
870
+ const CONTENT_TYPE$4 = "plugin::ai-sdk.task";
871
+ const manageTaskSchema = zod.z.object({
872
+ action: zod.z.enum(["create", "update", "complete", "list", "summary"]).describe(
873
+ "Action to perform: create a new task, update an existing task, complete (mark done), list open tasks, or get a summary."
874
+ ),
875
+ documentId: zod.z.string().optional().describe("Document ID of the task to update or complete. If not known, provide title instead — the system will search for a matching task."),
876
+ title: zod.z.string().optional().describe("Task title — short, clear action. For update/complete: used to find the task if documentId is not provided."),
877
+ description: zod.z.string().optional().describe("Brief context or why this matters."),
878
+ content: zod.z.string().optional().describe("Detailed notes, steps, or links (markdown)."),
879
+ priority: zod.z.enum(["low", "medium", "high", "urgent"]).optional().describe("Priority level. Default: medium."),
880
+ consequence: zod.z.number().int().min(1).max(5).optional().describe("1-5: What happens if this is NOT done? Do NOT ask the user — omit this and a UI form will collect it."),
881
+ impact: zod.z.number().int().min(1).max(5).optional().describe("1-5: How much does completing this move the needle? Do NOT ask the user — omit this and a UI form will collect it."),
882
+ dueDate: zod.z.string().optional().describe("Due date in YYYY-MM-DD format."),
883
+ done: zod.z.boolean().optional().describe("Set done status explicitly (for update action)."),
884
+ filters: zod.z.record(zod.z.string(), zod.z.unknown()).optional().describe("Additional Strapi filters for list action.")
885
+ });
886
+ const manageTaskDescription = "Manage the user's task list. Create, update, complete, list, or summarize tasks. Tasks are scored by consequence × impact to help prioritize what matters most.";
887
+ async function resolveTask(strapi, adminUserId, documentId, title) {
888
+ if (documentId) {
889
+ const task2 = await strapi.documents(CONTENT_TYPE$4).findOne({ documentId });
890
+ if (task2 && task2.adminUserId === adminUserId) return task2;
891
+ }
892
+ if (title) {
893
+ const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
894
+ filters: {
895
+ adminUserId,
896
+ title: { $containsi: title }
897
+ }
898
+ });
899
+ if (tasks.length === 1) return tasks[0];
900
+ if (tasks.length > 1) {
901
+ const exact = tasks.find(
902
+ (t) => t.title.toLowerCase() === title.toLowerCase()
903
+ );
904
+ if (exact) return exact;
905
+ return tasks.sort(
906
+ (a, b) => b.updatedAt.localeCompare(a.updatedAt)
907
+ )[0];
908
+ }
909
+ }
910
+ return null;
911
+ }
912
+ async function manageTask(strapi, params, context) {
913
+ if (!context?.adminUserId) {
914
+ return { success: false, message: "Cannot manage tasks: user context not available." };
915
+ }
916
+ const adminUserId = context.adminUserId;
917
+ try {
918
+ switch (params.action) {
919
+ case "create": {
920
+ if (!params.title) {
921
+ return { success: false, message: "Title is required to create a task." };
922
+ }
923
+ if (params.consequence == null || params.impact == null) {
924
+ return {
925
+ success: false,
926
+ status: "pending_confirmation",
927
+ message: "A confirmation form has been shown to the user in the chat. They will set consequence and impact scores and confirm. Do NOT re-call this tool — the task will be created when the user confirms.",
928
+ proposed: {
929
+ title: params.title,
930
+ description: params.description,
931
+ content: params.content,
932
+ priority: params.priority ?? "medium",
933
+ dueDate: params.dueDate ?? null
934
+ }
935
+ };
936
+ }
937
+ const duplicate = await resolveTask(strapi, adminUserId, void 0, params.title);
938
+ if (duplicate && !duplicate.done) {
939
+ const data = {};
940
+ if (params.description !== void 0) data.description = params.description;
941
+ if (params.content !== void 0) data.content = params.content;
942
+ if (params.priority !== void 0) data.priority = params.priority;
943
+ if (params.consequence !== void 0) data.consequence = params.consequence;
944
+ if (params.impact !== void 0) data.impact = params.impact;
945
+ if (params.dueDate !== void 0) data.dueDate = params.dueDate;
946
+ const updated = await strapi.documents(CONTENT_TYPE$4).update({
947
+ documentId: duplicate.documentId,
948
+ data
949
+ });
950
+ const score2 = (updated.consequence ?? 3) * (updated.impact ?? 3);
951
+ return {
952
+ success: true,
953
+ message: `A similar task already existed ("${duplicate.title}") — updated it instead of creating a duplicate. (priority: ${updated.priority}, score: ${score2})`,
954
+ data: updated
955
+ };
956
+ }
957
+ const task2 = await strapi.documents(CONTENT_TYPE$4).create({
958
+ data: {
959
+ title: params.title,
960
+ description: params.description,
961
+ content: params.content,
962
+ done: false,
963
+ priority: params.priority ?? "medium",
964
+ consequence: params.consequence ?? 3,
965
+ impact: params.impact ?? 3,
966
+ dueDate: params.dueDate,
967
+ adminUserId
968
+ }
969
+ });
970
+ const score = (task2.consequence ?? 3) * (task2.impact ?? 3);
971
+ return {
972
+ success: true,
973
+ message: `Task created: "${task2.title}" (priority: ${task2.priority}, score: ${score})`,
974
+ data: task2
975
+ };
976
+ }
977
+ case "update": {
978
+ const existing = await resolveTask(strapi, adminUserId, params.documentId, params.title);
979
+ if (!existing) {
980
+ return { success: false, message: "Task not found. Provide a documentId or a title to search by." };
981
+ }
982
+ const data = {};
983
+ for (const key of ["title", "description", "content", "priority", "consequence", "impact", "dueDate", "done"]) {
984
+ if (params[key] !== void 0) data[key] = params[key];
985
+ }
986
+ const updated = await strapi.documents(CONTENT_TYPE$4).update({
987
+ documentId: existing.documentId,
988
+ data
989
+ });
990
+ return { success: true, message: `Task updated: "${updated.title}"`, data: updated };
991
+ }
992
+ case "complete": {
993
+ const toComplete = await resolveTask(strapi, adminUserId, params.documentId, params.title);
994
+ if (!toComplete) {
995
+ return { success: false, message: "Task not found. Provide a documentId or a title to search by." };
996
+ }
997
+ await strapi.documents(CONTENT_TYPE$4).update({
998
+ documentId: toComplete.documentId,
999
+ data: { done: true }
1000
+ });
1001
+ return { success: true, message: `Task completed: "${toComplete.title}"` };
1002
+ }
1003
+ case "list": {
1004
+ const filters = {
1005
+ adminUserId,
1006
+ done: false,
1007
+ ...params.filters
1008
+ };
1009
+ const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
1010
+ filters
1011
+ });
1012
+ const sorted = tasks.sort((a, b) => {
1013
+ const scoreA = (a.consequence ?? 3) * (a.impact ?? 3);
1014
+ const scoreB = (b.consequence ?? 3) * (b.impact ?? 3);
1015
+ if (scoreB !== scoreA) return scoreB - scoreA;
1016
+ if (a.dueDate && !b.dueDate) return -1;
1017
+ if (!a.dueDate && b.dueDate) return 1;
1018
+ if (a.dueDate && b.dueDate) return a.dueDate.localeCompare(b.dueDate);
1019
+ return 0;
1020
+ });
1021
+ return {
1022
+ success: true,
1023
+ message: `Found ${sorted.length} open task(s).`,
1024
+ data: sorted.map((t) => ({
1025
+ documentId: t.documentId,
1026
+ title: t.title,
1027
+ description: t.description,
1028
+ priority: t.priority,
1029
+ consequence: t.consequence,
1030
+ impact: t.impact,
1031
+ score: (t.consequence ?? 3) * (t.impact ?? 3),
1032
+ dueDate: t.dueDate,
1033
+ done: t.done
1034
+ }))
1035
+ };
1036
+ }
1037
+ case "summary": {
1038
+ const allOpen = await strapi.documents(CONTENT_TYPE$4).findMany({
1039
+ filters: { adminUserId, done: false }
1040
+ });
1041
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1042
+ const overdue = allOpen.filter(
1043
+ (t) => t.dueDate && t.dueDate < today
1044
+ );
1045
+ const scored = allOpen.map((t) => ({
1046
+ documentId: t.documentId,
1047
+ title: t.title,
1048
+ priority: t.priority,
1049
+ consequence: t.consequence ?? 3,
1050
+ impact: t.impact ?? 3,
1051
+ score: (t.consequence ?? 3) * (t.impact ?? 3),
1052
+ dueDate: t.dueDate
1053
+ })).sort((a, b) => b.score - a.score);
1054
+ const urgent = scored.filter((t) => t.priority === "urgent");
1055
+ return {
1056
+ success: true,
1057
+ message: "Task summary generated.",
1058
+ data: {
1059
+ totalOpen: allOpen.length,
1060
+ overdueCount: overdue.length,
1061
+ overdueTasks: overdue.map((t) => ({ documentId: t.documentId, title: t.title, dueDate: t.dueDate })),
1062
+ urgentTasks: urgent,
1063
+ top3: scored.slice(0, 3)
1064
+ }
1065
+ };
1066
+ }
1067
+ default:
1068
+ return { success: false, message: `Unknown action: ${params.action}` };
1069
+ }
1070
+ } catch (error) {
1071
+ const detail = error instanceof Error ? error.message : String(error);
1072
+ return { success: false, message: `Task operation failed: ${detail}` };
1073
+ }
1074
+ }
870
1075
  const listContentTypesTool = {
871
1076
  name: "listContentTypes",
872
1077
  description: listContentTypesDescription,
@@ -979,6 +1184,13 @@ const aggregateContentTool = {
979
1184
  execute: async (args, strapi) => aggregateContent(strapi, args),
980
1185
  publicSafe: true
981
1186
  };
1187
+ const manageTaskTool = {
1188
+ name: "manageTask",
1189
+ description: manageTaskDescription,
1190
+ schema: manageTaskSchema,
1191
+ execute: async (args, strapi, context) => manageTask(strapi, args, context),
1192
+ internal: true
1193
+ };
982
1194
  const builtInTools = [
983
1195
  listContentTypesTool,
984
1196
  searchContentTool,
@@ -989,7 +1201,8 @@ const builtInTools = [
989
1201
  saveMemoryTool,
990
1202
  recallMemoriesTool,
991
1203
  recallPublicMemoriesTool,
992
- aggregateContentTool
1204
+ aggregateContentTool,
1205
+ manageTaskTool
993
1206
  ];
994
1207
  const PLUGIN_ID$2 = "ai-sdk";
995
1208
  const bootstrap = ({ strapi }) => {
@@ -1012,6 +1225,51 @@ const bootstrap = ({ strapi }) => {
1012
1225
  toolRegistry.register(tool);
1013
1226
  }
1014
1227
  plugin.toolRegistry = toolRegistry;
1228
+ const pluginNames = Object.keys(strapi.plugins).filter((n) => n !== PLUGIN_ID$2);
1229
+ strapi.log.info(`[${PLUGIN_ID$2}] Scanning ${pluginNames.length} plugins for ai-tools: [${pluginNames.join(", ")}]`);
1230
+ for (const [pluginName, pluginInstance] of Object.entries(strapi.plugins)) {
1231
+ if (pluginName === PLUGIN_ID$2) continue;
1232
+ try {
1233
+ let aiToolsService = null;
1234
+ try {
1235
+ aiToolsService = strapi.plugin(pluginName)?.service?.("ai-tools");
1236
+ } catch {
1237
+ }
1238
+ if (!aiToolsService) {
1239
+ try {
1240
+ aiToolsService = pluginInstance.service?.("ai-tools");
1241
+ } catch {
1242
+ }
1243
+ }
1244
+ if (!aiToolsService?.getTools) {
1245
+ strapi.log.debug(`[${PLUGIN_ID$2}] No ai-tools service on plugin: ${pluginName}`);
1246
+ continue;
1247
+ }
1248
+ strapi.log.info(`[${PLUGIN_ID$2}] Found ai-tools service on plugin: ${pluginName}`);
1249
+ const contributed = aiToolsService.getTools();
1250
+ if (!Array.isArray(contributed)) continue;
1251
+ let count = 0;
1252
+ for (const tool of contributed) {
1253
+ if (!tool.name || !tool.execute || !tool.schema) {
1254
+ strapi.log.warn(`[${PLUGIN_ID$2}] Invalid tool from ${pluginName}: ${tool.name || "unnamed"}`);
1255
+ continue;
1256
+ }
1257
+ const safeName = pluginName.replace(/[^a-zA-Z0-9_-]/g, "_");
1258
+ const namespacedName = `${safeName}__${tool.name}`;
1259
+ if (toolRegistry.has(namespacedName)) {
1260
+ strapi.log.warn(`[${PLUGIN_ID$2}] Duplicate tool: ${namespacedName}`);
1261
+ continue;
1262
+ }
1263
+ toolRegistry.register({ ...tool, name: namespacedName });
1264
+ count++;
1265
+ }
1266
+ if (count > 0) {
1267
+ strapi.log.info(`[${PLUGIN_ID$2}] Registered ${count} tools from plugin: ${pluginName}`);
1268
+ }
1269
+ } catch (err) {
1270
+ strapi.log.warn(`[${PLUGIN_ID$2}] Tool discovery failed for ${pluginName}: ${err}`);
1271
+ }
1272
+ }
1015
1273
  plugin.createMcpServer = () => createMcpServer(strapi);
1016
1274
  plugin.mcpSessions = /* @__PURE__ */ new Map();
1017
1275
  strapi.log.info(`[${PLUGIN_ID$2}] MCP endpoint available at: /api/${PLUGIN_ID$2}/mcp`);
@@ -1098,17 +1356,17 @@ const config = {
1098
1356
  }
1099
1357
  }
1100
1358
  };
1101
- const kind$2 = "collectionType";
1102
- const collectionName$2 = "ai_sdk_conversations";
1103
- const info$2 = {
1359
+ const kind$3 = "collectionType";
1360
+ const collectionName$3 = "ai_sdk_conversations";
1361
+ const info$3 = {
1104
1362
  singularName: "conversation",
1105
1363
  pluralName: "conversations",
1106
1364
  displayName: "AI Conversation"
1107
1365
  };
1108
- const options$2 = {
1366
+ const options$3 = {
1109
1367
  draftAndPublish: false
1110
1368
  };
1111
- const pluginOptions$2 = {
1369
+ const pluginOptions$3 = {
1112
1370
  "content-manager": {
1113
1371
  visible: false
1114
1372
  },
@@ -1116,7 +1374,7 @@ const pluginOptions$2 = {
1116
1374
  visible: false
1117
1375
  }
1118
1376
  };
1119
- const attributes$2 = {
1377
+ const attributes$3 = {
1120
1378
  title: {
1121
1379
  type: "string",
1122
1380
  required: true,
@@ -1131,6 +1389,53 @@ const attributes$2 = {
1131
1389
  required: true
1132
1390
  }
1133
1391
  };
1392
+ const schema$3 = {
1393
+ kind: kind$3,
1394
+ collectionName: collectionName$3,
1395
+ info: info$3,
1396
+ options: options$3,
1397
+ pluginOptions: pluginOptions$3,
1398
+ attributes: attributes$3
1399
+ };
1400
+ const conversation = { schema: schema$3 };
1401
+ const kind$2 = "collectionType";
1402
+ const collectionName$2 = "ai_sdk_memories";
1403
+ const info$2 = {
1404
+ singularName: "memory",
1405
+ pluralName: "memories",
1406
+ displayName: "AI Memory"
1407
+ };
1408
+ const options$2 = {
1409
+ draftAndPublish: false
1410
+ };
1411
+ const pluginOptions$2 = {
1412
+ "content-manager": {
1413
+ visible: false
1414
+ },
1415
+ "content-type-builder": {
1416
+ visible: false
1417
+ }
1418
+ };
1419
+ const attributes$2 = {
1420
+ content: {
1421
+ type: "text",
1422
+ required: true
1423
+ },
1424
+ category: {
1425
+ type: "enumeration",
1426
+ "enum": [
1427
+ "general",
1428
+ "preference",
1429
+ "personal",
1430
+ "project"
1431
+ ],
1432
+ "default": "general"
1433
+ },
1434
+ adminUserId: {
1435
+ type: "integer",
1436
+ required: true
1437
+ }
1438
+ };
1134
1439
  const schema$2 = {
1135
1440
  kind: kind$2,
1136
1441
  collectionName: collectionName$2,
@@ -1139,13 +1444,13 @@ const schema$2 = {
1139
1444
  pluginOptions: pluginOptions$2,
1140
1445
  attributes: attributes$2
1141
1446
  };
1142
- const conversation = { schema: schema$2 };
1447
+ const memory = { schema: schema$2 };
1143
1448
  const kind$1 = "collectionType";
1144
- const collectionName$1 = "ai_sdk_memories";
1449
+ const collectionName$1 = "ai_sdk_public_memories";
1145
1450
  const info$1 = {
1146
- singularName: "memory",
1147
- pluralName: "memories",
1148
- displayName: "AI Memory"
1451
+ singularName: "public-memory",
1452
+ pluralName: "public-memories",
1453
+ displayName: "AI Public Memory"
1149
1454
  };
1150
1455
  const options$1 = {
1151
1456
  draftAndPublish: false
@@ -1167,15 +1472,11 @@ const attributes$1 = {
1167
1472
  type: "enumeration",
1168
1473
  "enum": [
1169
1474
  "general",
1170
- "preference",
1171
- "personal",
1172
- "project"
1475
+ "faq",
1476
+ "product",
1477
+ "policy"
1173
1478
  ],
1174
1479
  "default": "general"
1175
- },
1176
- adminUserId: {
1177
- type: "integer",
1178
- required: true
1179
1480
  }
1180
1481
  };
1181
1482
  const schema$1 = {
@@ -1186,39 +1487,68 @@ const schema$1 = {
1186
1487
  pluginOptions: pluginOptions$1,
1187
1488
  attributes: attributes$1
1188
1489
  };
1189
- const memory = { schema: schema$1 };
1490
+ const publicMemory = { schema: schema$1 };
1190
1491
  const kind = "collectionType";
1191
- const collectionName = "ai_sdk_public_memories";
1492
+ const collectionName = "ai_sdk_tasks";
1192
1493
  const info = {
1193
- singularName: "public-memory",
1194
- pluralName: "public-memories",
1195
- displayName: "AI Public Memory"
1494
+ singularName: "task",
1495
+ pluralName: "tasks",
1496
+ displayName: "AI Task"
1196
1497
  };
1197
1498
  const options = {
1198
1499
  draftAndPublish: false
1199
1500
  };
1200
1501
  const pluginOptions = {
1201
1502
  "content-manager": {
1202
- visible: false
1503
+ visible: true
1203
1504
  },
1204
1505
  "content-type-builder": {
1205
1506
  visible: false
1206
1507
  }
1207
1508
  };
1208
1509
  const attributes = {
1209
- content: {
1210
- type: "text",
1510
+ title: {
1511
+ type: "string",
1211
1512
  required: true
1212
1513
  },
1213
- category: {
1514
+ description: {
1515
+ type: "text"
1516
+ },
1517
+ content: {
1518
+ type: "richtext"
1519
+ },
1520
+ done: {
1521
+ type: "boolean",
1522
+ "default": false
1523
+ },
1524
+ priority: {
1214
1525
  type: "enumeration",
1215
1526
  "enum": [
1216
- "general",
1217
- "faq",
1218
- "product",
1219
- "policy"
1527
+ "low",
1528
+ "medium",
1529
+ "high",
1530
+ "urgent"
1220
1531
  ],
1221
- "default": "general"
1532
+ "default": "medium"
1533
+ },
1534
+ consequence: {
1535
+ type: "integer",
1536
+ "default": 3,
1537
+ min: 1,
1538
+ max: 5
1539
+ },
1540
+ impact: {
1541
+ type: "integer",
1542
+ "default": 3,
1543
+ min: 1,
1544
+ max: 5
1545
+ },
1546
+ dueDate: {
1547
+ type: "date"
1548
+ },
1549
+ adminUserId: {
1550
+ type: "integer",
1551
+ required: true
1222
1552
  }
1223
1553
  };
1224
1554
  const schema = {
@@ -1229,8 +1559,8 @@ const schema = {
1229
1559
  pluginOptions,
1230
1560
  attributes
1231
1561
  };
1232
- const publicMemory = { schema };
1233
- const contentTypes = { conversation, memory, "public-memory": publicMemory };
1562
+ const task = { schema };
1563
+ const contentTypes = { conversation, memory, "public-memory": publicMemory, task };
1234
1564
  function getService(strapi, ctx) {
1235
1565
  const service2 = strapi.plugin("ai-sdk").service("service");
1236
1566
  if (!service2.isInitialized()) {
@@ -1529,20 +1859,20 @@ const mcpController = ({ strapi }) => {
1529
1859
  }
1530
1860
  };
1531
1861
  };
1532
- const CONTENT_TYPE$2 = "plugin::ai-sdk.conversation";
1533
- function getAdminUserId$1(ctx) {
1862
+ const CONTENT_TYPE$3 = "plugin::ai-sdk.conversation";
1863
+ function getAdminUserId$2(ctx) {
1534
1864
  const id = ctx.state?.user?.id;
1535
1865
  return typeof id === "number" ? id : null;
1536
1866
  }
1537
1867
  const conversationController = ({ strapi }) => ({
1538
1868
  async find(ctx) {
1539
- const adminUserId = getAdminUserId$1(ctx);
1869
+ const adminUserId = getAdminUserId$2(ctx);
1540
1870
  if (!adminUserId) {
1541
1871
  ctx.status = 401;
1542
1872
  ctx.body = { error: "Unauthorized" };
1543
1873
  return;
1544
1874
  }
1545
- const conversations = await strapi.documents(CONTENT_TYPE$2).findMany({
1875
+ const conversations = await strapi.documents(CONTENT_TYPE$3).findMany({
1546
1876
  filters: { adminUserId },
1547
1877
  fields: ["title", "createdAt", "updatedAt"],
1548
1878
  sort: { updatedAt: "desc" }
@@ -1550,14 +1880,14 @@ const conversationController = ({ strapi }) => ({
1550
1880
  ctx.body = { data: conversations };
1551
1881
  },
1552
1882
  async findOne(ctx) {
1553
- const adminUserId = getAdminUserId$1(ctx);
1883
+ const adminUserId = getAdminUserId$2(ctx);
1554
1884
  if (!adminUserId) {
1555
1885
  ctx.status = 401;
1556
1886
  ctx.body = { error: "Unauthorized" };
1557
1887
  return;
1558
1888
  }
1559
1889
  const { id } = ctx.params;
1560
- const conversation2 = await strapi.documents(CONTENT_TYPE$2).findOne({
1890
+ const conversation2 = await strapi.documents(CONTENT_TYPE$3).findOne({
1561
1891
  documentId: id
1562
1892
  });
1563
1893
  if (!conversation2 || conversation2.adminUserId !== adminUserId) {
@@ -1568,14 +1898,14 @@ const conversationController = ({ strapi }) => ({
1568
1898
  ctx.body = { data: conversation2 };
1569
1899
  },
1570
1900
  async create(ctx) {
1571
- const adminUserId = getAdminUserId$1(ctx);
1901
+ const adminUserId = getAdminUserId$2(ctx);
1572
1902
  if (!adminUserId) {
1573
1903
  ctx.status = 401;
1574
1904
  ctx.body = { error: "Unauthorized" };
1575
1905
  return;
1576
1906
  }
1577
1907
  const { title, messages } = ctx.request.body;
1578
- const conversation2 = await strapi.documents(CONTENT_TYPE$2).create({
1908
+ const conversation2 = await strapi.documents(CONTENT_TYPE$3).create({
1579
1909
  data: {
1580
1910
  title: title || "New conversation",
1581
1911
  messages: messages || [],
@@ -1586,14 +1916,14 @@ const conversationController = ({ strapi }) => ({
1586
1916
  ctx.body = { data: conversation2 };
1587
1917
  },
1588
1918
  async update(ctx) {
1589
- const adminUserId = getAdminUserId$1(ctx);
1919
+ const adminUserId = getAdminUserId$2(ctx);
1590
1920
  if (!adminUserId) {
1591
1921
  ctx.status = 401;
1592
1922
  ctx.body = { error: "Unauthorized" };
1593
1923
  return;
1594
1924
  }
1595
1925
  const { id } = ctx.params;
1596
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1926
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
1597
1927
  documentId: id
1598
1928
  });
1599
1929
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1605,21 +1935,21 @@ const conversationController = ({ strapi }) => ({
1605
1935
  const data = {};
1606
1936
  if (title !== void 0) data.title = title;
1607
1937
  if (messages !== void 0) data.messages = messages;
1608
- const conversation2 = await strapi.documents(CONTENT_TYPE$2).update({
1938
+ const conversation2 = await strapi.documents(CONTENT_TYPE$3).update({
1609
1939
  documentId: id,
1610
1940
  data
1611
1941
  });
1612
1942
  ctx.body = { data: conversation2 };
1613
1943
  },
1614
1944
  async delete(ctx) {
1615
- const adminUserId = getAdminUserId$1(ctx);
1945
+ const adminUserId = getAdminUserId$2(ctx);
1616
1946
  if (!adminUserId) {
1617
1947
  ctx.status = 401;
1618
1948
  ctx.body = { error: "Unauthorized" };
1619
1949
  return;
1620
1950
  }
1621
1951
  const { id } = ctx.params;
1622
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1952
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
1623
1953
  documentId: id
1624
1954
  });
1625
1955
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1627,25 +1957,25 @@ const conversationController = ({ strapi }) => ({
1627
1957
  ctx.body = { error: "Conversation not found" };
1628
1958
  return;
1629
1959
  }
1630
- await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
1960
+ await strapi.documents(CONTENT_TYPE$3).delete({ documentId: id });
1631
1961
  ctx.status = 200;
1632
1962
  ctx.body = { data: { documentId: id } };
1633
1963
  }
1634
1964
  });
1635
- const CONTENT_TYPE$1 = "plugin::ai-sdk.memory";
1636
- function getAdminUserId(ctx) {
1965
+ const CONTENT_TYPE$2 = "plugin::ai-sdk.memory";
1966
+ function getAdminUserId$1(ctx) {
1637
1967
  const id = ctx.state?.user?.id;
1638
1968
  return typeof id === "number" ? id : null;
1639
1969
  }
1640
1970
  const memoryController = ({ strapi }) => ({
1641
1971
  async find(ctx) {
1642
- const adminUserId = getAdminUserId(ctx);
1972
+ const adminUserId = getAdminUserId$1(ctx);
1643
1973
  if (!adminUserId) {
1644
1974
  ctx.status = 401;
1645
1975
  ctx.body = { error: "Unauthorized" };
1646
1976
  return;
1647
1977
  }
1648
- const memories = await strapi.documents(CONTENT_TYPE$1).findMany({
1978
+ const memories = await strapi.documents(CONTENT_TYPE$2).findMany({
1649
1979
  filters: { adminUserId },
1650
1980
  fields: ["content", "category", "createdAt"],
1651
1981
  sort: { createdAt: "desc" }
@@ -1653,7 +1983,7 @@ const memoryController = ({ strapi }) => ({
1653
1983
  ctx.body = { data: memories };
1654
1984
  },
1655
1985
  async create(ctx) {
1656
- const adminUserId = getAdminUserId(ctx);
1986
+ const adminUserId = getAdminUserId$1(ctx);
1657
1987
  if (!adminUserId) {
1658
1988
  ctx.status = 401;
1659
1989
  ctx.body = { error: "Unauthorized" };
@@ -1665,7 +1995,7 @@ const memoryController = ({ strapi }) => ({
1665
1995
  ctx.body = { error: "content is required" };
1666
1996
  return;
1667
1997
  }
1668
- const memory2 = await strapi.documents(CONTENT_TYPE$1).create({
1998
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).create({
1669
1999
  data: {
1670
2000
  content,
1671
2001
  category: category || "general",
@@ -1676,14 +2006,14 @@ const memoryController = ({ strapi }) => ({
1676
2006
  ctx.body = { data: memory2 };
1677
2007
  },
1678
2008
  async update(ctx) {
1679
- const adminUserId = getAdminUserId(ctx);
2009
+ const adminUserId = getAdminUserId$1(ctx);
1680
2010
  if (!adminUserId) {
1681
2011
  ctx.status = 401;
1682
2012
  ctx.body = { error: "Unauthorized" };
1683
2013
  return;
1684
2014
  }
1685
2015
  const { id } = ctx.params;
1686
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
2016
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1687
2017
  documentId: id
1688
2018
  });
1689
2019
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1695,21 +2025,21 @@ const memoryController = ({ strapi }) => ({
1695
2025
  const data = {};
1696
2026
  if (content !== void 0) data.content = content;
1697
2027
  if (category !== void 0) data.category = category;
1698
- const memory2 = await strapi.documents(CONTENT_TYPE$1).update({
2028
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).update({
1699
2029
  documentId: id,
1700
2030
  data
1701
2031
  });
1702
2032
  ctx.body = { data: memory2 };
1703
2033
  },
1704
2034
  async delete(ctx) {
1705
- const adminUserId = getAdminUserId(ctx);
2035
+ const adminUserId = getAdminUserId$1(ctx);
1706
2036
  if (!adminUserId) {
1707
2037
  ctx.status = 401;
1708
2038
  ctx.body = { error: "Unauthorized" };
1709
2039
  return;
1710
2040
  }
1711
2041
  const { id } = ctx.params;
1712
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
2042
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1713
2043
  documentId: id
1714
2044
  });
1715
2045
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1717,15 +2047,15 @@ const memoryController = ({ strapi }) => ({
1717
2047
  ctx.body = { error: "Memory not found" };
1718
2048
  return;
1719
2049
  }
1720
- await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2050
+ await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
1721
2051
  ctx.status = 200;
1722
2052
  ctx.body = { data: { documentId: id } };
1723
2053
  }
1724
2054
  });
1725
- const CONTENT_TYPE = "plugin::ai-sdk.public-memory";
2055
+ const CONTENT_TYPE$1 = "plugin::ai-sdk.public-memory";
1726
2056
  const publicMemoryController = ({ strapi }) => ({
1727
2057
  async find(ctx) {
1728
- const memories = await strapi.documents(CONTENT_TYPE).findMany({
2058
+ const memories = await strapi.documents(CONTENT_TYPE$1).findMany({
1729
2059
  fields: ["content", "category", "createdAt"],
1730
2060
  sort: { createdAt: "desc" }
1731
2061
  });
@@ -1738,7 +2068,7 @@ const publicMemoryController = ({ strapi }) => ({
1738
2068
  ctx.body = { error: "content is required" };
1739
2069
  return;
1740
2070
  }
1741
- const memory2 = await strapi.documents(CONTENT_TYPE).create({
2071
+ const memory2 = await strapi.documents(CONTENT_TYPE$1).create({
1742
2072
  data: { content, category: category || "general" }
1743
2073
  });
1744
2074
  ctx.status = 201;
@@ -1746,7 +2076,7 @@ const publicMemoryController = ({ strapi }) => ({
1746
2076
  },
1747
2077
  async update(ctx) {
1748
2078
  const { id } = ctx.params;
1749
- const existing = await strapi.documents(CONTENT_TYPE).findOne({ documentId: id });
2079
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
1750
2080
  if (!existing) {
1751
2081
  ctx.status = 404;
1752
2082
  ctx.body = { error: "Public memory not found" };
@@ -1756,7 +2086,7 @@ const publicMemoryController = ({ strapi }) => ({
1756
2086
  const data = {};
1757
2087
  if (content !== void 0) data.content = content;
1758
2088
  if (category !== void 0) data.category = category;
1759
- const memory2 = await strapi.documents(CONTENT_TYPE).update({
2089
+ const memory2 = await strapi.documents(CONTENT_TYPE$1).update({
1760
2090
  documentId: id,
1761
2091
  data
1762
2092
  });
@@ -1764,12 +2094,103 @@ const publicMemoryController = ({ strapi }) => ({
1764
2094
  },
1765
2095
  async delete(ctx) {
1766
2096
  const { id } = ctx.params;
1767
- const existing = await strapi.documents(CONTENT_TYPE).findOne({ documentId: id });
2097
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
1768
2098
  if (!existing) {
1769
2099
  ctx.status = 404;
1770
2100
  ctx.body = { error: "Public memory not found" };
1771
2101
  return;
1772
2102
  }
2103
+ await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2104
+ ctx.status = 200;
2105
+ ctx.body = { data: { documentId: id } };
2106
+ }
2107
+ });
2108
+ const CONTENT_TYPE = "plugin::ai-sdk.task";
2109
+ function getAdminUserId(ctx) {
2110
+ const id = ctx.state?.user?.id;
2111
+ return typeof id === "number" ? id : null;
2112
+ }
2113
+ const taskController = ({ strapi }) => ({
2114
+ async find(ctx) {
2115
+ const adminUserId = getAdminUserId(ctx);
2116
+ if (!adminUserId) {
2117
+ ctx.status = 401;
2118
+ ctx.body = { error: "Unauthorized" };
2119
+ return;
2120
+ }
2121
+ const tasks = await strapi.documents(CONTENT_TYPE).findMany({
2122
+ filters: { adminUserId },
2123
+ sort: { createdAt: "desc" }
2124
+ });
2125
+ ctx.body = { data: tasks };
2126
+ },
2127
+ async create(ctx) {
2128
+ const adminUserId = getAdminUserId(ctx);
2129
+ if (!adminUserId) {
2130
+ ctx.status = 401;
2131
+ ctx.body = { error: "Unauthorized" };
2132
+ return;
2133
+ }
2134
+ const body = ctx.request.body;
2135
+ const task2 = await strapi.documents(CONTENT_TYPE).create({
2136
+ data: {
2137
+ title: body.title,
2138
+ description: body.description,
2139
+ content: body.content,
2140
+ done: body.done ?? false,
2141
+ priority: body.priority ?? "medium",
2142
+ consequence: body.consequence ?? 3,
2143
+ impact: body.impact ?? 3,
2144
+ dueDate: body.dueDate,
2145
+ adminUserId
2146
+ }
2147
+ });
2148
+ ctx.status = 201;
2149
+ ctx.body = { data: task2 };
2150
+ },
2151
+ async update(ctx) {
2152
+ const adminUserId = getAdminUserId(ctx);
2153
+ if (!adminUserId) {
2154
+ ctx.status = 401;
2155
+ ctx.body = { error: "Unauthorized" };
2156
+ return;
2157
+ }
2158
+ const { id } = ctx.params;
2159
+ const existing = await strapi.documents(CONTENT_TYPE).findOne({
2160
+ documentId: id
2161
+ });
2162
+ if (!existing || existing.adminUserId !== adminUserId) {
2163
+ ctx.status = 404;
2164
+ ctx.body = { error: "Task not found" };
2165
+ return;
2166
+ }
2167
+ const body = ctx.request.body;
2168
+ const data = {};
2169
+ for (const key of ["title", "description", "content", "done", "priority", "consequence", "impact", "dueDate"]) {
2170
+ if (body[key] !== void 0) data[key] = body[key];
2171
+ }
2172
+ const task2 = await strapi.documents(CONTENT_TYPE).update({
2173
+ documentId: id,
2174
+ data
2175
+ });
2176
+ ctx.body = { data: task2 };
2177
+ },
2178
+ async delete(ctx) {
2179
+ const adminUserId = getAdminUserId(ctx);
2180
+ if (!adminUserId) {
2181
+ ctx.status = 401;
2182
+ ctx.body = { error: "Unauthorized" };
2183
+ return;
2184
+ }
2185
+ const { id } = ctx.params;
2186
+ const existing = await strapi.documents(CONTENT_TYPE).findOne({
2187
+ documentId: id
2188
+ });
2189
+ if (!existing || existing.adminUserId !== adminUserId) {
2190
+ ctx.status = 404;
2191
+ ctx.body = { error: "Task not found" };
2192
+ return;
2193
+ }
1773
2194
  await strapi.documents(CONTENT_TYPE).delete({ documentId: id });
1774
2195
  ctx.status = 200;
1775
2196
  ctx.body = { data: { documentId: id } };
@@ -1780,7 +2201,8 @@ const controllers = {
1780
2201
  mcp: mcpController,
1781
2202
  conversation: conversationController,
1782
2203
  memory: memoryController,
1783
- "public-memory": publicMemoryController
2204
+ "public-memory": publicMemoryController,
2205
+ task: taskController
1784
2206
  };
1785
2207
  const promptInjection = [
1786
2208
  "ignore (all |any )?(previous|prior|above) (instructions|prompts|rules)",
@@ -2120,6 +2542,30 @@ const adminAPIRoutes = {
2120
2542
  path: "/public-memories/:id",
2121
2543
  handler: "public-memory.delete",
2122
2544
  config: { policies: [] }
2545
+ },
2546
+ {
2547
+ method: "GET",
2548
+ path: "/tasks",
2549
+ handler: "task.find",
2550
+ config: { policies: [] }
2551
+ },
2552
+ {
2553
+ method: "POST",
2554
+ path: "/tasks",
2555
+ handler: "task.create",
2556
+ config: { policies: [] }
2557
+ },
2558
+ {
2559
+ method: "PUT",
2560
+ path: "/tasks/:id",
2561
+ handler: "task.update",
2562
+ config: { policies: [] }
2563
+ },
2564
+ {
2565
+ method: "DELETE",
2566
+ path: "/tasks/:id",
2567
+ handler: "task.delete",
2568
+ config: { policies: [] }
2123
2569
  }
2124
2570
  ]
2125
2571
  };
@@ -2197,12 +2643,16 @@ const DEFAULT_PREAMBLE = `You are a Strapi CMS assistant. Use your tools to fulf
2197
2643
 
2198
2644
  For analytics and counting questions (e.g. "how many", "count", "distribution", "trends", "breakdown"), use the aggregateContent tool instead of searchContent — it is faster and purpose-built for these queries. Present analytics results as markdown tables. After showing analytics results, suggest 2-3 follow-up questions the user might find useful under a "**You might also want to know:**" heading.
2199
2645
 
2646
+ Plugin tools: When specialized tools from plugins are available (listed below), ALWAYS prefer them over the generic searchContent tool. For example, use searchMentions for social mentions (it has BM25 relevance scoring), semanticSearch for vector similarity search across embeddings, and ragQuery for question-answering grounded in embedded content. Only fall back to searchContent when no specialized tool covers the query.
2647
+
2200
2648
  Strapi filter syntax for searchContent and aggregateContent:
2201
2649
  - Scalar fields: { title: { $containsi: "hello" } }
2202
2650
  - Relation (manyToOne): { author: { name: { $eq: "John" } } }
2203
2651
  - Relation (manyToMany): { contentTags: { title: { $eq: "tutorial" } } }
2204
2652
  - Always nest relation filters as: { relationField: { fieldOnRelatedType: { $operator: value } } }
2205
- - Never use flat dot-path syntax like "contentTags.title" in filters — always use nested objects.`;
2653
+ - Never use flat dot-path syntax like "contentTags.title" in filters — always use nested objects.
2654
+
2655
+ Task management: You have a manageTask tool for tracking the user's tasks. At the START of every conversation, proactively use manageTask with action "summary" to check for open tasks, then greet the user with a brief status and suggest what to tackle next based on consequence × impact score. When the user mentions action items, commitments, or things they need to do during conversation, proactively create tasks by calling manageTask with action "create" — do NOT include consequence or impact scores, just omit them. A UI form will appear in the chat for the user to set scores and confirm. Never ask for scores in text. When presenting task lists, use markdown tables.`;
2206
2656
  const DEFAULT_PUBLIC_PREAMBLE = `You are a helpful public assistant for this website. Use your tools to answer questions about the site content. You cannot modify any content or perform administrative actions.
2207
2657
 
2208
2658
  For analytics and counting questions (e.g. "how many", "count", "distribution", "trends", "breakdown"), use the aggregateContent tool instead of searchContent — it is faster and purpose-built for these queries. Present analytics results as markdown tables. After showing analytics results, suggest 2-3 follow-up questions the user might find useful under a "**You might also want to know:**" heading.