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.
@@ -11,7 +11,7 @@ import * as path from "node:path";
11
11
  import { randomUUID } from "node:crypto";
12
12
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
13
  function toSnakeCase(str) {
14
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
14
+ return str.replace(/:/g, "__").replace(/-/g, "_").replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
15
15
  }
16
16
  function createMcpServer(strapi) {
17
17
  const plugin = strapi.plugin("ai-sdk");
@@ -437,7 +437,7 @@ async function sendEmail(strapi, params) {
437
437
  subject: params.subject
438
438
  };
439
439
  }
440
- const CONTENT_TYPE$5 = "plugin::ai-sdk.memory";
440
+ const CONTENT_TYPE$7 = "plugin::ai-sdk.memory";
441
441
  const saveMemorySchema = z.object({
442
442
  content: z.string().describe('A short, factual statement to remember about the user (e.g. "User prefers dark mode", "Company name is Acme Corp")'),
443
443
  category: z.string().optional().describe('Category for the memory (e.g. "preference", "project", "personal", "general"). Defaults to "general".')
@@ -448,7 +448,7 @@ async function saveMemory(strapi, params, context) {
448
448
  return { success: false, message: "Cannot save memory: user context not available." };
449
449
  }
450
450
  try {
451
- await strapi.documents(CONTENT_TYPE$5).create({
451
+ await strapi.documents(CONTENT_TYPE$7).create({
452
452
  data: {
453
453
  content: params.content,
454
454
  category: params.category || "general",
@@ -461,7 +461,7 @@ async function saveMemory(strapi, params, context) {
461
461
  return { success: false, message: `Failed to save memory: ${detail}` };
462
462
  }
463
463
  }
464
- const CONTENT_TYPE$4 = "plugin::ai-sdk.memory";
464
+ const CONTENT_TYPE$6 = "plugin::ai-sdk.memory";
465
465
  const recallMemoriesSchema = z.object({
466
466
  query: z.string().optional().describe("Optional search term to filter memories by content"),
467
467
  category: z.string().optional().describe('Optional category to filter by (e.g. "preference", "project", "personal")')
@@ -479,7 +479,7 @@ async function recallMemories(strapi, params, context) {
479
479
  if (params.query) {
480
480
  filters.content = { $containsi: params.query };
481
481
  }
482
- const memories = await strapi.documents(CONTENT_TYPE$4).findMany({
482
+ const memories = await strapi.documents(CONTENT_TYPE$6).findMany({
483
483
  filters,
484
484
  fields: ["content", "category"],
485
485
  sort: { createdAt: "desc" }
@@ -602,7 +602,7 @@ async function uploadMedia(strapi, params) {
602
602
  usage: `To link this file to a content type field, use writeContent with: { "fieldName": ${uploadedFile.id} }`
603
603
  };
604
604
  }
605
- const CONTENT_TYPE$3 = "plugin::ai-sdk.public-memory";
605
+ const CONTENT_TYPE$5 = "plugin::ai-sdk.public-memory";
606
606
  const recallPublicMemoriesSchema = z.object({
607
607
  query: z.string().optional().describe("Optional search term to filter public memories by content"),
608
608
  category: z.string().optional().describe('Optional category to filter by (e.g. "faq", "product", "policy")')
@@ -613,7 +613,7 @@ async function recallPublicMemories(strapi, params) {
613
613
  const filters = {};
614
614
  if (params.category) filters.category = params.category;
615
615
  if (params.query) filters.content = { $containsi: params.query };
616
- const memories = await strapi.documents(CONTENT_TYPE$3).findMany({
616
+ const memories = await strapi.documents(CONTENT_TYPE$5).findMany({
617
617
  filters,
618
618
  fields: ["content", "category"],
619
619
  sort: { createdAt: "desc" }
@@ -847,6 +847,211 @@ async function aggregateContent(strapi, params) {
847
847
  throw new Error(`Unknown operation: ${operation}`);
848
848
  }
849
849
  }
850
+ const CONTENT_TYPE$4 = "plugin::ai-sdk.task";
851
+ const manageTaskSchema = z.object({
852
+ action: z.enum(["create", "update", "complete", "list", "summary"]).describe(
853
+ "Action to perform: create a new task, update an existing task, complete (mark done), list open tasks, or get a summary."
854
+ ),
855
+ documentId: 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."),
856
+ title: z.string().optional().describe("Task title — short, clear action. For update/complete: used to find the task if documentId is not provided."),
857
+ description: z.string().optional().describe("Brief context or why this matters."),
858
+ content: z.string().optional().describe("Detailed notes, steps, or links (markdown)."),
859
+ priority: z.enum(["low", "medium", "high", "urgent"]).optional().describe("Priority level. Default: medium."),
860
+ consequence: 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."),
861
+ impact: 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."),
862
+ dueDate: z.string().optional().describe("Due date in YYYY-MM-DD format."),
863
+ done: z.boolean().optional().describe("Set done status explicitly (for update action)."),
864
+ filters: z.record(z.string(), z.unknown()).optional().describe("Additional Strapi filters for list action.")
865
+ });
866
+ 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.";
867
+ async function resolveTask(strapi, adminUserId, documentId, title) {
868
+ if (documentId) {
869
+ const task2 = await strapi.documents(CONTENT_TYPE$4).findOne({ documentId });
870
+ if (task2 && task2.adminUserId === adminUserId) return task2;
871
+ }
872
+ if (title) {
873
+ const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
874
+ filters: {
875
+ adminUserId,
876
+ title: { $containsi: title }
877
+ }
878
+ });
879
+ if (tasks.length === 1) return tasks[0];
880
+ if (tasks.length > 1) {
881
+ const exact = tasks.find(
882
+ (t) => t.title.toLowerCase() === title.toLowerCase()
883
+ );
884
+ if (exact) return exact;
885
+ return tasks.sort(
886
+ (a, b) => b.updatedAt.localeCompare(a.updatedAt)
887
+ )[0];
888
+ }
889
+ }
890
+ return null;
891
+ }
892
+ async function manageTask(strapi, params, context) {
893
+ if (!context?.adminUserId) {
894
+ return { success: false, message: "Cannot manage tasks: user context not available." };
895
+ }
896
+ const adminUserId = context.adminUserId;
897
+ try {
898
+ switch (params.action) {
899
+ case "create": {
900
+ if (!params.title) {
901
+ return { success: false, message: "Title is required to create a task." };
902
+ }
903
+ if (params.consequence == null || params.impact == null) {
904
+ return {
905
+ success: false,
906
+ status: "pending_confirmation",
907
+ 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.",
908
+ proposed: {
909
+ title: params.title,
910
+ description: params.description,
911
+ content: params.content,
912
+ priority: params.priority ?? "medium",
913
+ dueDate: params.dueDate ?? null
914
+ }
915
+ };
916
+ }
917
+ const duplicate = await resolveTask(strapi, adminUserId, void 0, params.title);
918
+ if (duplicate && !duplicate.done) {
919
+ const data = {};
920
+ if (params.description !== void 0) data.description = params.description;
921
+ if (params.content !== void 0) data.content = params.content;
922
+ if (params.priority !== void 0) data.priority = params.priority;
923
+ if (params.consequence !== void 0) data.consequence = params.consequence;
924
+ if (params.impact !== void 0) data.impact = params.impact;
925
+ if (params.dueDate !== void 0) data.dueDate = params.dueDate;
926
+ const updated = await strapi.documents(CONTENT_TYPE$4).update({
927
+ documentId: duplicate.documentId,
928
+ data
929
+ });
930
+ const score2 = (updated.consequence ?? 3) * (updated.impact ?? 3);
931
+ return {
932
+ success: true,
933
+ message: `A similar task already existed ("${duplicate.title}") — updated it instead of creating a duplicate. (priority: ${updated.priority}, score: ${score2})`,
934
+ data: updated
935
+ };
936
+ }
937
+ const task2 = await strapi.documents(CONTENT_TYPE$4).create({
938
+ data: {
939
+ title: params.title,
940
+ description: params.description,
941
+ content: params.content,
942
+ done: false,
943
+ priority: params.priority ?? "medium",
944
+ consequence: params.consequence ?? 3,
945
+ impact: params.impact ?? 3,
946
+ dueDate: params.dueDate,
947
+ adminUserId
948
+ }
949
+ });
950
+ const score = (task2.consequence ?? 3) * (task2.impact ?? 3);
951
+ return {
952
+ success: true,
953
+ message: `Task created: "${task2.title}" (priority: ${task2.priority}, score: ${score})`,
954
+ data: task2
955
+ };
956
+ }
957
+ case "update": {
958
+ const existing = await resolveTask(strapi, adminUserId, params.documentId, params.title);
959
+ if (!existing) {
960
+ return { success: false, message: "Task not found. Provide a documentId or a title to search by." };
961
+ }
962
+ const data = {};
963
+ for (const key of ["title", "description", "content", "priority", "consequence", "impact", "dueDate", "done"]) {
964
+ if (params[key] !== void 0) data[key] = params[key];
965
+ }
966
+ const updated = await strapi.documents(CONTENT_TYPE$4).update({
967
+ documentId: existing.documentId,
968
+ data
969
+ });
970
+ return { success: true, message: `Task updated: "${updated.title}"`, data: updated };
971
+ }
972
+ case "complete": {
973
+ const toComplete = await resolveTask(strapi, adminUserId, params.documentId, params.title);
974
+ if (!toComplete) {
975
+ return { success: false, message: "Task not found. Provide a documentId or a title to search by." };
976
+ }
977
+ await strapi.documents(CONTENT_TYPE$4).update({
978
+ documentId: toComplete.documentId,
979
+ data: { done: true }
980
+ });
981
+ return { success: true, message: `Task completed: "${toComplete.title}"` };
982
+ }
983
+ case "list": {
984
+ const filters = {
985
+ adminUserId,
986
+ done: false,
987
+ ...params.filters
988
+ };
989
+ const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
990
+ filters
991
+ });
992
+ const sorted = tasks.sort((a, b) => {
993
+ const scoreA = (a.consequence ?? 3) * (a.impact ?? 3);
994
+ const scoreB = (b.consequence ?? 3) * (b.impact ?? 3);
995
+ if (scoreB !== scoreA) return scoreB - scoreA;
996
+ if (a.dueDate && !b.dueDate) return -1;
997
+ if (!a.dueDate && b.dueDate) return 1;
998
+ if (a.dueDate && b.dueDate) return a.dueDate.localeCompare(b.dueDate);
999
+ return 0;
1000
+ });
1001
+ return {
1002
+ success: true,
1003
+ message: `Found ${sorted.length} open task(s).`,
1004
+ data: sorted.map((t) => ({
1005
+ documentId: t.documentId,
1006
+ title: t.title,
1007
+ description: t.description,
1008
+ priority: t.priority,
1009
+ consequence: t.consequence,
1010
+ impact: t.impact,
1011
+ score: (t.consequence ?? 3) * (t.impact ?? 3),
1012
+ dueDate: t.dueDate,
1013
+ done: t.done
1014
+ }))
1015
+ };
1016
+ }
1017
+ case "summary": {
1018
+ const allOpen = await strapi.documents(CONTENT_TYPE$4).findMany({
1019
+ filters: { adminUserId, done: false }
1020
+ });
1021
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1022
+ const overdue = allOpen.filter(
1023
+ (t) => t.dueDate && t.dueDate < today
1024
+ );
1025
+ const scored = allOpen.map((t) => ({
1026
+ documentId: t.documentId,
1027
+ title: t.title,
1028
+ priority: t.priority,
1029
+ consequence: t.consequence ?? 3,
1030
+ impact: t.impact ?? 3,
1031
+ score: (t.consequence ?? 3) * (t.impact ?? 3),
1032
+ dueDate: t.dueDate
1033
+ })).sort((a, b) => b.score - a.score);
1034
+ const urgent = scored.filter((t) => t.priority === "urgent");
1035
+ return {
1036
+ success: true,
1037
+ message: "Task summary generated.",
1038
+ data: {
1039
+ totalOpen: allOpen.length,
1040
+ overdueCount: overdue.length,
1041
+ overdueTasks: overdue.map((t) => ({ documentId: t.documentId, title: t.title, dueDate: t.dueDate })),
1042
+ urgentTasks: urgent,
1043
+ top3: scored.slice(0, 3)
1044
+ }
1045
+ };
1046
+ }
1047
+ default:
1048
+ return { success: false, message: `Unknown action: ${params.action}` };
1049
+ }
1050
+ } catch (error) {
1051
+ const detail = error instanceof Error ? error.message : String(error);
1052
+ return { success: false, message: `Task operation failed: ${detail}` };
1053
+ }
1054
+ }
850
1055
  const listContentTypesTool = {
851
1056
  name: "listContentTypes",
852
1057
  description: listContentTypesDescription,
@@ -959,6 +1164,13 @@ const aggregateContentTool = {
959
1164
  execute: async (args, strapi) => aggregateContent(strapi, args),
960
1165
  publicSafe: true
961
1166
  };
1167
+ const manageTaskTool = {
1168
+ name: "manageTask",
1169
+ description: manageTaskDescription,
1170
+ schema: manageTaskSchema,
1171
+ execute: async (args, strapi, context) => manageTask(strapi, args, context),
1172
+ internal: true
1173
+ };
962
1174
  const builtInTools = [
963
1175
  listContentTypesTool,
964
1176
  searchContentTool,
@@ -969,7 +1181,8 @@ const builtInTools = [
969
1181
  saveMemoryTool,
970
1182
  recallMemoriesTool,
971
1183
  recallPublicMemoriesTool,
972
- aggregateContentTool
1184
+ aggregateContentTool,
1185
+ manageTaskTool
973
1186
  ];
974
1187
  const PLUGIN_ID$2 = "ai-sdk";
975
1188
  const bootstrap = ({ strapi }) => {
@@ -992,6 +1205,51 @@ const bootstrap = ({ strapi }) => {
992
1205
  toolRegistry.register(tool2);
993
1206
  }
994
1207
  plugin.toolRegistry = toolRegistry;
1208
+ const pluginNames = Object.keys(strapi.plugins).filter((n) => n !== PLUGIN_ID$2);
1209
+ strapi.log.info(`[${PLUGIN_ID$2}] Scanning ${pluginNames.length} plugins for ai-tools: [${pluginNames.join(", ")}]`);
1210
+ for (const [pluginName, pluginInstance] of Object.entries(strapi.plugins)) {
1211
+ if (pluginName === PLUGIN_ID$2) continue;
1212
+ try {
1213
+ let aiToolsService = null;
1214
+ try {
1215
+ aiToolsService = strapi.plugin(pluginName)?.service?.("ai-tools");
1216
+ } catch {
1217
+ }
1218
+ if (!aiToolsService) {
1219
+ try {
1220
+ aiToolsService = pluginInstance.service?.("ai-tools");
1221
+ } catch {
1222
+ }
1223
+ }
1224
+ if (!aiToolsService?.getTools) {
1225
+ strapi.log.debug(`[${PLUGIN_ID$2}] No ai-tools service on plugin: ${pluginName}`);
1226
+ continue;
1227
+ }
1228
+ strapi.log.info(`[${PLUGIN_ID$2}] Found ai-tools service on plugin: ${pluginName}`);
1229
+ const contributed = aiToolsService.getTools();
1230
+ if (!Array.isArray(contributed)) continue;
1231
+ let count = 0;
1232
+ for (const tool2 of contributed) {
1233
+ if (!tool2.name || !tool2.execute || !tool2.schema) {
1234
+ strapi.log.warn(`[${PLUGIN_ID$2}] Invalid tool from ${pluginName}: ${tool2.name || "unnamed"}`);
1235
+ continue;
1236
+ }
1237
+ const safeName = pluginName.replace(/[^a-zA-Z0-9_-]/g, "_");
1238
+ const namespacedName = `${safeName}__${tool2.name}`;
1239
+ if (toolRegistry.has(namespacedName)) {
1240
+ strapi.log.warn(`[${PLUGIN_ID$2}] Duplicate tool: ${namespacedName}`);
1241
+ continue;
1242
+ }
1243
+ toolRegistry.register({ ...tool2, name: namespacedName });
1244
+ count++;
1245
+ }
1246
+ if (count > 0) {
1247
+ strapi.log.info(`[${PLUGIN_ID$2}] Registered ${count} tools from plugin: ${pluginName}`);
1248
+ }
1249
+ } catch (err) {
1250
+ strapi.log.warn(`[${PLUGIN_ID$2}] Tool discovery failed for ${pluginName}: ${err}`);
1251
+ }
1252
+ }
995
1253
  plugin.createMcpServer = () => createMcpServer(strapi);
996
1254
  plugin.mcpSessions = /* @__PURE__ */ new Map();
997
1255
  strapi.log.info(`[${PLUGIN_ID$2}] MCP endpoint available at: /api/${PLUGIN_ID$2}/mcp`);
@@ -1078,17 +1336,17 @@ const config = {
1078
1336
  }
1079
1337
  }
1080
1338
  };
1081
- const kind$2 = "collectionType";
1082
- const collectionName$2 = "ai_sdk_conversations";
1083
- const info$2 = {
1339
+ const kind$3 = "collectionType";
1340
+ const collectionName$3 = "ai_sdk_conversations";
1341
+ const info$3 = {
1084
1342
  singularName: "conversation",
1085
1343
  pluralName: "conversations",
1086
1344
  displayName: "AI Conversation"
1087
1345
  };
1088
- const options$2 = {
1346
+ const options$3 = {
1089
1347
  draftAndPublish: false
1090
1348
  };
1091
- const pluginOptions$2 = {
1349
+ const pluginOptions$3 = {
1092
1350
  "content-manager": {
1093
1351
  visible: false
1094
1352
  },
@@ -1096,7 +1354,7 @@ const pluginOptions$2 = {
1096
1354
  visible: false
1097
1355
  }
1098
1356
  };
1099
- const attributes$2 = {
1357
+ const attributes$3 = {
1100
1358
  title: {
1101
1359
  type: "string",
1102
1360
  required: true,
@@ -1111,6 +1369,53 @@ const attributes$2 = {
1111
1369
  required: true
1112
1370
  }
1113
1371
  };
1372
+ const schema$3 = {
1373
+ kind: kind$3,
1374
+ collectionName: collectionName$3,
1375
+ info: info$3,
1376
+ options: options$3,
1377
+ pluginOptions: pluginOptions$3,
1378
+ attributes: attributes$3
1379
+ };
1380
+ const conversation = { schema: schema$3 };
1381
+ const kind$2 = "collectionType";
1382
+ const collectionName$2 = "ai_sdk_memories";
1383
+ const info$2 = {
1384
+ singularName: "memory",
1385
+ pluralName: "memories",
1386
+ displayName: "AI Memory"
1387
+ };
1388
+ const options$2 = {
1389
+ draftAndPublish: false
1390
+ };
1391
+ const pluginOptions$2 = {
1392
+ "content-manager": {
1393
+ visible: false
1394
+ },
1395
+ "content-type-builder": {
1396
+ visible: false
1397
+ }
1398
+ };
1399
+ const attributes$2 = {
1400
+ content: {
1401
+ type: "text",
1402
+ required: true
1403
+ },
1404
+ category: {
1405
+ type: "enumeration",
1406
+ "enum": [
1407
+ "general",
1408
+ "preference",
1409
+ "personal",
1410
+ "project"
1411
+ ],
1412
+ "default": "general"
1413
+ },
1414
+ adminUserId: {
1415
+ type: "integer",
1416
+ required: true
1417
+ }
1418
+ };
1114
1419
  const schema$2 = {
1115
1420
  kind: kind$2,
1116
1421
  collectionName: collectionName$2,
@@ -1119,13 +1424,13 @@ const schema$2 = {
1119
1424
  pluginOptions: pluginOptions$2,
1120
1425
  attributes: attributes$2
1121
1426
  };
1122
- const conversation = { schema: schema$2 };
1427
+ const memory = { schema: schema$2 };
1123
1428
  const kind$1 = "collectionType";
1124
- const collectionName$1 = "ai_sdk_memories";
1429
+ const collectionName$1 = "ai_sdk_public_memories";
1125
1430
  const info$1 = {
1126
- singularName: "memory",
1127
- pluralName: "memories",
1128
- displayName: "AI Memory"
1431
+ singularName: "public-memory",
1432
+ pluralName: "public-memories",
1433
+ displayName: "AI Public Memory"
1129
1434
  };
1130
1435
  const options$1 = {
1131
1436
  draftAndPublish: false
@@ -1147,15 +1452,11 @@ const attributes$1 = {
1147
1452
  type: "enumeration",
1148
1453
  "enum": [
1149
1454
  "general",
1150
- "preference",
1151
- "personal",
1152
- "project"
1455
+ "faq",
1456
+ "product",
1457
+ "policy"
1153
1458
  ],
1154
1459
  "default": "general"
1155
- },
1156
- adminUserId: {
1157
- type: "integer",
1158
- required: true
1159
1460
  }
1160
1461
  };
1161
1462
  const schema$1 = {
@@ -1166,39 +1467,68 @@ const schema$1 = {
1166
1467
  pluginOptions: pluginOptions$1,
1167
1468
  attributes: attributes$1
1168
1469
  };
1169
- const memory = { schema: schema$1 };
1470
+ const publicMemory = { schema: schema$1 };
1170
1471
  const kind = "collectionType";
1171
- const collectionName = "ai_sdk_public_memories";
1472
+ const collectionName = "ai_sdk_tasks";
1172
1473
  const info = {
1173
- singularName: "public-memory",
1174
- pluralName: "public-memories",
1175
- displayName: "AI Public Memory"
1474
+ singularName: "task",
1475
+ pluralName: "tasks",
1476
+ displayName: "AI Task"
1176
1477
  };
1177
1478
  const options = {
1178
1479
  draftAndPublish: false
1179
1480
  };
1180
1481
  const pluginOptions = {
1181
1482
  "content-manager": {
1182
- visible: false
1483
+ visible: true
1183
1484
  },
1184
1485
  "content-type-builder": {
1185
1486
  visible: false
1186
1487
  }
1187
1488
  };
1188
1489
  const attributes = {
1189
- content: {
1190
- type: "text",
1490
+ title: {
1491
+ type: "string",
1191
1492
  required: true
1192
1493
  },
1193
- category: {
1494
+ description: {
1495
+ type: "text"
1496
+ },
1497
+ content: {
1498
+ type: "richtext"
1499
+ },
1500
+ done: {
1501
+ type: "boolean",
1502
+ "default": false
1503
+ },
1504
+ priority: {
1194
1505
  type: "enumeration",
1195
1506
  "enum": [
1196
- "general",
1197
- "faq",
1198
- "product",
1199
- "policy"
1507
+ "low",
1508
+ "medium",
1509
+ "high",
1510
+ "urgent"
1200
1511
  ],
1201
- "default": "general"
1512
+ "default": "medium"
1513
+ },
1514
+ consequence: {
1515
+ type: "integer",
1516
+ "default": 3,
1517
+ min: 1,
1518
+ max: 5
1519
+ },
1520
+ impact: {
1521
+ type: "integer",
1522
+ "default": 3,
1523
+ min: 1,
1524
+ max: 5
1525
+ },
1526
+ dueDate: {
1527
+ type: "date"
1528
+ },
1529
+ adminUserId: {
1530
+ type: "integer",
1531
+ required: true
1202
1532
  }
1203
1533
  };
1204
1534
  const schema = {
@@ -1209,8 +1539,8 @@ const schema = {
1209
1539
  pluginOptions,
1210
1540
  attributes
1211
1541
  };
1212
- const publicMemory = { schema };
1213
- const contentTypes = { conversation, memory, "public-memory": publicMemory };
1542
+ const task = { schema };
1543
+ const contentTypes = { conversation, memory, "public-memory": publicMemory, task };
1214
1544
  function getService(strapi, ctx) {
1215
1545
  const service2 = strapi.plugin("ai-sdk").service("service");
1216
1546
  if (!service2.isInitialized()) {
@@ -1509,20 +1839,20 @@ const mcpController = ({ strapi }) => {
1509
1839
  }
1510
1840
  };
1511
1841
  };
1512
- const CONTENT_TYPE$2 = "plugin::ai-sdk.conversation";
1513
- function getAdminUserId$1(ctx) {
1842
+ const CONTENT_TYPE$3 = "plugin::ai-sdk.conversation";
1843
+ function getAdminUserId$2(ctx) {
1514
1844
  const id = ctx.state?.user?.id;
1515
1845
  return typeof id === "number" ? id : null;
1516
1846
  }
1517
1847
  const conversationController = ({ strapi }) => ({
1518
1848
  async find(ctx) {
1519
- const adminUserId = getAdminUserId$1(ctx);
1849
+ const adminUserId = getAdminUserId$2(ctx);
1520
1850
  if (!adminUserId) {
1521
1851
  ctx.status = 401;
1522
1852
  ctx.body = { error: "Unauthorized" };
1523
1853
  return;
1524
1854
  }
1525
- const conversations = await strapi.documents(CONTENT_TYPE$2).findMany({
1855
+ const conversations = await strapi.documents(CONTENT_TYPE$3).findMany({
1526
1856
  filters: { adminUserId },
1527
1857
  fields: ["title", "createdAt", "updatedAt"],
1528
1858
  sort: { updatedAt: "desc" }
@@ -1530,14 +1860,14 @@ const conversationController = ({ strapi }) => ({
1530
1860
  ctx.body = { data: conversations };
1531
1861
  },
1532
1862
  async findOne(ctx) {
1533
- const adminUserId = getAdminUserId$1(ctx);
1863
+ const adminUserId = getAdminUserId$2(ctx);
1534
1864
  if (!adminUserId) {
1535
1865
  ctx.status = 401;
1536
1866
  ctx.body = { error: "Unauthorized" };
1537
1867
  return;
1538
1868
  }
1539
1869
  const { id } = ctx.params;
1540
- const conversation2 = await strapi.documents(CONTENT_TYPE$2).findOne({
1870
+ const conversation2 = await strapi.documents(CONTENT_TYPE$3).findOne({
1541
1871
  documentId: id
1542
1872
  });
1543
1873
  if (!conversation2 || conversation2.adminUserId !== adminUserId) {
@@ -1548,14 +1878,14 @@ const conversationController = ({ strapi }) => ({
1548
1878
  ctx.body = { data: conversation2 };
1549
1879
  },
1550
1880
  async create(ctx) {
1551
- const adminUserId = getAdminUserId$1(ctx);
1881
+ const adminUserId = getAdminUserId$2(ctx);
1552
1882
  if (!adminUserId) {
1553
1883
  ctx.status = 401;
1554
1884
  ctx.body = { error: "Unauthorized" };
1555
1885
  return;
1556
1886
  }
1557
1887
  const { title, messages } = ctx.request.body;
1558
- const conversation2 = await strapi.documents(CONTENT_TYPE$2).create({
1888
+ const conversation2 = await strapi.documents(CONTENT_TYPE$3).create({
1559
1889
  data: {
1560
1890
  title: title || "New conversation",
1561
1891
  messages: messages || [],
@@ -1566,14 +1896,14 @@ const conversationController = ({ strapi }) => ({
1566
1896
  ctx.body = { data: conversation2 };
1567
1897
  },
1568
1898
  async update(ctx) {
1569
- const adminUserId = getAdminUserId$1(ctx);
1899
+ const adminUserId = getAdminUserId$2(ctx);
1570
1900
  if (!adminUserId) {
1571
1901
  ctx.status = 401;
1572
1902
  ctx.body = { error: "Unauthorized" };
1573
1903
  return;
1574
1904
  }
1575
1905
  const { id } = ctx.params;
1576
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1906
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
1577
1907
  documentId: id
1578
1908
  });
1579
1909
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1585,21 +1915,21 @@ const conversationController = ({ strapi }) => ({
1585
1915
  const data = {};
1586
1916
  if (title !== void 0) data.title = title;
1587
1917
  if (messages !== void 0) data.messages = messages;
1588
- const conversation2 = await strapi.documents(CONTENT_TYPE$2).update({
1918
+ const conversation2 = await strapi.documents(CONTENT_TYPE$3).update({
1589
1919
  documentId: id,
1590
1920
  data
1591
1921
  });
1592
1922
  ctx.body = { data: conversation2 };
1593
1923
  },
1594
1924
  async delete(ctx) {
1595
- const adminUserId = getAdminUserId$1(ctx);
1925
+ const adminUserId = getAdminUserId$2(ctx);
1596
1926
  if (!adminUserId) {
1597
1927
  ctx.status = 401;
1598
1928
  ctx.body = { error: "Unauthorized" };
1599
1929
  return;
1600
1930
  }
1601
1931
  const { id } = ctx.params;
1602
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1932
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
1603
1933
  documentId: id
1604
1934
  });
1605
1935
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1607,25 +1937,25 @@ const conversationController = ({ strapi }) => ({
1607
1937
  ctx.body = { error: "Conversation not found" };
1608
1938
  return;
1609
1939
  }
1610
- await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
1940
+ await strapi.documents(CONTENT_TYPE$3).delete({ documentId: id });
1611
1941
  ctx.status = 200;
1612
1942
  ctx.body = { data: { documentId: id } };
1613
1943
  }
1614
1944
  });
1615
- const CONTENT_TYPE$1 = "plugin::ai-sdk.memory";
1616
- function getAdminUserId(ctx) {
1945
+ const CONTENT_TYPE$2 = "plugin::ai-sdk.memory";
1946
+ function getAdminUserId$1(ctx) {
1617
1947
  const id = ctx.state?.user?.id;
1618
1948
  return typeof id === "number" ? id : null;
1619
1949
  }
1620
1950
  const memoryController = ({ strapi }) => ({
1621
1951
  async find(ctx) {
1622
- const adminUserId = getAdminUserId(ctx);
1952
+ const adminUserId = getAdminUserId$1(ctx);
1623
1953
  if (!adminUserId) {
1624
1954
  ctx.status = 401;
1625
1955
  ctx.body = { error: "Unauthorized" };
1626
1956
  return;
1627
1957
  }
1628
- const memories = await strapi.documents(CONTENT_TYPE$1).findMany({
1958
+ const memories = await strapi.documents(CONTENT_TYPE$2).findMany({
1629
1959
  filters: { adminUserId },
1630
1960
  fields: ["content", "category", "createdAt"],
1631
1961
  sort: { createdAt: "desc" }
@@ -1633,7 +1963,7 @@ const memoryController = ({ strapi }) => ({
1633
1963
  ctx.body = { data: memories };
1634
1964
  },
1635
1965
  async create(ctx) {
1636
- const adminUserId = getAdminUserId(ctx);
1966
+ const adminUserId = getAdminUserId$1(ctx);
1637
1967
  if (!adminUserId) {
1638
1968
  ctx.status = 401;
1639
1969
  ctx.body = { error: "Unauthorized" };
@@ -1645,7 +1975,7 @@ const memoryController = ({ strapi }) => ({
1645
1975
  ctx.body = { error: "content is required" };
1646
1976
  return;
1647
1977
  }
1648
- const memory2 = await strapi.documents(CONTENT_TYPE$1).create({
1978
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).create({
1649
1979
  data: {
1650
1980
  content,
1651
1981
  category: category || "general",
@@ -1656,14 +1986,14 @@ const memoryController = ({ strapi }) => ({
1656
1986
  ctx.body = { data: memory2 };
1657
1987
  },
1658
1988
  async update(ctx) {
1659
- const adminUserId = getAdminUserId(ctx);
1989
+ const adminUserId = getAdminUserId$1(ctx);
1660
1990
  if (!adminUserId) {
1661
1991
  ctx.status = 401;
1662
1992
  ctx.body = { error: "Unauthorized" };
1663
1993
  return;
1664
1994
  }
1665
1995
  const { id } = ctx.params;
1666
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
1996
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1667
1997
  documentId: id
1668
1998
  });
1669
1999
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1675,21 +2005,21 @@ const memoryController = ({ strapi }) => ({
1675
2005
  const data = {};
1676
2006
  if (content !== void 0) data.content = content;
1677
2007
  if (category !== void 0) data.category = category;
1678
- const memory2 = await strapi.documents(CONTENT_TYPE$1).update({
2008
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).update({
1679
2009
  documentId: id,
1680
2010
  data
1681
2011
  });
1682
2012
  ctx.body = { data: memory2 };
1683
2013
  },
1684
2014
  async delete(ctx) {
1685
- const adminUserId = getAdminUserId(ctx);
2015
+ const adminUserId = getAdminUserId$1(ctx);
1686
2016
  if (!adminUserId) {
1687
2017
  ctx.status = 401;
1688
2018
  ctx.body = { error: "Unauthorized" };
1689
2019
  return;
1690
2020
  }
1691
2021
  const { id } = ctx.params;
1692
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
2022
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
1693
2023
  documentId: id
1694
2024
  });
1695
2025
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1697,15 +2027,15 @@ const memoryController = ({ strapi }) => ({
1697
2027
  ctx.body = { error: "Memory not found" };
1698
2028
  return;
1699
2029
  }
1700
- await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2030
+ await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
1701
2031
  ctx.status = 200;
1702
2032
  ctx.body = { data: { documentId: id } };
1703
2033
  }
1704
2034
  });
1705
- const CONTENT_TYPE = "plugin::ai-sdk.public-memory";
2035
+ const CONTENT_TYPE$1 = "plugin::ai-sdk.public-memory";
1706
2036
  const publicMemoryController = ({ strapi }) => ({
1707
2037
  async find(ctx) {
1708
- const memories = await strapi.documents(CONTENT_TYPE).findMany({
2038
+ const memories = await strapi.documents(CONTENT_TYPE$1).findMany({
1709
2039
  fields: ["content", "category", "createdAt"],
1710
2040
  sort: { createdAt: "desc" }
1711
2041
  });
@@ -1718,7 +2048,7 @@ const publicMemoryController = ({ strapi }) => ({
1718
2048
  ctx.body = { error: "content is required" };
1719
2049
  return;
1720
2050
  }
1721
- const memory2 = await strapi.documents(CONTENT_TYPE).create({
2051
+ const memory2 = await strapi.documents(CONTENT_TYPE$1).create({
1722
2052
  data: { content, category: category || "general" }
1723
2053
  });
1724
2054
  ctx.status = 201;
@@ -1726,7 +2056,7 @@ const publicMemoryController = ({ strapi }) => ({
1726
2056
  },
1727
2057
  async update(ctx) {
1728
2058
  const { id } = ctx.params;
1729
- const existing = await strapi.documents(CONTENT_TYPE).findOne({ documentId: id });
2059
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
1730
2060
  if (!existing) {
1731
2061
  ctx.status = 404;
1732
2062
  ctx.body = { error: "Public memory not found" };
@@ -1736,7 +2066,7 @@ const publicMemoryController = ({ strapi }) => ({
1736
2066
  const data = {};
1737
2067
  if (content !== void 0) data.content = content;
1738
2068
  if (category !== void 0) data.category = category;
1739
- const memory2 = await strapi.documents(CONTENT_TYPE).update({
2069
+ const memory2 = await strapi.documents(CONTENT_TYPE$1).update({
1740
2070
  documentId: id,
1741
2071
  data
1742
2072
  });
@@ -1744,12 +2074,103 @@ const publicMemoryController = ({ strapi }) => ({
1744
2074
  },
1745
2075
  async delete(ctx) {
1746
2076
  const { id } = ctx.params;
1747
- const existing = await strapi.documents(CONTENT_TYPE).findOne({ documentId: id });
2077
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
1748
2078
  if (!existing) {
1749
2079
  ctx.status = 404;
1750
2080
  ctx.body = { error: "Public memory not found" };
1751
2081
  return;
1752
2082
  }
2083
+ await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2084
+ ctx.status = 200;
2085
+ ctx.body = { data: { documentId: id } };
2086
+ }
2087
+ });
2088
+ const CONTENT_TYPE = "plugin::ai-sdk.task";
2089
+ function getAdminUserId(ctx) {
2090
+ const id = ctx.state?.user?.id;
2091
+ return typeof id === "number" ? id : null;
2092
+ }
2093
+ const taskController = ({ strapi }) => ({
2094
+ async find(ctx) {
2095
+ const adminUserId = getAdminUserId(ctx);
2096
+ if (!adminUserId) {
2097
+ ctx.status = 401;
2098
+ ctx.body = { error: "Unauthorized" };
2099
+ return;
2100
+ }
2101
+ const tasks = await strapi.documents(CONTENT_TYPE).findMany({
2102
+ filters: { adminUserId },
2103
+ sort: { createdAt: "desc" }
2104
+ });
2105
+ ctx.body = { data: tasks };
2106
+ },
2107
+ async create(ctx) {
2108
+ const adminUserId = getAdminUserId(ctx);
2109
+ if (!adminUserId) {
2110
+ ctx.status = 401;
2111
+ ctx.body = { error: "Unauthorized" };
2112
+ return;
2113
+ }
2114
+ const body = ctx.request.body;
2115
+ const task2 = await strapi.documents(CONTENT_TYPE).create({
2116
+ data: {
2117
+ title: body.title,
2118
+ description: body.description,
2119
+ content: body.content,
2120
+ done: body.done ?? false,
2121
+ priority: body.priority ?? "medium",
2122
+ consequence: body.consequence ?? 3,
2123
+ impact: body.impact ?? 3,
2124
+ dueDate: body.dueDate,
2125
+ adminUserId
2126
+ }
2127
+ });
2128
+ ctx.status = 201;
2129
+ ctx.body = { data: task2 };
2130
+ },
2131
+ async update(ctx) {
2132
+ const adminUserId = getAdminUserId(ctx);
2133
+ if (!adminUserId) {
2134
+ ctx.status = 401;
2135
+ ctx.body = { error: "Unauthorized" };
2136
+ return;
2137
+ }
2138
+ const { id } = ctx.params;
2139
+ const existing = await strapi.documents(CONTENT_TYPE).findOne({
2140
+ documentId: id
2141
+ });
2142
+ if (!existing || existing.adminUserId !== adminUserId) {
2143
+ ctx.status = 404;
2144
+ ctx.body = { error: "Task not found" };
2145
+ return;
2146
+ }
2147
+ const body = ctx.request.body;
2148
+ const data = {};
2149
+ for (const key of ["title", "description", "content", "done", "priority", "consequence", "impact", "dueDate"]) {
2150
+ if (body[key] !== void 0) data[key] = body[key];
2151
+ }
2152
+ const task2 = await strapi.documents(CONTENT_TYPE).update({
2153
+ documentId: id,
2154
+ data
2155
+ });
2156
+ ctx.body = { data: task2 };
2157
+ },
2158
+ async delete(ctx) {
2159
+ const adminUserId = getAdminUserId(ctx);
2160
+ if (!adminUserId) {
2161
+ ctx.status = 401;
2162
+ ctx.body = { error: "Unauthorized" };
2163
+ return;
2164
+ }
2165
+ const { id } = ctx.params;
2166
+ const existing = await strapi.documents(CONTENT_TYPE).findOne({
2167
+ documentId: id
2168
+ });
2169
+ if (!existing || existing.adminUserId !== adminUserId) {
2170
+ ctx.status = 404;
2171
+ ctx.body = { error: "Task not found" };
2172
+ return;
2173
+ }
1753
2174
  await strapi.documents(CONTENT_TYPE).delete({ documentId: id });
1754
2175
  ctx.status = 200;
1755
2176
  ctx.body = { data: { documentId: id } };
@@ -1760,7 +2181,8 @@ const controllers = {
1760
2181
  mcp: mcpController,
1761
2182
  conversation: conversationController,
1762
2183
  memory: memoryController,
1763
- "public-memory": publicMemoryController
2184
+ "public-memory": publicMemoryController,
2185
+ task: taskController
1764
2186
  };
1765
2187
  const promptInjection = [
1766
2188
  "ignore (all |any )?(previous|prior|above) (instructions|prompts|rules)",
@@ -2100,6 +2522,30 @@ const adminAPIRoutes = {
2100
2522
  path: "/public-memories/:id",
2101
2523
  handler: "public-memory.delete",
2102
2524
  config: { policies: [] }
2525
+ },
2526
+ {
2527
+ method: "GET",
2528
+ path: "/tasks",
2529
+ handler: "task.find",
2530
+ config: { policies: [] }
2531
+ },
2532
+ {
2533
+ method: "POST",
2534
+ path: "/tasks",
2535
+ handler: "task.create",
2536
+ config: { policies: [] }
2537
+ },
2538
+ {
2539
+ method: "PUT",
2540
+ path: "/tasks/:id",
2541
+ handler: "task.update",
2542
+ config: { policies: [] }
2543
+ },
2544
+ {
2545
+ method: "DELETE",
2546
+ path: "/tasks/:id",
2547
+ handler: "task.delete",
2548
+ config: { policies: [] }
2103
2549
  }
2104
2550
  ]
2105
2551
  };
@@ -2177,12 +2623,16 @@ const DEFAULT_PREAMBLE = `You are a Strapi CMS assistant. Use your tools to fulf
2177
2623
 
2178
2624
  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.
2179
2625
 
2626
+ 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.
2627
+
2180
2628
  Strapi filter syntax for searchContent and aggregateContent:
2181
2629
  - Scalar fields: { title: { $containsi: "hello" } }
2182
2630
  - Relation (manyToOne): { author: { name: { $eq: "John" } } }
2183
2631
  - Relation (manyToMany): { contentTags: { title: { $eq: "tutorial" } } }
2184
2632
  - Always nest relation filters as: { relationField: { fieldOnRelatedType: { $operator: value } } }
2185
- - Never use flat dot-path syntax like "contentTags.title" in filters — always use nested objects.`;
2633
+ - Never use flat dot-path syntax like "contentTags.title" in filters — always use nested objects.
2634
+
2635
+ 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.`;
2186
2636
  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.
2187
2637
 
2188
2638
  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.