@vheins/local-memory-mcp 0.13.2 → 0.14.1

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.
@@ -81,8 +81,8 @@ function loadServerInstructions() {
81
81
  // src/mcp/capabilities.ts
82
82
  var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
83
83
  var pkgVersion = "0.1.0";
84
- if ("0.13.2") {
85
- pkgVersion = "0.13.2";
84
+ if ("0.14.1") {
85
+ pkgVersion = "0.14.1";
86
86
  } else {
87
87
  let searchDir = __dirname2;
88
88
  for (let i = 0; i < 5; i++) {
@@ -1119,6 +1119,23 @@ var BaseEntity = class {
1119
1119
  };
1120
1120
 
1121
1121
  // src/mcp/entities/memory.ts
1122
+ var VALID_COLUMNS = /* @__PURE__ */ new Set([
1123
+ "code",
1124
+ "type",
1125
+ "title",
1126
+ "content",
1127
+ "importance",
1128
+ "agent",
1129
+ "role",
1130
+ "model",
1131
+ "completed_at",
1132
+ "expires_at",
1133
+ "supersedes",
1134
+ "status",
1135
+ "hit_count",
1136
+ "recall_count",
1137
+ "last_used_at"
1138
+ ]);
1122
1139
  var MemoryEntity = class extends BaseEntity {
1123
1140
  insert(entry) {
1124
1141
  this.run(
@@ -1179,7 +1196,7 @@ var MemoryEntity = class extends BaseEntity {
1179
1196
  } else if (k === "is_global") {
1180
1197
  fields.push(`${k} = ?`);
1181
1198
  values.push(val ? 1 : 0);
1182
- } else if (k !== "id" && k !== "created_at") {
1199
+ } else if (VALID_COLUMNS.has(k)) {
1183
1200
  fields.push(`${k} = ?`);
1184
1201
  values.push(val);
1185
1202
  }
@@ -1303,13 +1320,27 @@ var MemoryEntity = class extends BaseEntity {
1303
1320
  Object.keys(updates).forEach((key) => {
1304
1321
  const value = updates[key];
1305
1322
  if (value !== void 0) {
1306
- if (key === "tags" || key === "metadata") {
1323
+ if (key === "scope") {
1324
+ const scope = updates.scope;
1325
+ if (scope?.repo) {
1326
+ fields.push("repo = ?");
1327
+ values.push(scope.repo);
1328
+ }
1329
+ if (scope?.folder !== void 0) {
1330
+ fields.push("folder = ?");
1331
+ values.push(scope.folder);
1332
+ }
1333
+ if (scope?.language !== void 0) {
1334
+ fields.push("language = ?");
1335
+ values.push(scope.language);
1336
+ }
1337
+ } else if (key === "tags" || key === "metadata") {
1307
1338
  fields.push(`${key} = ?`);
1308
1339
  values.push(JSON.stringify(value));
1309
1340
  } else if (key === "is_global") {
1310
1341
  fields.push(`${key} = ?`);
1311
1342
  values.push(value ? 1 : 0);
1312
- } else {
1343
+ } else if (VALID_COLUMNS.has(key)) {
1313
1344
  fields.push(`${key} = ?`);
1314
1345
  values.push(value);
1315
1346
  }
@@ -1332,19 +1363,6 @@ var MemoryEntity = class extends BaseEntity {
1332
1363
  return count;
1333
1364
  });
1334
1365
  }
1335
- bulkDeleteMemories(ids) {
1336
- if (ids.length === 0) return 0;
1337
- return this.transaction(() => {
1338
- let count = 0;
1339
- const chunkSize = 500;
1340
- for (let i = 0; i < ids.length; i += chunkSize) {
1341
- const chunk = ids.slice(i, i + chunkSize);
1342
- const result = this.run(`DELETE FROM memories WHERE id IN (${chunk.map(() => "?").join(",")})`, chunk);
1343
- count += result.changes;
1344
- }
1345
- return count;
1346
- });
1347
- }
1348
1366
  getRecentMemories(repo, limit, offset = 0, includeArchived = false, excludeTypes = [], sortOrder = "DESC") {
1349
1367
  let query = "SELECT * FROM memories WHERE repo = ?";
1350
1368
  const params = [repo];
@@ -1390,24 +1408,6 @@ var MemoryEntity = class extends BaseEntity {
1390
1408
  id
1391
1409
  ]);
1392
1410
  }
1393
- getVectorCandidates(repo, limit = 100) {
1394
- let sql = `SELECT mv.memory_id, mv.vector FROM memory_vectors mv JOIN memories m ON mv.memory_id = m.id`;
1395
- const params = [];
1396
- if (repo) {
1397
- sql += " WHERE m.repo = ?";
1398
- params.push(repo);
1399
- }
1400
- sql += " LIMIT ?";
1401
- params.push(limit);
1402
- return this.all(sql, params);
1403
- }
1404
- upsertVectorEmbedding(memoryId, vector) {
1405
- this.run(
1406
- `INSERT INTO memory_vectors (memory_id, vector, updated_at) VALUES (?, ?, ?)
1407
- ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, updated_at = excluded.updated_at`,
1408
- [memoryId, JSON.stringify(vector), (/* @__PURE__ */ new Date()).toISOString()]
1409
- );
1410
- }
1411
1411
  getSummary(repo) {
1412
1412
  const row = this.get(
1413
1413
  "SELECT summary, updated_at FROM memory_summary WHERE repo = ?",
@@ -1487,6 +1487,28 @@ var MemoryEntity = class extends BaseEntity {
1487
1487
  }));
1488
1488
  return { items, memories: items, total, limit, offset };
1489
1489
  }
1490
+ };
1491
+
1492
+ // src/mcp/entities/memory.vector.ts
1493
+ var MemoryVectorEntity = class extends BaseEntity {
1494
+ getVectorCandidates(repo, limit = 100) {
1495
+ let sql = `SELECT mv.memory_id, mv.vector FROM memory_vectors mv JOIN memories m ON mv.memory_id = m.id`;
1496
+ const params = [];
1497
+ if (repo) {
1498
+ sql += " WHERE m.repo = ?";
1499
+ params.push(repo);
1500
+ }
1501
+ sql += " LIMIT ?";
1502
+ params.push(limit);
1503
+ return this.all(sql, params);
1504
+ }
1505
+ upsertVectorEmbedding(memoryId, vector) {
1506
+ this.run(
1507
+ `INSERT INTO memory_vectors (memory_id, vector, updated_at) VALUES (?, ?, ?)
1508
+ ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, updated_at = excluded.updated_at`,
1509
+ [memoryId, JSON.stringify(vector), (/* @__PURE__ */ new Date()).toISOString()]
1510
+ );
1511
+ }
1490
1512
  searchBySimilarity(query, repo, limit = 10, includeArchived = false, currentTags = []) {
1491
1513
  const queryVector = this.computeVector(query);
1492
1514
  const now = /* @__PURE__ */ new Date();
@@ -1504,8 +1526,12 @@ var MemoryEntity = class extends BaseEntity {
1504
1526
  if (candidates.length < 5) {
1505
1527
  const recentSql = `SELECT * FROM memories WHERE (${where.join(" OR ")}) AND status = 'active' AND (expires_at IS NULL OR expires_at > ?) ORDER BY created_at DESC LIMIT 10`;
1506
1528
  const recent = this.all(recentSql, [...params, now.toISOString()]);
1529
+ const candidateIds = new Set(candidates.map((c) => c.id));
1507
1530
  for (const r of recent) {
1508
- if (!candidates.find((c) => c.id === r.id)) candidates.push(r);
1531
+ if (!candidateIds.has(r.id)) {
1532
+ candidateIds.add(r.id);
1533
+ candidates.push(r);
1534
+ }
1509
1535
  }
1510
1536
  }
1511
1537
  return candidates.map((row) => {
@@ -1531,6 +1557,23 @@ var MemoryEntity = class extends BaseEntity {
1531
1557
  }
1532
1558
  return null;
1533
1559
  }
1560
+ };
1561
+
1562
+ // src/mcp/entities/memory.archive.ts
1563
+ var MemoryArchiveEntity = class extends BaseEntity {
1564
+ bulkDeleteMemories(ids) {
1565
+ if (ids.length === 0) return 0;
1566
+ return this.transaction(() => {
1567
+ let count = 0;
1568
+ const chunkSize = 500;
1569
+ for (let i = 0; i < ids.length; i += chunkSize) {
1570
+ const chunk = ids.slice(i, i + chunkSize);
1571
+ const result = this.run(`DELETE FROM memories WHERE id IN (${chunk.map(() => "?").join(",")})`, chunk);
1572
+ count += result.changes;
1573
+ }
1574
+ return count;
1575
+ });
1576
+ }
1534
1577
  archiveExpiredMemories(force = false) {
1535
1578
  if (process.env.ENABLE_AUTO_ARCHIVE !== "true" && !force) return 0;
1536
1579
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1606,7 +1649,7 @@ var TaskEntity = class extends BaseEntity {
1606
1649
  const fields = [];
1607
1650
  const values = [];
1608
1651
  const anyUpdates = updates;
1609
- const VALID_COLUMNS = /* @__PURE__ */ new Set([
1652
+ const VALID_COLUMNS2 = /* @__PURE__ */ new Set([
1610
1653
  "repo",
1611
1654
  "task_code",
1612
1655
  "phase",
@@ -1629,7 +1672,7 @@ var TaskEntity = class extends BaseEntity {
1629
1672
  "changed_files"
1630
1673
  ]);
1631
1674
  Object.keys(updates).forEach((key) => {
1632
- if (VALID_COLUMNS.has(key) && anyUpdates[key] !== void 0) {
1675
+ if (VALID_COLUMNS2.has(key) && anyUpdates[key] !== void 0) {
1633
1676
  if (key === "tags" || key === "metadata" || key === "changed_files") {
1634
1677
  fields.push(`${key} = ?`);
1635
1678
  values.push(JSON.stringify(anyUpdates[key]));
@@ -1659,7 +1702,7 @@ var TaskEntity = class extends BaseEntity {
1659
1702
  WHERE t.id = ?`,
1660
1703
  [id]
1661
1704
  );
1662
- return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(id) } : null;
1705
+ return row ? { ...this.rowToTask(row), comments: this.all("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at DESC, id DESC", [id]) } : null;
1663
1706
  }
1664
1707
  getTasksByIds(ids) {
1665
1708
  if (ids.length === 0) return [];
@@ -1700,7 +1743,7 @@ var TaskEntity = class extends BaseEntity {
1700
1743
  WHERE t.repo = ? AND t.task_code = ?`,
1701
1744
  [repo, taskCode]
1702
1745
  );
1703
- return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(row.id) } : null;
1746
+ return row ? { ...this.rowToTask(row), comments: this.all("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at DESC, id DESC", [row.id]) } : null;
1704
1747
  }
1705
1748
  getTasksByRepo(repo, status, limit, offset, search) {
1706
1749
  let query = `
@@ -1886,6 +1929,10 @@ var TaskEntity = class extends BaseEntity {
1886
1929
  return count;
1887
1930
  });
1888
1931
  }
1932
+ };
1933
+
1934
+ // src/mcp/entities/task-comment.ts
1935
+ var TaskCommentEntity = class extends BaseEntity {
1889
1936
  insertTaskComment(comment) {
1890
1937
  this.run(
1891
1938
  `INSERT INTO task_comments (
@@ -1909,8 +1956,17 @@ var TaskEntity = class extends BaseEntity {
1909
1956
  const fields = [];
1910
1957
  const values = [];
1911
1958
  const anyUpdates = updates;
1959
+ const VALID_COLUMNS2 = /* @__PURE__ */ new Set([
1960
+ "repo",
1961
+ "comment",
1962
+ "agent",
1963
+ "role",
1964
+ "model",
1965
+ "previous_status",
1966
+ "next_status"
1967
+ ]);
1912
1968
  Object.keys(updates).forEach((key) => {
1913
- if (anyUpdates[key] !== void 0) {
1969
+ if (VALID_COLUMNS2.has(key) && anyUpdates[key] !== void 0) {
1914
1970
  fields.push(`${key} = ?`);
1915
1971
  values.push(anyUpdates[key]);
1916
1972
  }
@@ -1935,6 +1991,10 @@ var TaskEntity = class extends BaseEntity {
1935
1991
  repo
1936
1992
  ]);
1937
1993
  }
1994
+ };
1995
+
1996
+ // src/mcp/entities/task-stats.ts
1997
+ var TaskStatsEntity = class extends BaseEntity {
1938
1998
  getTaskStats(repo) {
1939
1999
  const rows = this.all(
1940
2000
  "SELECT status, COUNT(*) as count FROM tasks WHERE repo = ? GROUP BY status",
@@ -2863,7 +2923,11 @@ var DB_PATH = resolveDbPath();
2863
2923
  var SQLiteStore = class _SQLiteStore {
2864
2924
  db;
2865
2925
  memories;
2926
+ memoryVectors;
2927
+ memoryArchives;
2866
2928
  tasks;
2929
+ taskComments;
2930
+ taskStats;
2867
2931
  actions;
2868
2932
  system;
2869
2933
  summaries;
@@ -2894,7 +2958,11 @@ var SQLiteStore = class _SQLiteStore {
2894
2958
  migrator.addMemoryCodeColumn();
2895
2959
  migrator.addStandardCodeColumn();
2896
2960
  this.memories = new MemoryEntity(this.db);
2961
+ this.memoryVectors = new MemoryVectorEntity(this.db);
2962
+ this.memoryArchives = new MemoryArchiveEntity(this.db);
2897
2963
  this.tasks = new TaskEntity(this.db);
2964
+ this.taskComments = new TaskCommentEntity(this.db);
2965
+ this.taskStats = new TaskStatsEntity(this.db);
2898
2966
  this.actions = new ActionEntity(this.db);
2899
2967
  this.system = new SystemEntity(this.db);
2900
2968
  this.summaries = new SummaryEntity(this.db);
@@ -3045,7 +3113,7 @@ var RealVectorStore = class {
3045
3113
  if (kind === "standard") {
3046
3114
  this.db.standards.upsertVectorEmbedding(id, vector);
3047
3115
  } else {
3048
- this.db.memories.upsertVectorEmbedding(id, vector);
3116
+ this.db.memoryVectors.upsertVectorEmbedding(id, vector);
3049
3117
  }
3050
3118
  } catch (error) {
3051
3119
  logger.error("[Vectors] Error during upsert", { id, kind, error: String(error) });
@@ -3061,7 +3129,7 @@ var RealVectorStore = class {
3061
3129
  const extractor = await this.getExtractor();
3062
3130
  const output = await extractor(query, { pooling: "mean", normalize: true });
3063
3131
  const queryVector = Array.from(output.data);
3064
- const rows = kind === "standard" ? this.db.standards.getVectorCandidates(repo, 100).map((row) => ({ id: row.standard_id, vector: row.vector })) : this.db.memories.getVectorCandidates(repo, 100).map((row) => ({ id: row.memory_id, vector: row.vector }));
3132
+ const rows = kind === "standard" ? this.db.standards.getVectorCandidates(repo, 100).map((row) => ({ id: row.standard_id, vector: row.vector })) : this.db.memoryVectors.getVectorCandidates(repo, 100).map((row) => ({ id: row.memory_id, vector: row.vector }));
3065
3133
  const results = rows.map((row) => {
3066
3134
  const memoryVector = JSON.parse(row.vector);
3067
3135
  return {
@@ -3264,9 +3332,12 @@ var MemoryDeleteSchema = z.object({
3264
3332
  code: z.string().max(20).optional(),
3265
3333
  codes: z.array(z.string().max(20)).min(1).optional(),
3266
3334
  structured: z.boolean().default(false)
3267
- }).refine((data) => data.id !== void 0 || data.ids !== void 0 || data.code !== void 0 || data.codes !== void 0, {
3268
- message: "Either 'id', 'ids', 'code', or 'codes' must be provided for deletion"
3269
- });
3335
+ }).refine(
3336
+ (data) => data.id !== void 0 || data.ids !== void 0 || data.code !== void 0 || data.codes !== void 0,
3337
+ {
3338
+ message: "Either 'id', 'ids', 'code', or 'codes' must be provided for deletion"
3339
+ }
3340
+ );
3270
3341
  var MemorySummarizeSchema = z.object({
3271
3342
  repo: z.string().min(1).transform(normalizeRepo),
3272
3343
  signals: z.array(z.string().max(200)).min(1),
@@ -3374,6 +3445,8 @@ var TaskSearchSchema = z.object({
3374
3445
  repo: z.string().min(1).transform(normalizeRepo),
3375
3446
  query: z.string().min(1),
3376
3447
  status: z.string().optional(),
3448
+ phase: z.string().optional(),
3449
+ priority: z.number().min(1).max(5).optional(),
3377
3450
  limit: z.number().min(1).max(100).default(10),
3378
3451
  offset: z.number().min(0).default(0),
3379
3452
  structured: z.boolean().default(false)
@@ -3408,9 +3481,12 @@ var StandardDeleteSchema = z.object({
3408
3481
  code: z.string().max(20).optional(),
3409
3482
  codes: z.array(z.string().max(20)).min(1).optional(),
3410
3483
  structured: z.boolean().default(false)
3411
- }).refine((data) => data.id !== void 0 || data.ids !== void 0 || data.code !== void 0 || data.codes !== void 0, {
3412
- message: "Either 'id', 'ids', 'code', or 'codes' must be provided for deletion"
3413
- });
3484
+ }).refine(
3485
+ (data) => data.id !== void 0 || data.ids !== void 0 || data.code !== void 0 || data.codes !== void 0,
3486
+ {
3487
+ message: "Either 'id', 'ids', 'code', or 'codes' must be provided for deletion"
3488
+ }
3489
+ );
3414
3490
  var TaskGetSchema = z.object({
3415
3491
  repo: z.string().min(1).transform(normalizeRepo),
3416
3492
  id: z.string().uuid().optional(),
@@ -4037,7 +4113,11 @@ var TOOL_DEFINITIONS = [
4037
4113
  type: "object",
4038
4114
  properties: {
4039
4115
  repo: { type: "string", description: "Repository name (optional for single id)" },
4040
- id: { type: "string", format: "uuid", description: "Coding standard ID to delete. Optional if code is provided." },
4116
+ id: {
4117
+ type: "string",
4118
+ format: "uuid",
4119
+ description: "Coding standard ID to delete. Optional if code is provided."
4120
+ },
4041
4121
  ids: {
4042
4122
  type: "array",
4043
4123
  items: { type: "string", format: "uuid" },
@@ -4184,7 +4264,10 @@ var TOOL_DEFINITIONS = [
4184
4264
  doc_path: { type: "string" },
4185
4265
  tags: { type: "array", items: { type: "string" } },
4186
4266
  metadata: { type: "object" },
4187
- parent_id: { type: "string", description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing." },
4267
+ parent_id: {
4268
+ type: "string",
4269
+ description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing."
4270
+ },
4188
4271
  depends_on: { type: "string", format: "uuid" },
4189
4272
  est_tokens: { type: "number", minimum: 0, description: "Estimated tokens budget for this task" },
4190
4273
  tasks: {
@@ -4212,7 +4295,10 @@ var TOOL_DEFINITIONS = [
4212
4295
  doc_path: { type: "string" },
4213
4296
  tags: { type: "array", items: { type: "string" } },
4214
4297
  metadata: { type: "object" },
4215
- parent_id: { type: "string", description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing." },
4298
+ parent_id: {
4299
+ type: "string",
4300
+ description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing."
4301
+ },
4216
4302
  depends_on: { type: "string", format: "uuid" },
4217
4303
  est_tokens: { type: "number", minimum: 0 }
4218
4304
  },
@@ -4284,7 +4370,10 @@ var TOOL_DEFINITIONS = [
4284
4370
  doc_path: { type: "string" },
4285
4371
  tags: { type: "array", items: { type: "string" } },
4286
4372
  metadata: { type: "object" },
4287
- parent_id: { type: "string", description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing." },
4373
+ parent_id: {
4374
+ type: "string",
4375
+ description: "Optional parent task ID (UUID) or parent task code (e.g. TASK-001). Resolved to UUID before storing."
4376
+ },
4288
4377
  depends_on: { type: "string", format: "uuid" },
4289
4378
  est_tokens: {
4290
4379
  type: "number",
@@ -4340,7 +4429,11 @@ var TOOL_DEFINITIONS = [
4340
4429
  type: "object",
4341
4430
  properties: {
4342
4431
  repo: { type: "string", description: "Repository name" },
4343
- id: { type: "string", format: "uuid", description: "Task ID (for single deletion). Optional if task_code is provided." },
4432
+ id: {
4433
+ type: "string",
4434
+ format: "uuid",
4435
+ description: "Task ID (for single deletion). Optional if task_code is provided."
4436
+ },
4344
4437
  ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk deletion)" },
4345
4438
  task_code: { type: "string", description: "Task code (e.g. TASK-001). Optional if id is provided." },
4346
4439
  structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
@@ -4436,6 +4529,58 @@ var TOOL_DEFINITIONS = [
4436
4529
  required: ["schema", "tasks", "count"]
4437
4530
  }
4438
4531
  },
4532
+ {
4533
+ name: "task-search",
4534
+ title: "Task Search",
4535
+ description: "Dedicated search tool for tasks. Returns a compact pointer table of matching tasks [id, task_code, title, status, priority, updated_at, phase]. Use task-detail for full task content.",
4536
+ annotations: {
4537
+ readOnlyHint: true,
4538
+ idempotentHint: true,
4539
+ openWorldHint: false
4540
+ },
4541
+ inputSchema: {
4542
+ type: "object",
4543
+ properties: {
4544
+ repo: { type: "string", description: "Repository name" },
4545
+ query: { type: "string", minLength: 1, description: "Search keyword matching task code, title, or description" },
4546
+ status: { type: "string", description: "Optional status filter (single or comma-separated)" },
4547
+ phase: { type: "string", description: "Filter by phase (e.g., 'research', 'implementation')" },
4548
+ priority: { type: "number", minimum: 1, maximum: 5, description: "Filter by priority (1-5)" },
4549
+ limit: { type: "number", minimum: 1, maximum: 100, default: 10 },
4550
+ offset: { type: "number", minimum: 0, default: 0 },
4551
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
4552
+ },
4553
+ required: ["repo", "query"]
4554
+ },
4555
+ outputSchema: {
4556
+ type: "object",
4557
+ properties: {
4558
+ schema: { type: "string", enum: ["task-search"] },
4559
+ query: { type: "string" },
4560
+ count: { type: "number", description: "Number of rows returned" },
4561
+ total: { type: "number", description: "Total matching tasks before pagination" },
4562
+ offset: { type: "number" },
4563
+ limit: { type: "number" },
4564
+ results: {
4565
+ type: "object",
4566
+ properties: {
4567
+ columns: {
4568
+ type: "array",
4569
+ items: { type: "string" },
4570
+ description: "Column names: [id, task_code, title, status, priority, updated_at, phase]"
4571
+ },
4572
+ rows: {
4573
+ type: "array",
4574
+ items: { type: "array" },
4575
+ description: "Each row: [id, task_code, title, status, priority, updated_at, phase]. Use task-detail to fetch full task."
4576
+ }
4577
+ },
4578
+ required: ["columns", "rows"]
4579
+ }
4580
+ },
4581
+ required: ["schema", "query", "count", "total", "offset", "limit", "results"]
4582
+ }
4583
+ },
4439
4584
  {
4440
4585
  name: "handoff-create",
4441
4586
  title: "Handoff Create",
@@ -5409,6 +5554,369 @@ async function completePromptArgument(name, argName, value, contextArguments, da
5409
5554
  return [];
5410
5555
  }
5411
5556
 
5557
+ // src/mcp/utils/mcp-response.ts
5558
+ import { z as z2 } from "zod";
5559
+ var McpAnnotationsSchema = z2.object({
5560
+ audience: z2.array(z2.enum(["user", "assistant"])).optional(),
5561
+ priority: z2.number().min(0).max(1).optional(),
5562
+ lastModified: z2.string().optional()
5563
+ }).strict().optional();
5564
+ var McpContentSchema = z2.discriminatedUnion("type", [
5565
+ z2.object({
5566
+ type: z2.literal("text"),
5567
+ text: z2.string(),
5568
+ annotations: McpAnnotationsSchema
5569
+ }),
5570
+ z2.object({
5571
+ type: z2.literal("image"),
5572
+ data: z2.string(),
5573
+ mimeType: z2.string(),
5574
+ annotations: McpAnnotationsSchema
5575
+ }),
5576
+ z2.object({
5577
+ type: z2.literal("resource"),
5578
+ resource: z2.object({
5579
+ uri: z2.string(),
5580
+ mimeType: z2.string().optional(),
5581
+ text: z2.string().optional(),
5582
+ annotations: McpAnnotationsSchema
5583
+ })
5584
+ })
5585
+ ]);
5586
+ function createMcpResponse(data, summary, options) {
5587
+ const { structuredContentPathHint, contentSummary, includeSerializedStructuredContent = false } = options || {};
5588
+ void includeSerializedStructuredContent;
5589
+ let finalData = data;
5590
+ if (data && typeof data === "object") {
5591
+ const cloned = JSON.parse(JSON.stringify(data));
5592
+ finalData = cloned;
5593
+ const arrayKeys = ["results", "tasks", "memories", "items"];
5594
+ let foundArray = false;
5595
+ for (const key of arrayKeys) {
5596
+ const value = cloned[key];
5597
+ if (Array.isArray(value)) {
5598
+ cloned[key] = value.map(
5599
+ (item) => pruneMetadata(item)
5600
+ );
5601
+ foundArray = true;
5602
+ }
5603
+ }
5604
+ if (Array.isArray(cloned)) {
5605
+ finalData = cloned.map((item) => pruneMetadata(item));
5606
+ } else if (!foundArray) {
5607
+ finalData = pruneMetadata(cloned);
5608
+ }
5609
+ }
5610
+ const content = [];
5611
+ if (contentSummary && contentSummary.trim().length > 0) {
5612
+ content.push({
5613
+ type: "text",
5614
+ text: contentSummary.trim()
5615
+ });
5616
+ } else if (summary && summary.trim().length > 0) {
5617
+ const pointerText = structuredContentPathHint ? `Read structuredContent.${structuredContentPathHint} for details.` : `Read structuredContent for machine-readable results.`;
5618
+ content.push({
5619
+ type: "text",
5620
+ text: `${summary.trim()} ${pointerText}`
5621
+ });
5622
+ }
5623
+ const response = {
5624
+ structuredContent: finalData,
5625
+ isError: false
5626
+ };
5627
+ if (includeSerializedStructuredContent === false) {
5628
+ delete response.structuredContent;
5629
+ }
5630
+ response.content = content;
5631
+ return response;
5632
+ }
5633
+ function pruneMetadata(item) {
5634
+ if (!item || typeof item !== "object") return item;
5635
+ const pruned = { ...item };
5636
+ const toRemove = [
5637
+ "hit_count",
5638
+ "recall_count",
5639
+ "last_used_at",
5640
+ "expires_at",
5641
+ "agent",
5642
+ "role",
5643
+ "model",
5644
+ "recall_rate",
5645
+ "vector_version",
5646
+ "similarity"
5647
+ // Similarity is useful but adds noise if many results
5648
+ ];
5649
+ for (const field of toRemove) {
5650
+ delete pruned[field];
5651
+ }
5652
+ return pruned;
5653
+ }
5654
+ function getPrimaryTextContent(response) {
5655
+ if (!Array.isArray(response.content)) return "";
5656
+ const textItem = response.content.find((item) => item.type === "text");
5657
+ return textItem?.type === "text" ? textItem.text : "";
5658
+ }
5659
+
5660
+ // src/mcp/tools/handoff.manage.ts
5661
+ function buildHandoffListSummary(repo, count, status, fromAgent, toAgent) {
5662
+ const parts = [`Found ${count} handoff${count === 1 ? "" : "s"} in repo "${repo}".`];
5663
+ if (status) {
5664
+ parts.push(`Status filter: ${status}.`);
5665
+ }
5666
+ if (fromAgent) {
5667
+ parts.push(`From agent: ${fromAgent}.`);
5668
+ }
5669
+ if (toAgent) {
5670
+ parts.push(`To agent: ${toAgent}.`);
5671
+ }
5672
+ return parts.join("\n");
5673
+ }
5674
+ function buildClaimListSummary(repo, count, agent, activeOnly) {
5675
+ const parts = [`Found ${count} claim${count === 1 ? "" : "s"} in repo "${repo}".`];
5676
+ if (agent) {
5677
+ parts.push(`Agent filter: ${agent}.`);
5678
+ }
5679
+ if (activeOnly) {
5680
+ parts.push("Showing active claims only.");
5681
+ }
5682
+ return parts.join("\n");
5683
+ }
5684
+ async function handleHandoffCreate(args, storage) {
5685
+ const validated = HandoffCreateSchema.parse(args);
5686
+ const { repo, from_agent, to_agent, task_id, task_code, summary, context, expires_at, structured } = validated;
5687
+ let resolvedTaskId = task_id ?? null;
5688
+ if (!resolvedTaskId && task_code) {
5689
+ const task = storage.tasks.getTaskByCode(repo, task_code);
5690
+ if (!task) {
5691
+ throw new Error(`Task not found: ${task_code} in repo ${repo}`);
5692
+ }
5693
+ resolvedTaskId = task.id;
5694
+ }
5695
+ const handoff = storage.handoffs.createHandoff({
5696
+ repo,
5697
+ from_agent,
5698
+ to_agent,
5699
+ task_id: resolvedTaskId,
5700
+ summary,
5701
+ context,
5702
+ expires_at
5703
+ });
5704
+ const contentSummary = [
5705
+ `Created handoff ${handoff.id}.`,
5706
+ `Repo: ${handoff.repo}`,
5707
+ `From: ${handoff.from_agent}`,
5708
+ `To: ${handoff.to_agent || "unassigned"}`,
5709
+ `Status: ${handoff.status}`,
5710
+ `Task ID: ${handoff.task_id || "-"}`,
5711
+ `Summary: ${handoff.summary}`
5712
+ ].join("\n");
5713
+ return createMcpResponse(handoff, contentSummary, {
5714
+ contentSummary,
5715
+ includeSerializedStructuredContent: structured
5716
+ });
5717
+ }
5718
+ async function handleHandoffList(args, storage) {
5719
+ const validated = HandoffListSchema.parse(args);
5720
+ const { repo, status, from_agent, to_agent, limit, offset, structured } = validated;
5721
+ const handoffs = storage.handoffs.listHandoffs({
5722
+ repo,
5723
+ status,
5724
+ from_agent,
5725
+ to_agent,
5726
+ limit,
5727
+ offset
5728
+ });
5729
+ const COLUMNS = [
5730
+ "id",
5731
+ "from_agent",
5732
+ "to_agent",
5733
+ "task_id",
5734
+ "task_code",
5735
+ "status",
5736
+ "created_at",
5737
+ "updated_at",
5738
+ "expires_at",
5739
+ "summary",
5740
+ "context"
5741
+ ];
5742
+ const rows = handoffs.map((handoff) => [
5743
+ handoff.id,
5744
+ handoff.from_agent,
5745
+ handoff.to_agent,
5746
+ handoff.task_id,
5747
+ handoff.task_code ?? null,
5748
+ handoff.status,
5749
+ handoff.created_at,
5750
+ handoff.updated_at,
5751
+ handoff.expires_at,
5752
+ handoff.summary,
5753
+ handoff.context
5754
+ ]);
5755
+ const structuredData = {
5756
+ schema: "handoff-list",
5757
+ handoffs: {
5758
+ columns: [...COLUMNS],
5759
+ rows
5760
+ },
5761
+ count: rows.length,
5762
+ offset
5763
+ };
5764
+ const contentSummary = buildHandoffListSummary(repo, rows.length, status, from_agent, to_agent);
5765
+ return createMcpResponse(structuredData, contentSummary, {
5766
+ contentSummary,
5767
+ includeSerializedStructuredContent: structured
5768
+ });
5769
+ }
5770
+ async function handleHandoffUpdate(args, storage) {
5771
+ const validated = HandoffUpdateSchema.parse(args);
5772
+ const { id, status, structured } = validated;
5773
+ const existing = storage.handoffs.getHandoffById(id);
5774
+ if (!existing) {
5775
+ throw new Error(`Handoff not found: ${id}`);
5776
+ }
5777
+ const success = storage.handoffs.updateHandoffStatus(id, status);
5778
+ if (!success) {
5779
+ throw new Error(`Failed to update handoff: ${id}`);
5780
+ }
5781
+ const updated = storage.handoffs.getHandoffById(id);
5782
+ const result = {
5783
+ success,
5784
+ id,
5785
+ status,
5786
+ handoff: updated
5787
+ };
5788
+ const contentSummary = [`Updated handoff ${id}.`, `Status: ${status}`].join("\n");
5789
+ return createMcpResponse(result, contentSummary, {
5790
+ contentSummary,
5791
+ includeSerializedStructuredContent: structured
5792
+ });
5793
+ }
5794
+ async function handleTaskClaim(args, storage) {
5795
+ const validated = TaskClaimSchema.parse(args);
5796
+ const { repo, task_id, task_code, agent, role, metadata, structured } = validated;
5797
+ let taskId = task_id;
5798
+ let resolvedTaskCode;
5799
+ if (taskId) {
5800
+ const task = storage.tasks.getTaskById(taskId);
5801
+ if (!task || task.repo !== repo) {
5802
+ throw new Error(`Task not found: ${taskId} in repo ${repo}`);
5803
+ }
5804
+ resolvedTaskCode = task.task_code;
5805
+ } else if (task_code) {
5806
+ const task = storage.tasks.getTaskByCode(repo, task_code);
5807
+ if (!task) {
5808
+ throw new Error(`Task not found: ${task_code} in repo ${repo}`);
5809
+ }
5810
+ taskId = task.id;
5811
+ resolvedTaskCode = task.task_code;
5812
+ } else {
5813
+ throw new Error("Either task_id or task_code must be provided");
5814
+ }
5815
+ const claim = storage.handoffs.claimTask({
5816
+ repo,
5817
+ task_id: taskId,
5818
+ agent,
5819
+ role,
5820
+ metadata
5821
+ });
5822
+ const responseData = {
5823
+ ...claim,
5824
+ task_code: resolvedTaskCode
5825
+ };
5826
+ const contentSummary = [
5827
+ `Claimed task ${resolvedTaskCode || claim.task_id}.`,
5828
+ `Repo: ${claim.repo}`,
5829
+ `Task ID: ${claim.task_id}`,
5830
+ `Agent: ${claim.agent}`,
5831
+ `Role: ${claim.role}`,
5832
+ `Claimed At: ${claim.claimed_at}`
5833
+ ].join("\n");
5834
+ const response = createMcpResponse(responseData, contentSummary, {
5835
+ contentSummary,
5836
+ includeSerializedStructuredContent: structured
5837
+ });
5838
+ if (structured) {
5839
+ response.structuredContent = responseData;
5840
+ }
5841
+ return response;
5842
+ }
5843
+ async function handleClaimList(args, storage) {
5844
+ const validated = ClaimListSchema.parse(args);
5845
+ const { repo, agent, active_only, limit, offset, structured } = validated;
5846
+ const claims = storage.handoffs.listClaims({
5847
+ repo,
5848
+ agent,
5849
+ active_only,
5850
+ limit,
5851
+ offset
5852
+ });
5853
+ const COLUMNS = ["id", "task_id", "task_code", "agent", "role", "claimed_at", "released_at", "metadata"];
5854
+ const rows = claims.map((claim) => [
5855
+ claim.id,
5856
+ claim.task_id,
5857
+ claim.task_code ?? null,
5858
+ claim.agent,
5859
+ claim.role,
5860
+ claim.claimed_at,
5861
+ claim.released_at,
5862
+ claim.metadata
5863
+ ]);
5864
+ const structuredData = {
5865
+ schema: "claim-list",
5866
+ claims: {
5867
+ columns: [...COLUMNS],
5868
+ rows
5869
+ },
5870
+ count: rows.length,
5871
+ offset
5872
+ };
5873
+ const contentSummary = buildClaimListSummary(repo, rows.length, agent, active_only);
5874
+ return createMcpResponse(structuredData, contentSummary, {
5875
+ contentSummary,
5876
+ includeSerializedStructuredContent: structured
5877
+ });
5878
+ }
5879
+ async function handleClaimRelease(args, storage) {
5880
+ const validated = ClaimReleaseSchema.parse(args);
5881
+ const { repo, task_id, task_code, agent, structured } = validated;
5882
+ let resolvedTaskId = task_id;
5883
+ let resolvedTaskCode = task_code ?? null;
5884
+ if (resolvedTaskId) {
5885
+ const task = storage.tasks.getTaskById(resolvedTaskId);
5886
+ if (!task || task.repo !== repo) {
5887
+ throw new Error(`Task not found: ${resolvedTaskId} in repo ${repo}`);
5888
+ }
5889
+ resolvedTaskCode = task.task_code;
5890
+ } else if (task_code) {
5891
+ const task = storage.tasks.getTaskByCode(repo, task_code);
5892
+ if (!task) {
5893
+ throw new Error(`Task not found: ${task_code} in repo ${repo}`);
5894
+ }
5895
+ resolvedTaskId = task.id;
5896
+ resolvedTaskCode = task.task_code;
5897
+ }
5898
+ const success = storage.handoffs.releaseClaim(resolvedTaskId, agent);
5899
+ if (!success) {
5900
+ throw new Error(`No active claim found for task ${resolvedTaskCode || resolvedTaskId}`);
5901
+ }
5902
+ const result = {
5903
+ success,
5904
+ repo,
5905
+ task_id: resolvedTaskId,
5906
+ task_code: resolvedTaskCode,
5907
+ agent: agent ?? null
5908
+ };
5909
+ const contentSummary = [
5910
+ `Released claim for task ${resolvedTaskCode || resolvedTaskId}.`,
5911
+ `Repo: ${repo}`,
5912
+ agent ? `Agent: ${agent}` : "Agent: any active claimant"
5913
+ ].join("\n");
5914
+ return createMcpResponse(result, contentSummary, {
5915
+ contentSummary,
5916
+ includeSerializedStructuredContent: structured
5917
+ });
5918
+ }
5919
+
5412
5920
  // src/mcp/tools/standard.shared.ts
5413
5921
  function toContextSlug(value) {
5414
5922
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -5450,17 +5958,12 @@ export {
5450
5958
  TaskCreateInteractiveSchema,
5451
5959
  TaskUpdateSchema,
5452
5960
  TaskListSchema,
5961
+ TaskSearchSchema,
5453
5962
  TaskDeleteSchema,
5454
5963
  MemoryDetailSchema,
5455
5964
  StandardDetailSchema,
5456
5965
  StandardDeleteSchema,
5457
5966
  TaskGetSchema,
5458
- HandoffCreateSchema,
5459
- HandoffUpdateSchema,
5460
- HandoffListSchema,
5461
- TaskClaimSchema,
5462
- ClaimListSchema,
5463
- ClaimReleaseSchema,
5464
5967
  StandardStoreSchema,
5465
5968
  StandardUpdateSchema,
5466
5969
  StandardSearchSchema,
@@ -5483,6 +5986,14 @@ export {
5483
5986
  listPrompts,
5484
5987
  getPrompt,
5485
5988
  completePromptArgument,
5989
+ createMcpResponse,
5990
+ getPrimaryTextContent,
5991
+ handleHandoffCreate,
5992
+ handleHandoffList,
5993
+ handleHandoffUpdate,
5994
+ handleTaskClaim,
5995
+ handleClaimList,
5996
+ handleClaimRelease,
5486
5997
  toContextSlug,
5487
5998
  buildStandardVectorText
5488
5999
  };