@hasna/todos 0.9.34 → 0.9.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1262 -102
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +113 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +625 -12
- package/dist/lib/recurrence.d.ts +10 -0
- package/dist/lib/recurrence.d.ts.map +1 -0
- package/dist/lib/search.d.ts +14 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +1271 -101
- package/dist/server/index.js +176 -6
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -192,6 +192,8 @@ function ensureSchema(db) {
|
|
|
192
192
|
ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
|
|
193
193
|
ensureColumn("tasks", "approved_by", "TEXT");
|
|
194
194
|
ensureColumn("tasks", "approved_at", "TEXT");
|
|
195
|
+
ensureColumn("tasks", "recurrence_rule", "TEXT");
|
|
196
|
+
ensureColumn("tasks", "recurrence_parent_id", "TEXT REFERENCES tasks(id) ON DELETE SET NULL");
|
|
195
197
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
196
198
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
197
199
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -216,6 +218,8 @@ function ensureSchema(db) {
|
|
|
216
218
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
|
|
217
219
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
|
|
218
220
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
|
|
221
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id)");
|
|
222
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL");
|
|
219
223
|
}
|
|
220
224
|
function backfillTaskTags(db) {
|
|
221
225
|
try {
|
|
@@ -515,6 +519,13 @@ var init_database = __esm(() => {
|
|
|
515
519
|
ALTER TABLE agents ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
|
|
516
520
|
ALTER TABLE projects ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
|
|
517
521
|
INSERT OR IGNORE INTO _migrations (id) VALUES (12);
|
|
522
|
+
`,
|
|
523
|
+
`
|
|
524
|
+
ALTER TABLE tasks ADD COLUMN recurrence_rule TEXT;
|
|
525
|
+
ALTER TABLE tasks ADD COLUMN recurrence_parent_id TEXT REFERENCES tasks(id) ON DELETE SET NULL;
|
|
526
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id);
|
|
527
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL;
|
|
528
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (13);
|
|
518
529
|
`
|
|
519
530
|
];
|
|
520
531
|
});
|
|
@@ -4789,6 +4800,8 @@ class VersionConflictError extends Error {
|
|
|
4789
4800
|
taskId;
|
|
4790
4801
|
expectedVersion;
|
|
4791
4802
|
actualVersion;
|
|
4803
|
+
static code = "VERSION_CONFLICT";
|
|
4804
|
+
static suggestion = "Fetch the task with get_task to get the current version before updating.";
|
|
4792
4805
|
constructor(taskId, expectedVersion, actualVersion) {
|
|
4793
4806
|
super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
|
|
4794
4807
|
this.taskId = taskId;
|
|
@@ -4800,14 +4813,30 @@ class VersionConflictError extends Error {
|
|
|
4800
4813
|
|
|
4801
4814
|
class TaskNotFoundError extends Error {
|
|
4802
4815
|
taskId;
|
|
4816
|
+
static code = "TASK_NOT_FOUND";
|
|
4817
|
+
static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
|
|
4803
4818
|
constructor(taskId) {
|
|
4804
4819
|
super(`Task not found: ${taskId}`);
|
|
4805
4820
|
this.taskId = taskId;
|
|
4806
4821
|
this.name = "TaskNotFoundError";
|
|
4807
4822
|
}
|
|
4808
4823
|
}
|
|
4824
|
+
|
|
4825
|
+
class ProjectNotFoundError extends Error {
|
|
4826
|
+
projectId;
|
|
4827
|
+
static code = "PROJECT_NOT_FOUND";
|
|
4828
|
+
static suggestion = "Use list_projects to see available projects.";
|
|
4829
|
+
constructor(projectId) {
|
|
4830
|
+
super(`Project not found: ${projectId}`);
|
|
4831
|
+
this.projectId = projectId;
|
|
4832
|
+
this.name = "ProjectNotFoundError";
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4809
4836
|
class PlanNotFoundError extends Error {
|
|
4810
4837
|
planId;
|
|
4838
|
+
static code = "PLAN_NOT_FOUND";
|
|
4839
|
+
static suggestion = "Use list_plans to see available plans.";
|
|
4811
4840
|
constructor(planId) {
|
|
4812
4841
|
super(`Plan not found: ${planId}`);
|
|
4813
4842
|
this.planId = planId;
|
|
@@ -4818,6 +4847,8 @@ class PlanNotFoundError extends Error {
|
|
|
4818
4847
|
class LockError extends Error {
|
|
4819
4848
|
taskId;
|
|
4820
4849
|
lockedBy;
|
|
4850
|
+
static code = "LOCK_ERROR";
|
|
4851
|
+
static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
|
|
4821
4852
|
constructor(taskId, lockedBy) {
|
|
4822
4853
|
super(`Task ${taskId} is locked by ${lockedBy}`);
|
|
4823
4854
|
this.taskId = taskId;
|
|
@@ -4825,8 +4856,22 @@ class LockError extends Error {
|
|
|
4825
4856
|
this.name = "LockError";
|
|
4826
4857
|
}
|
|
4827
4858
|
}
|
|
4859
|
+
|
|
4860
|
+
class AgentNotFoundError extends Error {
|
|
4861
|
+
agentId;
|
|
4862
|
+
static code = "AGENT_NOT_FOUND";
|
|
4863
|
+
static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
|
|
4864
|
+
constructor(agentId) {
|
|
4865
|
+
super(`Agent not found: ${agentId}`);
|
|
4866
|
+
this.agentId = agentId;
|
|
4867
|
+
this.name = "AgentNotFoundError";
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4828
4871
|
class TaskListNotFoundError extends Error {
|
|
4829
4872
|
taskListId;
|
|
4873
|
+
static code = "TASK_LIST_NOT_FOUND";
|
|
4874
|
+
static suggestion = "Use list_task_lists to see available lists.";
|
|
4830
4875
|
constructor(taskListId) {
|
|
4831
4876
|
super(`Task list not found: ${taskListId}`);
|
|
4832
4877
|
this.taskListId = taskListId;
|
|
@@ -4837,6 +4882,8 @@ class TaskListNotFoundError extends Error {
|
|
|
4837
4882
|
class DependencyCycleError extends Error {
|
|
4838
4883
|
taskId;
|
|
4839
4884
|
dependsOn;
|
|
4885
|
+
static code = "DEPENDENCY_CYCLE";
|
|
4886
|
+
static suggestion = "Check the dependency chain with get_task to avoid circular references.";
|
|
4840
4887
|
constructor(taskId, dependsOn) {
|
|
4841
4888
|
super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
|
|
4842
4889
|
this.taskId = taskId;
|
|
@@ -4848,6 +4895,8 @@ class DependencyCycleError extends Error {
|
|
|
4848
4895
|
class CompletionGuardError extends Error {
|
|
4849
4896
|
reason;
|
|
4850
4897
|
retryAfterSeconds;
|
|
4898
|
+
static code = "COMPLETION_BLOCKED";
|
|
4899
|
+
static suggestion = "Wait for the cooldown period, then retry.";
|
|
4851
4900
|
constructor(reason, retryAfterSeconds) {
|
|
4852
4901
|
super(reason);
|
|
4853
4902
|
this.reason = reason;
|
|
@@ -5074,6 +5123,95 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
5074
5123
|
|
|
5075
5124
|
// src/db/tasks.ts
|
|
5076
5125
|
init_audit();
|
|
5126
|
+
|
|
5127
|
+
// src/lib/recurrence.ts
|
|
5128
|
+
var DAY_NAMES = {
|
|
5129
|
+
sunday: 0,
|
|
5130
|
+
sun: 0,
|
|
5131
|
+
monday: 1,
|
|
5132
|
+
mon: 1,
|
|
5133
|
+
tuesday: 2,
|
|
5134
|
+
tue: 2,
|
|
5135
|
+
wednesday: 3,
|
|
5136
|
+
wed: 3,
|
|
5137
|
+
thursday: 4,
|
|
5138
|
+
thu: 4,
|
|
5139
|
+
friday: 5,
|
|
5140
|
+
fri: 5,
|
|
5141
|
+
saturday: 6,
|
|
5142
|
+
sat: 6
|
|
5143
|
+
};
|
|
5144
|
+
function parseRecurrenceRule(rule) {
|
|
5145
|
+
const normalized = rule.trim().toLowerCase();
|
|
5146
|
+
if (normalized === "every weekday" || normalized === "every weekdays") {
|
|
5147
|
+
return { type: "specific_days", days: [1, 2, 3, 4, 5] };
|
|
5148
|
+
}
|
|
5149
|
+
if (normalized === "every day" || normalized === "daily") {
|
|
5150
|
+
return { type: "interval", interval: 1, unit: "day" };
|
|
5151
|
+
}
|
|
5152
|
+
if (normalized === "every week" || normalized === "weekly") {
|
|
5153
|
+
return { type: "interval", interval: 1, unit: "week" };
|
|
5154
|
+
}
|
|
5155
|
+
if (normalized === "every month" || normalized === "monthly") {
|
|
5156
|
+
return { type: "interval", interval: 1, unit: "month" };
|
|
5157
|
+
}
|
|
5158
|
+
const intervalMatch = normalized.match(/^every\s+(\d+)\s+(day|week|month)s?$/);
|
|
5159
|
+
if (intervalMatch) {
|
|
5160
|
+
return {
|
|
5161
|
+
type: "interval",
|
|
5162
|
+
interval: parseInt(intervalMatch[1], 10),
|
|
5163
|
+
unit: intervalMatch[2]
|
|
5164
|
+
};
|
|
5165
|
+
}
|
|
5166
|
+
const daysMatch = normalized.match(/^every\s+(.+)$/);
|
|
5167
|
+
if (daysMatch) {
|
|
5168
|
+
const dayParts = daysMatch[1].split(/[,\s]+/).map((d) => d.trim()).filter(Boolean);
|
|
5169
|
+
const days = [];
|
|
5170
|
+
for (const part of dayParts) {
|
|
5171
|
+
const dayNum = DAY_NAMES[part];
|
|
5172
|
+
if (dayNum !== undefined) {
|
|
5173
|
+
days.push(dayNum);
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
5176
|
+
if (days.length > 0) {
|
|
5177
|
+
return { type: "specific_days", days: days.sort((a, b) => a - b) };
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
throw new Error(`Invalid recurrence rule: "${rule}". Supported formats: "every day", "every weekday", "every week", "every 2 weeks", "every month", "every N days/weeks/months", "every monday", "every mon,wed,fri"`);
|
|
5181
|
+
}
|
|
5182
|
+
function nextOccurrence(rule, from) {
|
|
5183
|
+
const parsed = parseRecurrenceRule(rule);
|
|
5184
|
+
const base = from || new Date;
|
|
5185
|
+
if (parsed.type === "interval") {
|
|
5186
|
+
const next = new Date(base);
|
|
5187
|
+
if (parsed.unit === "day") {
|
|
5188
|
+
next.setDate(next.getDate() + parsed.interval);
|
|
5189
|
+
} else if (parsed.unit === "week") {
|
|
5190
|
+
next.setDate(next.getDate() + parsed.interval * 7);
|
|
5191
|
+
} else if (parsed.unit === "month") {
|
|
5192
|
+
next.setMonth(next.getMonth() + parsed.interval);
|
|
5193
|
+
}
|
|
5194
|
+
return next.toISOString();
|
|
5195
|
+
}
|
|
5196
|
+
if (parsed.type === "specific_days") {
|
|
5197
|
+
const currentDay = base.getDay();
|
|
5198
|
+
const days = parsed.days;
|
|
5199
|
+
let daysToAdd = Infinity;
|
|
5200
|
+
for (const day of days) {
|
|
5201
|
+
let diff = day - currentDay;
|
|
5202
|
+
if (diff <= 0)
|
|
5203
|
+
diff += 7;
|
|
5204
|
+
if (diff < daysToAdd)
|
|
5205
|
+
daysToAdd = diff;
|
|
5206
|
+
}
|
|
5207
|
+
const next = new Date(base);
|
|
5208
|
+
next.setDate(next.getDate() + daysToAdd);
|
|
5209
|
+
return next.toISOString();
|
|
5210
|
+
}
|
|
5211
|
+
throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5214
|
+
// src/db/tasks.ts
|
|
5077
5215
|
function rowToTask(row) {
|
|
5078
5216
|
return {
|
|
5079
5217
|
...row,
|
|
@@ -5104,8 +5242,8 @@ function createTask(input, db) {
|
|
|
5104
5242
|
const tags = input.tags || [];
|
|
5105
5243
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
5106
5244
|
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
5107
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at)
|
|
5108
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5245
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id)
|
|
5246
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5109
5247
|
id,
|
|
5110
5248
|
shortId,
|
|
5111
5249
|
input.project_id || null,
|
|
@@ -5128,7 +5266,9 @@ function createTask(input, db) {
|
|
|
5128
5266
|
input.estimated_minutes || null,
|
|
5129
5267
|
input.requires_approval ? 1 : 0,
|
|
5130
5268
|
null,
|
|
5131
|
-
null
|
|
5269
|
+
null,
|
|
5270
|
+
input.recurrence_rule || null,
|
|
5271
|
+
input.recurrence_parent_id || null
|
|
5132
5272
|
]);
|
|
5133
5273
|
if (tags.length > 0) {
|
|
5134
5274
|
insertTaskTags(id, tags, d);
|
|
@@ -5228,6 +5368,11 @@ function listTasks(filter = {}, db) {
|
|
|
5228
5368
|
conditions.push("task_list_id = ?");
|
|
5229
5369
|
params.push(filter.task_list_id);
|
|
5230
5370
|
}
|
|
5371
|
+
if (filter.has_recurrence === true) {
|
|
5372
|
+
conditions.push("recurrence_rule IS NOT NULL");
|
|
5373
|
+
} else if (filter.has_recurrence === false) {
|
|
5374
|
+
conditions.push("recurrence_rule IS NULL");
|
|
5375
|
+
}
|
|
5231
5376
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5232
5377
|
let limitClause = "";
|
|
5233
5378
|
if (filter.limit) {
|
|
@@ -5243,6 +5388,69 @@ function listTasks(filter = {}, db) {
|
|
|
5243
5388
|
created_at DESC${limitClause}`).all(...params);
|
|
5244
5389
|
return rows.map(rowToTask);
|
|
5245
5390
|
}
|
|
5391
|
+
function countTasks(filter = {}, db) {
|
|
5392
|
+
const d = db || getDatabase();
|
|
5393
|
+
const conditions = [];
|
|
5394
|
+
const params = [];
|
|
5395
|
+
if (filter.project_id) {
|
|
5396
|
+
conditions.push("project_id = ?");
|
|
5397
|
+
params.push(filter.project_id);
|
|
5398
|
+
}
|
|
5399
|
+
if (filter.parent_id !== undefined) {
|
|
5400
|
+
if (filter.parent_id === null) {
|
|
5401
|
+
conditions.push("parent_id IS NULL");
|
|
5402
|
+
} else {
|
|
5403
|
+
conditions.push("parent_id = ?");
|
|
5404
|
+
params.push(filter.parent_id);
|
|
5405
|
+
}
|
|
5406
|
+
}
|
|
5407
|
+
if (filter.status) {
|
|
5408
|
+
if (Array.isArray(filter.status)) {
|
|
5409
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
5410
|
+
params.push(...filter.status);
|
|
5411
|
+
} else {
|
|
5412
|
+
conditions.push("status = ?");
|
|
5413
|
+
params.push(filter.status);
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
if (filter.priority) {
|
|
5417
|
+
if (Array.isArray(filter.priority)) {
|
|
5418
|
+
conditions.push(`priority IN (${filter.priority.map(() => "?").join(",")})`);
|
|
5419
|
+
params.push(...filter.priority);
|
|
5420
|
+
} else {
|
|
5421
|
+
conditions.push("priority = ?");
|
|
5422
|
+
params.push(filter.priority);
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
if (filter.assigned_to) {
|
|
5426
|
+
conditions.push("assigned_to = ?");
|
|
5427
|
+
params.push(filter.assigned_to);
|
|
5428
|
+
}
|
|
5429
|
+
if (filter.agent_id) {
|
|
5430
|
+
conditions.push("agent_id = ?");
|
|
5431
|
+
params.push(filter.agent_id);
|
|
5432
|
+
}
|
|
5433
|
+
if (filter.session_id) {
|
|
5434
|
+
conditions.push("session_id = ?");
|
|
5435
|
+
params.push(filter.session_id);
|
|
5436
|
+
}
|
|
5437
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
5438
|
+
const placeholders = filter.tags.map(() => "?").join(",");
|
|
5439
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
5440
|
+
params.push(...filter.tags);
|
|
5441
|
+
}
|
|
5442
|
+
if (filter.plan_id) {
|
|
5443
|
+
conditions.push("plan_id = ?");
|
|
5444
|
+
params.push(filter.plan_id);
|
|
5445
|
+
}
|
|
5446
|
+
if (filter.task_list_id) {
|
|
5447
|
+
conditions.push("task_list_id = ?");
|
|
5448
|
+
params.push(filter.task_list_id);
|
|
5449
|
+
}
|
|
5450
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5451
|
+
const row = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
5452
|
+
return row.count;
|
|
5453
|
+
}
|
|
5246
5454
|
function updateTask(id, input, db) {
|
|
5247
5455
|
const d = db || getDatabase();
|
|
5248
5456
|
const task = getTask(id, d);
|
|
@@ -5314,6 +5522,10 @@ function updateTask(id, input, db) {
|
|
|
5314
5522
|
sets.push("approved_at = ?");
|
|
5315
5523
|
params.push(now());
|
|
5316
5524
|
}
|
|
5525
|
+
if (input.recurrence_rule !== undefined) {
|
|
5526
|
+
sets.push("recurrence_rule = ?");
|
|
5527
|
+
params.push(input.recurrence_rule);
|
|
5528
|
+
}
|
|
5317
5529
|
params.push(id, input.version);
|
|
5318
5530
|
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
5319
5531
|
if (result.changes === 0) {
|
|
@@ -5387,7 +5599,7 @@ function startTask(id, agentId, db) {
|
|
|
5387
5599
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
5388
5600
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
5389
5601
|
}
|
|
5390
|
-
function completeTask(id, agentId, db,
|
|
5602
|
+
function completeTask(id, agentId, db, options) {
|
|
5391
5603
|
const d = db || getDatabase();
|
|
5392
5604
|
const task = getTask(id, d);
|
|
5393
5605
|
if (!task)
|
|
@@ -5396,7 +5608,9 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
5396
5608
|
throw new LockError(id, task.locked_by);
|
|
5397
5609
|
}
|
|
5398
5610
|
checkCompletionGuard(task, agentId || null, d);
|
|
5399
|
-
|
|
5611
|
+
const evidence = options ? { files_changed: options.files_changed, test_results: options.test_results, commit_hash: options.commit_hash, notes: options.notes } : undefined;
|
|
5612
|
+
const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes);
|
|
5613
|
+
if (hasEvidence) {
|
|
5400
5614
|
const meta2 = { ...task.metadata, _evidence: evidence };
|
|
5401
5615
|
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
|
|
5402
5616
|
}
|
|
@@ -5404,7 +5618,14 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
5404
5618
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
5405
5619
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
5406
5620
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
5407
|
-
|
|
5621
|
+
let spawnedTask = null;
|
|
5622
|
+
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
5623
|
+
spawnedTask = spawnNextRecurrence(task, d);
|
|
5624
|
+
}
|
|
5625
|
+
const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
|
|
5626
|
+
if (spawnedTask) {
|
|
5627
|
+
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
5628
|
+
}
|
|
5408
5629
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
5409
5630
|
}
|
|
5410
5631
|
function lockTask(id, agentId, db) {
|
|
@@ -5467,6 +5688,266 @@ function getTaskDependencies(taskId, db) {
|
|
|
5467
5688
|
const d = db || getDatabase();
|
|
5468
5689
|
return d.query("SELECT * FROM task_dependencies WHERE task_id = ?").all(taskId);
|
|
5469
5690
|
}
|
|
5691
|
+
function cloneTask(taskId, overrides, db) {
|
|
5692
|
+
const d = db || getDatabase();
|
|
5693
|
+
const source = getTask(taskId, d);
|
|
5694
|
+
if (!source)
|
|
5695
|
+
throw new TaskNotFoundError(taskId);
|
|
5696
|
+
const input = {
|
|
5697
|
+
title: overrides?.title ?? source.title,
|
|
5698
|
+
description: overrides?.description ?? source.description ?? undefined,
|
|
5699
|
+
priority: overrides?.priority ?? source.priority,
|
|
5700
|
+
project_id: overrides?.project_id ?? source.project_id ?? undefined,
|
|
5701
|
+
parent_id: overrides?.parent_id ?? source.parent_id ?? undefined,
|
|
5702
|
+
plan_id: overrides?.plan_id ?? source.plan_id ?? undefined,
|
|
5703
|
+
task_list_id: overrides?.task_list_id ?? source.task_list_id ?? undefined,
|
|
5704
|
+
status: overrides?.status ?? "pending",
|
|
5705
|
+
agent_id: overrides?.agent_id ?? source.agent_id ?? undefined,
|
|
5706
|
+
assigned_to: overrides?.assigned_to ?? source.assigned_to ?? undefined,
|
|
5707
|
+
tags: overrides?.tags ?? source.tags,
|
|
5708
|
+
metadata: overrides?.metadata ?? source.metadata,
|
|
5709
|
+
estimated_minutes: overrides?.estimated_minutes ?? source.estimated_minutes ?? undefined,
|
|
5710
|
+
recurrence_rule: overrides?.recurrence_rule ?? source.recurrence_rule ?? undefined
|
|
5711
|
+
};
|
|
5712
|
+
return createTask(input, d);
|
|
5713
|
+
}
|
|
5714
|
+
function getTaskGraph(taskId, direction = "both", db) {
|
|
5715
|
+
const d = db || getDatabase();
|
|
5716
|
+
const task = getTask(taskId, d);
|
|
5717
|
+
if (!task)
|
|
5718
|
+
throw new TaskNotFoundError(taskId);
|
|
5719
|
+
function toNode(t) {
|
|
5720
|
+
const deps = getTaskDependencies(t.id, d);
|
|
5721
|
+
const hasUnfinishedDeps = deps.some((dep) => {
|
|
5722
|
+
const depTask = getTask(dep.depends_on, d);
|
|
5723
|
+
return depTask && depTask.status !== "completed";
|
|
5724
|
+
});
|
|
5725
|
+
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
5726
|
+
}
|
|
5727
|
+
function buildUp(id, visited) {
|
|
5728
|
+
if (visited.has(id))
|
|
5729
|
+
return [];
|
|
5730
|
+
visited.add(id);
|
|
5731
|
+
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
5732
|
+
return deps.map((dep) => {
|
|
5733
|
+
const depTask = getTask(dep.depends_on, d);
|
|
5734
|
+
if (!depTask)
|
|
5735
|
+
return null;
|
|
5736
|
+
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
5737
|
+
}).filter(Boolean);
|
|
5738
|
+
}
|
|
5739
|
+
function buildDown(id, visited) {
|
|
5740
|
+
if (visited.has(id))
|
|
5741
|
+
return [];
|
|
5742
|
+
visited.add(id);
|
|
5743
|
+
const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
|
|
5744
|
+
return dependents.map((dep) => {
|
|
5745
|
+
const depTask = getTask(dep.task_id, d);
|
|
5746
|
+
if (!depTask)
|
|
5747
|
+
return null;
|
|
5748
|
+
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
5749
|
+
}).filter(Boolean);
|
|
5750
|
+
}
|
|
5751
|
+
const rootNode = toNode(task);
|
|
5752
|
+
const depends_on = direction === "up" || direction === "both" ? buildUp(taskId, new Set) : [];
|
|
5753
|
+
const blocks = direction === "down" || direction === "both" ? buildDown(taskId, new Set) : [];
|
|
5754
|
+
return { task: rootNode, depends_on, blocks };
|
|
5755
|
+
}
|
|
5756
|
+
function moveTask(taskId, target, db) {
|
|
5757
|
+
const d = db || getDatabase();
|
|
5758
|
+
const task = getTask(taskId, d);
|
|
5759
|
+
if (!task)
|
|
5760
|
+
throw new TaskNotFoundError(taskId);
|
|
5761
|
+
const sets = ["updated_at = ?", "version = version + 1"];
|
|
5762
|
+
const params = [now()];
|
|
5763
|
+
if (target.task_list_id !== undefined) {
|
|
5764
|
+
sets.push("task_list_id = ?");
|
|
5765
|
+
params.push(target.task_list_id);
|
|
5766
|
+
}
|
|
5767
|
+
if (target.project_id !== undefined) {
|
|
5768
|
+
sets.push("project_id = ?");
|
|
5769
|
+
params.push(target.project_id);
|
|
5770
|
+
}
|
|
5771
|
+
if (target.plan_id !== undefined) {
|
|
5772
|
+
sets.push("plan_id = ?");
|
|
5773
|
+
params.push(target.plan_id);
|
|
5774
|
+
}
|
|
5775
|
+
params.push(taskId);
|
|
5776
|
+
d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5777
|
+
return getTask(taskId, d);
|
|
5778
|
+
}
|
|
5779
|
+
function spawnNextRecurrence(completedTask, db) {
|
|
5780
|
+
const dueAt = nextOccurrence(completedTask.recurrence_rule, new Date);
|
|
5781
|
+
let title = completedTask.title;
|
|
5782
|
+
if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
|
|
5783
|
+
title = title.slice(completedTask.short_id.length + 2);
|
|
5784
|
+
}
|
|
5785
|
+
const recurrenceParentId = completedTask.recurrence_parent_id || completedTask.id;
|
|
5786
|
+
return createTask({
|
|
5787
|
+
title,
|
|
5788
|
+
description: completedTask.description ?? undefined,
|
|
5789
|
+
priority: completedTask.priority,
|
|
5790
|
+
project_id: completedTask.project_id ?? undefined,
|
|
5791
|
+
task_list_id: completedTask.task_list_id ?? undefined,
|
|
5792
|
+
plan_id: completedTask.plan_id ?? undefined,
|
|
5793
|
+
assigned_to: completedTask.assigned_to ?? undefined,
|
|
5794
|
+
tags: completedTask.tags,
|
|
5795
|
+
metadata: completedTask.metadata,
|
|
5796
|
+
estimated_minutes: completedTask.estimated_minutes ?? undefined,
|
|
5797
|
+
recurrence_rule: completedTask.recurrence_rule,
|
|
5798
|
+
recurrence_parent_id: recurrenceParentId,
|
|
5799
|
+
due_at: dueAt
|
|
5800
|
+
}, db);
|
|
5801
|
+
}
|
|
5802
|
+
function claimNextTask(agentId, filters, db) {
|
|
5803
|
+
const d = db || getDatabase();
|
|
5804
|
+
const tx = d.transaction(() => {
|
|
5805
|
+
const task = getNextTask(agentId, filters, d);
|
|
5806
|
+
if (!task)
|
|
5807
|
+
return null;
|
|
5808
|
+
return startTask(task.id, agentId, d);
|
|
5809
|
+
});
|
|
5810
|
+
return tx();
|
|
5811
|
+
}
|
|
5812
|
+
function getNextTask(agentId, filters, db) {
|
|
5813
|
+
const d = db || getDatabase();
|
|
5814
|
+
clearExpiredLocks(d);
|
|
5815
|
+
const conditions = ["status = 'pending'", "(locked_by IS NULL OR locked_at < ?)"];
|
|
5816
|
+
const params = [lockExpiryCutoff()];
|
|
5817
|
+
if (filters?.project_id) {
|
|
5818
|
+
conditions.push("project_id = ?");
|
|
5819
|
+
params.push(filters.project_id);
|
|
5820
|
+
}
|
|
5821
|
+
if (filters?.task_list_id) {
|
|
5822
|
+
conditions.push("task_list_id = ?");
|
|
5823
|
+
params.push(filters.task_list_id);
|
|
5824
|
+
}
|
|
5825
|
+
if (filters?.plan_id) {
|
|
5826
|
+
conditions.push("plan_id = ?");
|
|
5827
|
+
params.push(filters.plan_id);
|
|
5828
|
+
}
|
|
5829
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
5830
|
+
const placeholders = filters.tags.map(() => "?").join(",");
|
|
5831
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
5832
|
+
params.push(...filters.tags);
|
|
5833
|
+
}
|
|
5834
|
+
conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
|
|
5835
|
+
const where = conditions.join(" AND ");
|
|
5836
|
+
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
5837
|
+
if (agentId) {
|
|
5838
|
+
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
5839
|
+
params.push(agentId);
|
|
5840
|
+
}
|
|
5841
|
+
sql += `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END, created_at ASC LIMIT 1`;
|
|
5842
|
+
const row = d.query(sql).get(...params);
|
|
5843
|
+
return row ? rowToTask(row) : null;
|
|
5844
|
+
}
|
|
5845
|
+
function getActiveWork(filters, db) {
|
|
5846
|
+
const d = db || getDatabase();
|
|
5847
|
+
clearExpiredLocks(d);
|
|
5848
|
+
const conditions = ["status = 'in_progress'"];
|
|
5849
|
+
const params = [];
|
|
5850
|
+
if (filters?.project_id) {
|
|
5851
|
+
conditions.push("project_id = ?");
|
|
5852
|
+
params.push(filters.project_id);
|
|
5853
|
+
}
|
|
5854
|
+
if (filters?.task_list_id) {
|
|
5855
|
+
conditions.push("task_list_id = ?");
|
|
5856
|
+
params.push(filters.task_list_id);
|
|
5857
|
+
}
|
|
5858
|
+
const where = conditions.join(" AND ");
|
|
5859
|
+
const rows = d.query(`SELECT id, short_id, title, priority, assigned_to, locked_by, locked_at, updated_at FROM tasks WHERE ${where} ORDER BY
|
|
5860
|
+
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
5861
|
+
updated_at DESC`).all(...params);
|
|
5862
|
+
return rows;
|
|
5863
|
+
}
|
|
5864
|
+
function getTasksChangedSince(since, filters, db) {
|
|
5865
|
+
const d = db || getDatabase();
|
|
5866
|
+
const conditions = ["updated_at > ?"];
|
|
5867
|
+
const params = [since];
|
|
5868
|
+
if (filters?.project_id) {
|
|
5869
|
+
conditions.push("project_id = ?");
|
|
5870
|
+
params.push(filters.project_id);
|
|
5871
|
+
}
|
|
5872
|
+
if (filters?.task_list_id) {
|
|
5873
|
+
conditions.push("task_list_id = ?");
|
|
5874
|
+
params.push(filters.task_list_id);
|
|
5875
|
+
}
|
|
5876
|
+
const where = conditions.join(" AND ");
|
|
5877
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at DESC`).all(...params);
|
|
5878
|
+
return rows.map(rowToTask);
|
|
5879
|
+
}
|
|
5880
|
+
function failTask(id, agentId, reason, options, db) {
|
|
5881
|
+
const d = db || getDatabase();
|
|
5882
|
+
const task = getTask(id, d);
|
|
5883
|
+
if (!task)
|
|
5884
|
+
throw new TaskNotFoundError(id);
|
|
5885
|
+
const meta = {
|
|
5886
|
+
...task.metadata,
|
|
5887
|
+
_failure: {
|
|
5888
|
+
reason: reason || "Unknown failure",
|
|
5889
|
+
error_code: options?.error_code || null,
|
|
5890
|
+
failed_by: agentId || null,
|
|
5891
|
+
failed_at: now(),
|
|
5892
|
+
retry_requested: options?.retry || false
|
|
5893
|
+
}
|
|
5894
|
+
};
|
|
5895
|
+
const timestamp = now();
|
|
5896
|
+
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
5897
|
+
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
5898
|
+
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
5899
|
+
const failedTask = {
|
|
5900
|
+
...task,
|
|
5901
|
+
status: "failed",
|
|
5902
|
+
locked_by: null,
|
|
5903
|
+
locked_at: null,
|
|
5904
|
+
metadata: meta,
|
|
5905
|
+
version: task.version + 1,
|
|
5906
|
+
updated_at: timestamp
|
|
5907
|
+
};
|
|
5908
|
+
let retryTask;
|
|
5909
|
+
if (options?.retry) {
|
|
5910
|
+
let title = task.title;
|
|
5911
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
5912
|
+
title = title.slice(task.short_id.length + 2);
|
|
5913
|
+
}
|
|
5914
|
+
retryTask = createTask({
|
|
5915
|
+
title,
|
|
5916
|
+
description: task.description ?? undefined,
|
|
5917
|
+
priority: task.priority,
|
|
5918
|
+
project_id: task.project_id ?? undefined,
|
|
5919
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
5920
|
+
plan_id: task.plan_id ?? undefined,
|
|
5921
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
5922
|
+
tags: task.tags,
|
|
5923
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
|
|
5924
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
5925
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
5926
|
+
due_at: options.retry_after || task.due_at || undefined
|
|
5927
|
+
}, d);
|
|
5928
|
+
}
|
|
5929
|
+
return { task: failedTask, retryTask };
|
|
5930
|
+
}
|
|
5931
|
+
function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
5932
|
+
const d = db || getDatabase();
|
|
5933
|
+
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
5934
|
+
const conditions = [
|
|
5935
|
+
"status = 'in_progress'",
|
|
5936
|
+
"(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
|
|
5937
|
+
];
|
|
5938
|
+
const params = [cutoff, cutoff];
|
|
5939
|
+
if (filters?.project_id) {
|
|
5940
|
+
conditions.push("project_id = ?");
|
|
5941
|
+
params.push(filters.project_id);
|
|
5942
|
+
}
|
|
5943
|
+
if (filters?.task_list_id) {
|
|
5944
|
+
conditions.push("task_list_id = ?");
|
|
5945
|
+
params.push(filters.task_list_id);
|
|
5946
|
+
}
|
|
5947
|
+
const where = conditions.join(" AND ");
|
|
5948
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
5949
|
+
return rows.map(rowToTask);
|
|
5950
|
+
}
|
|
5470
5951
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
5471
5952
|
const visited = new Set;
|
|
5472
5953
|
const queue = [dependsOn];
|
|
@@ -5484,6 +5965,91 @@ function wouldCreateCycle(taskId, dependsOn, db) {
|
|
|
5484
5965
|
}
|
|
5485
5966
|
return false;
|
|
5486
5967
|
}
|
|
5968
|
+
function getTaskStats(filters, db) {
|
|
5969
|
+
const d = db || getDatabase();
|
|
5970
|
+
const conditions = [];
|
|
5971
|
+
const params = [];
|
|
5972
|
+
if (filters?.project_id) {
|
|
5973
|
+
conditions.push("project_id = ?");
|
|
5974
|
+
params.push(filters.project_id);
|
|
5975
|
+
}
|
|
5976
|
+
if (filters?.task_list_id) {
|
|
5977
|
+
conditions.push("task_list_id = ?");
|
|
5978
|
+
params.push(filters.task_list_id);
|
|
5979
|
+
}
|
|
5980
|
+
if (filters?.agent_id) {
|
|
5981
|
+
conditions.push("(agent_id = ? OR assigned_to = ?)");
|
|
5982
|
+
params.push(filters.agent_id, filters.agent_id);
|
|
5983
|
+
}
|
|
5984
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5985
|
+
const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
5986
|
+
const statusRows = d.query(`SELECT status, COUNT(*) as count FROM tasks ${where} GROUP BY status`).all(...params);
|
|
5987
|
+
const by_status = {};
|
|
5988
|
+
for (const r of statusRows)
|
|
5989
|
+
by_status[r.status] = r.count;
|
|
5990
|
+
const priorityRows = d.query(`SELECT priority, COUNT(*) as count FROM tasks ${where} GROUP BY priority`).all(...params);
|
|
5991
|
+
const by_priority = {};
|
|
5992
|
+
for (const r of priorityRows)
|
|
5993
|
+
by_priority[r.priority] = r.count;
|
|
5994
|
+
const agentRows = d.query(`SELECT COALESCE(assigned_to, agent_id, 'unassigned') as agent, COUNT(*) as count FROM tasks ${where} GROUP BY agent`).all(...params);
|
|
5995
|
+
const by_agent = {};
|
|
5996
|
+
for (const r of agentRows)
|
|
5997
|
+
by_agent[r.agent] = r.count;
|
|
5998
|
+
const completed = by_status["completed"] || 0;
|
|
5999
|
+
const completion_rate = totalRow.count > 0 ? Math.round(completed / totalRow.count * 100) : 0;
|
|
6000
|
+
return { total: totalRow.count, by_status, by_priority, completion_rate, by_agent };
|
|
6001
|
+
}
|
|
6002
|
+
function bulkCreateTasks(inputs, db) {
|
|
6003
|
+
const d = db || getDatabase();
|
|
6004
|
+
const tempIdToRealId = new Map;
|
|
6005
|
+
const created = [];
|
|
6006
|
+
const tx = d.transaction(() => {
|
|
6007
|
+
for (const input of inputs) {
|
|
6008
|
+
const { temp_id, depends_on_temp_ids: _deps, ...createInput } = input;
|
|
6009
|
+
const task = createTask(createInput, d);
|
|
6010
|
+
if (temp_id)
|
|
6011
|
+
tempIdToRealId.set(temp_id, task.id);
|
|
6012
|
+
created.push({ temp_id: temp_id || null, id: task.id, short_id: task.short_id, title: task.title });
|
|
6013
|
+
}
|
|
6014
|
+
for (const input of inputs) {
|
|
6015
|
+
if (input.depends_on_temp_ids && input.depends_on_temp_ids.length > 0) {
|
|
6016
|
+
const taskId = input.temp_id ? tempIdToRealId.get(input.temp_id) : null;
|
|
6017
|
+
if (!taskId)
|
|
6018
|
+
continue;
|
|
6019
|
+
for (const depTempId of input.depends_on_temp_ids) {
|
|
6020
|
+
const depRealId = tempIdToRealId.get(depTempId);
|
|
6021
|
+
if (depRealId) {
|
|
6022
|
+
addDependency(taskId, depRealId, d);
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
});
|
|
6028
|
+
tx();
|
|
6029
|
+
return { created };
|
|
6030
|
+
}
|
|
6031
|
+
function bulkUpdateTasks(taskIds, updates, db) {
|
|
6032
|
+
const d = db || getDatabase();
|
|
6033
|
+
let updated = 0;
|
|
6034
|
+
const failed = [];
|
|
6035
|
+
const tx = d.transaction(() => {
|
|
6036
|
+
for (const id of taskIds) {
|
|
6037
|
+
try {
|
|
6038
|
+
const task = getTask(id, d);
|
|
6039
|
+
if (!task) {
|
|
6040
|
+
failed.push({ id, error: "Task not found" });
|
|
6041
|
+
continue;
|
|
6042
|
+
}
|
|
6043
|
+
updateTask(id, { ...updates, version: task.version }, d);
|
|
6044
|
+
updated++;
|
|
6045
|
+
} catch (e) {
|
|
6046
|
+
failed.push({ id, error: e instanceof Error ? e.message : String(e) });
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
});
|
|
6050
|
+
tx();
|
|
6051
|
+
return { updated, failed };
|
|
6052
|
+
}
|
|
5487
6053
|
|
|
5488
6054
|
// src/db/comments.ts
|
|
5489
6055
|
init_database();
|
|
@@ -5657,19 +6223,64 @@ function rowToTask2(row) {
|
|
|
5657
6223
|
requires_approval: Boolean(row.requires_approval)
|
|
5658
6224
|
};
|
|
5659
6225
|
}
|
|
5660
|
-
function searchTasks(
|
|
6226
|
+
function searchTasks(options, projectId, taskListId, db) {
|
|
6227
|
+
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
5661
6228
|
const d = db || getDatabase();
|
|
5662
6229
|
clearExpiredLocks(d);
|
|
5663
|
-
const pattern = `%${query}%`;
|
|
6230
|
+
const pattern = `%${opts.query}%`;
|
|
5664
6231
|
let sql = `SELECT * FROM tasks WHERE (title LIKE ? OR description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = tasks.id AND tag LIKE ?))`;
|
|
5665
6232
|
const params = [pattern, pattern, pattern];
|
|
5666
|
-
if (
|
|
6233
|
+
if (opts.project_id) {
|
|
5667
6234
|
sql += " AND project_id = ?";
|
|
5668
|
-
params.push(
|
|
6235
|
+
params.push(opts.project_id);
|
|
5669
6236
|
}
|
|
5670
|
-
if (
|
|
6237
|
+
if (opts.task_list_id) {
|
|
5671
6238
|
sql += " AND task_list_id = ?";
|
|
5672
|
-
params.push(
|
|
6239
|
+
params.push(opts.task_list_id);
|
|
6240
|
+
}
|
|
6241
|
+
if (opts.status) {
|
|
6242
|
+
if (Array.isArray(opts.status)) {
|
|
6243
|
+
sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
|
|
6244
|
+
params.push(...opts.status);
|
|
6245
|
+
} else {
|
|
6246
|
+
sql += " AND status = ?";
|
|
6247
|
+
params.push(opts.status);
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
if (opts.priority) {
|
|
6251
|
+
if (Array.isArray(opts.priority)) {
|
|
6252
|
+
sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
6253
|
+
params.push(...opts.priority);
|
|
6254
|
+
} else {
|
|
6255
|
+
sql += " AND priority = ?";
|
|
6256
|
+
params.push(opts.priority);
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
if (opts.assigned_to) {
|
|
6260
|
+
sql += " AND assigned_to = ?";
|
|
6261
|
+
params.push(opts.assigned_to);
|
|
6262
|
+
}
|
|
6263
|
+
if (opts.agent_id) {
|
|
6264
|
+
sql += " AND agent_id = ?";
|
|
6265
|
+
params.push(opts.agent_id);
|
|
6266
|
+
}
|
|
6267
|
+
if (opts.created_after) {
|
|
6268
|
+
sql += " AND created_at > ?";
|
|
6269
|
+
params.push(opts.created_after);
|
|
6270
|
+
}
|
|
6271
|
+
if (opts.updated_after) {
|
|
6272
|
+
sql += " AND updated_at > ?";
|
|
6273
|
+
params.push(opts.updated_after);
|
|
6274
|
+
}
|
|
6275
|
+
if (opts.has_dependencies === true) {
|
|
6276
|
+
sql += " AND id IN (SELECT task_id FROM task_dependencies)";
|
|
6277
|
+
} else if (opts.has_dependencies === false) {
|
|
6278
|
+
sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
6279
|
+
}
|
|
6280
|
+
if (opts.is_blocked === true) {
|
|
6281
|
+
sql += " AND id IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
6282
|
+
} else if (opts.is_blocked === false) {
|
|
6283
|
+
sql += " AND id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
5673
6284
|
}
|
|
5674
6285
|
sql += ` ORDER BY
|
|
5675
6286
|
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
@@ -6199,28 +6810,41 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
6199
6810
|
init_database();
|
|
6200
6811
|
var server = new McpServer({
|
|
6201
6812
|
name: "todos",
|
|
6202
|
-
version: "0.9.
|
|
6813
|
+
version: "0.9.35"
|
|
6203
6814
|
});
|
|
6204
6815
|
function formatError(error) {
|
|
6205
|
-
if (error instanceof VersionConflictError)
|
|
6206
|
-
return
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
if (error instanceof
|
|
6212
|
-
return
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6816
|
+
if (error instanceof VersionConflictError) {
|
|
6817
|
+
return JSON.stringify({ code: VersionConflictError.code, message: error.message, suggestion: VersionConflictError.suggestion });
|
|
6818
|
+
}
|
|
6819
|
+
if (error instanceof TaskNotFoundError) {
|
|
6820
|
+
return JSON.stringify({ code: TaskNotFoundError.code, message: error.message, suggestion: TaskNotFoundError.suggestion });
|
|
6821
|
+
}
|
|
6822
|
+
if (error instanceof ProjectNotFoundError) {
|
|
6823
|
+
return JSON.stringify({ code: ProjectNotFoundError.code, message: error.message, suggestion: ProjectNotFoundError.suggestion });
|
|
6824
|
+
}
|
|
6825
|
+
if (error instanceof PlanNotFoundError) {
|
|
6826
|
+
return JSON.stringify({ code: PlanNotFoundError.code, message: error.message, suggestion: PlanNotFoundError.suggestion });
|
|
6827
|
+
}
|
|
6828
|
+
if (error instanceof TaskListNotFoundError) {
|
|
6829
|
+
return JSON.stringify({ code: TaskListNotFoundError.code, message: error.message, suggestion: TaskListNotFoundError.suggestion });
|
|
6830
|
+
}
|
|
6831
|
+
if (error instanceof LockError) {
|
|
6832
|
+
return JSON.stringify({ code: LockError.code, message: error.message, suggestion: LockError.suggestion });
|
|
6833
|
+
}
|
|
6834
|
+
if (error instanceof AgentNotFoundError) {
|
|
6835
|
+
return JSON.stringify({ code: AgentNotFoundError.code, message: error.message, suggestion: AgentNotFoundError.suggestion });
|
|
6836
|
+
}
|
|
6837
|
+
if (error instanceof DependencyCycleError) {
|
|
6838
|
+
return JSON.stringify({ code: DependencyCycleError.code, message: error.message, suggestion: DependencyCycleError.suggestion });
|
|
6839
|
+
}
|
|
6217
6840
|
if (error instanceof CompletionGuardError) {
|
|
6218
|
-
const retry = error.retryAfterSeconds ?
|
|
6219
|
-
return
|
|
6841
|
+
const retry = error.retryAfterSeconds ? { retryAfterSeconds: error.retryAfterSeconds } : {};
|
|
6842
|
+
return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
|
|
6220
6843
|
}
|
|
6221
|
-
if (error instanceof Error)
|
|
6222
|
-
return error.message;
|
|
6223
|
-
|
|
6844
|
+
if (error instanceof Error) {
|
|
6845
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: error.message });
|
|
6846
|
+
}
|
|
6847
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
|
|
6224
6848
|
}
|
|
6225
6849
|
function resolveId(partialId, table = "tasks") {
|
|
6226
6850
|
const db = getDatabase();
|
|
@@ -6243,7 +6867,8 @@ function formatTask(task) {
|
|
|
6243
6867
|
const id = task.short_id || task.id.slice(0, 8);
|
|
6244
6868
|
const assigned = task.assigned_to ? ` -> ${task.assigned_to}` : "";
|
|
6245
6869
|
const lock = task.locked_by ? ` [locked:${task.locked_by}]` : "";
|
|
6246
|
-
|
|
6870
|
+
const recur = task.recurrence_rule ? ` [\u21BB]` : "";
|
|
6871
|
+
return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
|
|
6247
6872
|
}
|
|
6248
6873
|
function formatTaskDetail(task) {
|
|
6249
6874
|
const parts = [
|
|
@@ -6268,6 +6893,10 @@ function formatTaskDetail(task) {
|
|
|
6268
6893
|
parts.push(`Plan: ${task.plan_id}`);
|
|
6269
6894
|
if (task.tags.length > 0)
|
|
6270
6895
|
parts.push(`Tags: ${task.tags.join(", ")}`);
|
|
6896
|
+
if (task.recurrence_rule)
|
|
6897
|
+
parts.push(`Recurrence: ${task.recurrence_rule}`);
|
|
6898
|
+
if (task.recurrence_parent_id)
|
|
6899
|
+
parts.push(`Recurrence parent: ${task.recurrence_parent_id}`);
|
|
6271
6900
|
parts.push(`Version: ${task.version}`);
|
|
6272
6901
|
parts.push(`Created: ${task.created_at}`);
|
|
6273
6902
|
if (task.completed_at)
|
|
@@ -6291,7 +6920,8 @@ server.tool("create_task", "Create a new task", {
|
|
|
6291
6920
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
6292
6921
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
6293
6922
|
estimated_minutes: exports_external.number().optional(),
|
|
6294
|
-
requires_approval: exports_external.boolean().optional()
|
|
6923
|
+
requires_approval: exports_external.boolean().optional(),
|
|
6924
|
+
recurrence_rule: exports_external.string().optional()
|
|
6295
6925
|
}, async (params) => {
|
|
6296
6926
|
try {
|
|
6297
6927
|
const resolved = { ...params };
|
|
@@ -6309,7 +6939,7 @@ server.tool("create_task", "Create a new task", {
|
|
|
6309
6939
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6310
6940
|
}
|
|
6311
6941
|
});
|
|
6312
|
-
server.tool("list_tasks", "List tasks with optional filters", {
|
|
6942
|
+
server.tool("list_tasks", "List tasks with optional filters and pagination.", {
|
|
6313
6943
|
project_id: exports_external.string().optional(),
|
|
6314
6944
|
status: exports_external.union([
|
|
6315
6945
|
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
@@ -6322,7 +6952,10 @@ server.tool("list_tasks", "List tasks with optional filters", {
|
|
|
6322
6952
|
assigned_to: exports_external.string().optional(),
|
|
6323
6953
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
6324
6954
|
plan_id: exports_external.string().optional(),
|
|
6325
|
-
task_list_id: exports_external.string().optional()
|
|
6955
|
+
task_list_id: exports_external.string().optional(),
|
|
6956
|
+
has_recurrence: exports_external.boolean().optional(),
|
|
6957
|
+
limit: exports_external.number().optional(),
|
|
6958
|
+
offset: exports_external.number().optional()
|
|
6326
6959
|
}, async (params) => {
|
|
6327
6960
|
try {
|
|
6328
6961
|
const resolved = { ...params };
|
|
@@ -6333,8 +6966,10 @@ server.tool("list_tasks", "List tasks with optional filters", {
|
|
|
6333
6966
|
if (resolved.task_list_id)
|
|
6334
6967
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
6335
6968
|
const tasks = listTasks(resolved);
|
|
6969
|
+
const { limit: _limit, offset: _offset, ...countFilter } = resolved;
|
|
6970
|
+
const total = countTasks(countFilter);
|
|
6336
6971
|
if (tasks.length === 0) {
|
|
6337
|
-
return { content: [{ type: "text", text: "No tasks found." }] };
|
|
6972
|
+
return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
|
|
6338
6973
|
}
|
|
6339
6974
|
const text = tasks.map((t) => {
|
|
6340
6975
|
const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
|
|
@@ -6342,13 +6977,15 @@ server.tool("list_tasks", "List tasks with optional filters", {
|
|
|
6342
6977
|
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}`;
|
|
6343
6978
|
}).join(`
|
|
6344
6979
|
`);
|
|
6980
|
+
const pagination = resolved.limit ? `
|
|
6981
|
+
(showing ${tasks.length} of ${total}, offset: ${resolved.offset || 0})` : "";
|
|
6345
6982
|
return { content: [{ type: "text", text: `${tasks.length} task(s):
|
|
6346
|
-
${text}` }] };
|
|
6983
|
+
${text}${pagination}` }] };
|
|
6347
6984
|
} catch (e) {
|
|
6348
6985
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6349
6986
|
}
|
|
6350
6987
|
});
|
|
6351
|
-
server.tool("get_task", "Get full task details with
|
|
6988
|
+
server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
|
|
6352
6989
|
id: exports_external.string()
|
|
6353
6990
|
}, async ({ id }) => {
|
|
6354
6991
|
try {
|
|
@@ -6417,7 +7054,7 @@ server.tool("update_task", "Update task fields. Version required for optimistic
|
|
|
6417
7054
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6418
7055
|
}
|
|
6419
7056
|
});
|
|
6420
|
-
server.tool("delete_task", "Delete a task permanently", {
|
|
7057
|
+
server.tool("delete_task", "Delete a task permanently. Subtasks cascade-deleted.", {
|
|
6421
7058
|
id: exports_external.string()
|
|
6422
7059
|
}, async ({ id }) => {
|
|
6423
7060
|
try {
|
|
@@ -6433,7 +7070,7 @@ server.tool("delete_task", "Delete a task permanently", {
|
|
|
6433
7070
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6434
7071
|
}
|
|
6435
7072
|
});
|
|
6436
|
-
server.tool("start_task", "Claim, lock, and set task
|
|
7073
|
+
server.tool("start_task", "Claim, lock, and set task to in_progress.", {
|
|
6437
7074
|
id: exports_external.string(),
|
|
6438
7075
|
agent_id: exports_external.string()
|
|
6439
7076
|
}, async ({ id, agent_id }) => {
|
|
@@ -6445,19 +7082,26 @@ server.tool("start_task", "Claim, lock, and set task status to in_progress.", {
|
|
|
6445
7082
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6446
7083
|
}
|
|
6447
7084
|
});
|
|
6448
|
-
server.tool("complete_task", "
|
|
7085
|
+
server.tool("complete_task", "Complete a task. For recurring tasks, auto-spawns next instance.", {
|
|
6449
7086
|
id: exports_external.string(),
|
|
6450
|
-
agent_id: exports_external.string().optional()
|
|
6451
|
-
|
|
7087
|
+
agent_id: exports_external.string().optional(),
|
|
7088
|
+
skip_recurrence: exports_external.boolean().optional()
|
|
7089
|
+
}, async ({ id, agent_id, skip_recurrence }) => {
|
|
6452
7090
|
try {
|
|
6453
7091
|
const resolvedId = resolveId(id);
|
|
6454
|
-
const task = completeTask(resolvedId, agent_id);
|
|
6455
|
-
|
|
7092
|
+
const task = completeTask(resolvedId, agent_id, undefined, { skip_recurrence });
|
|
7093
|
+
let text = `completed: ${formatTask(task)}`;
|
|
7094
|
+
if (task.metadata._next_recurrence) {
|
|
7095
|
+
const next = task.metadata._next_recurrence;
|
|
7096
|
+
text += `
|
|
7097
|
+
next: ${next.short_id || next.id.slice(0, 8)} due ${next.due_at}`;
|
|
7098
|
+
}
|
|
7099
|
+
return { content: [{ type: "text", text }] };
|
|
6456
7100
|
} catch (e) {
|
|
6457
7101
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6458
7102
|
}
|
|
6459
7103
|
});
|
|
6460
|
-
server.tool("lock_task", "Acquire exclusive lock
|
|
7104
|
+
server.tool("lock_task", "Acquire exclusive lock. Expires after 30 min. Idempotent per agent.", {
|
|
6461
7105
|
id: exports_external.string(),
|
|
6462
7106
|
agent_id: exports_external.string()
|
|
6463
7107
|
}, async ({ id, agent_id }) => {
|
|
@@ -6472,7 +7116,7 @@ server.tool("lock_task", "Acquire exclusive lock on a task", {
|
|
|
6472
7116
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6473
7117
|
}
|
|
6474
7118
|
});
|
|
6475
|
-
server.tool("unlock_task", "Release exclusive lock on a task", {
|
|
7119
|
+
server.tool("unlock_task", "Release exclusive lock on a task.", {
|
|
6476
7120
|
id: exports_external.string(),
|
|
6477
7121
|
agent_id: exports_external.string().optional()
|
|
6478
7122
|
}, async ({ id, agent_id }) => {
|
|
@@ -6484,7 +7128,7 @@ server.tool("unlock_task", "Release exclusive lock on a task", {
|
|
|
6484
7128
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6485
7129
|
}
|
|
6486
7130
|
});
|
|
6487
|
-
server.tool("add_dependency", "Add a dependency
|
|
7131
|
+
server.tool("add_dependency", "Add a dependency. Prevents cycles via BFS detection.", {
|
|
6488
7132
|
task_id: exports_external.string(),
|
|
6489
7133
|
depends_on: exports_external.string()
|
|
6490
7134
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -6497,7 +7141,7 @@ server.tool("add_dependency", "Add a dependency: task_id depends on depends_on."
|
|
|
6497
7141
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6498
7142
|
}
|
|
6499
7143
|
});
|
|
6500
|
-
server.tool("remove_dependency", "Remove a dependency between tasks", {
|
|
7144
|
+
server.tool("remove_dependency", "Remove a dependency link between two tasks.", {
|
|
6501
7145
|
task_id: exports_external.string(),
|
|
6502
7146
|
depends_on: exports_external.string()
|
|
6503
7147
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -6515,7 +7159,7 @@ server.tool("remove_dependency", "Remove a dependency between tasks", {
|
|
|
6515
7159
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6516
7160
|
}
|
|
6517
7161
|
});
|
|
6518
|
-
server.tool("add_comment", "Add a comment
|
|
7162
|
+
server.tool("add_comment", "Add a comment or note to a task. Comments are append-only.", {
|
|
6519
7163
|
task_id: exports_external.string(),
|
|
6520
7164
|
content: exports_external.string(),
|
|
6521
7165
|
agent_id: exports_external.string().optional(),
|
|
@@ -6546,7 +7190,7 @@ ${text}` }] };
|
|
|
6546
7190
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6547
7191
|
}
|
|
6548
7192
|
});
|
|
6549
|
-
server.tool("create_project", "Register a new project", {
|
|
7193
|
+
server.tool("create_project", "Register a new project with auto-generated task prefix.", {
|
|
6550
7194
|
name: exports_external.string(),
|
|
6551
7195
|
path: exports_external.string(),
|
|
6552
7196
|
description: exports_external.string().optional(),
|
|
@@ -6565,7 +7209,7 @@ server.tool("create_project", "Register a new project", {
|
|
|
6565
7209
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6566
7210
|
}
|
|
6567
7211
|
});
|
|
6568
|
-
server.tool("create_plan", "Create a
|
|
7212
|
+
server.tool("create_plan", "Create a plan to group related tasks.", {
|
|
6569
7213
|
name: exports_external.string(),
|
|
6570
7214
|
project_id: exports_external.string().optional(),
|
|
6571
7215
|
description: exports_external.string().optional(),
|
|
@@ -6590,7 +7234,7 @@ server.tool("create_plan", "Create a new plan", {
|
|
|
6590
7234
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6591
7235
|
}
|
|
6592
7236
|
});
|
|
6593
|
-
server.tool("list_plans", "List plans
|
|
7237
|
+
server.tool("list_plans", "List all plans, optionally filtered by project.", {
|
|
6594
7238
|
project_id: exports_external.string().optional()
|
|
6595
7239
|
}, async ({ project_id }) => {
|
|
6596
7240
|
try {
|
|
@@ -6610,7 +7254,7 @@ ${text}` }] };
|
|
|
6610
7254
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6611
7255
|
}
|
|
6612
7256
|
});
|
|
6613
|
-
server.tool("get_plan", "Get plan details", {
|
|
7257
|
+
server.tool("get_plan", "Get plan details including status and timestamps.", {
|
|
6614
7258
|
id: exports_external.string()
|
|
6615
7259
|
}, async ({ id }) => {
|
|
6616
7260
|
try {
|
|
@@ -6635,7 +7279,7 @@ server.tool("get_plan", "Get plan details", {
|
|
|
6635
7279
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6636
7280
|
}
|
|
6637
7281
|
});
|
|
6638
|
-
server.tool("update_plan", "Update
|
|
7282
|
+
server.tool("update_plan", "Update plan fields (name, description, status).", {
|
|
6639
7283
|
id: exports_external.string(),
|
|
6640
7284
|
name: exports_external.string().optional(),
|
|
6641
7285
|
description: exports_external.string().optional(),
|
|
@@ -6659,7 +7303,7 @@ server.tool("update_plan", "Update a plan", {
|
|
|
6659
7303
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6660
7304
|
}
|
|
6661
7305
|
});
|
|
6662
|
-
server.tool("delete_plan", "Delete a plan", {
|
|
7306
|
+
server.tool("delete_plan", "Delete a plan. Tasks in the plan are orphaned (not deleted).", {
|
|
6663
7307
|
id: exports_external.string()
|
|
6664
7308
|
}, async ({ id }) => {
|
|
6665
7309
|
try {
|
|
@@ -6675,15 +7319,34 @@ server.tool("delete_plan", "Delete a plan", {
|
|
|
6675
7319
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6676
7320
|
}
|
|
6677
7321
|
});
|
|
6678
|
-
server.tool("search_tasks", "Full-text search across
|
|
7322
|
+
server.tool("search_tasks", "Full-text search across tasks with filters.", {
|
|
6679
7323
|
query: exports_external.string(),
|
|
6680
7324
|
project_id: exports_external.string().optional(),
|
|
6681
|
-
task_list_id: exports_external.string().optional()
|
|
6682
|
-
|
|
7325
|
+
task_list_id: exports_external.string().optional(),
|
|
7326
|
+
status: exports_external.union([
|
|
7327
|
+
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
7328
|
+
exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))
|
|
7329
|
+
]).optional(),
|
|
7330
|
+
priority: exports_external.union([
|
|
7331
|
+
exports_external.enum(["low", "medium", "high", "critical"]),
|
|
7332
|
+
exports_external.array(exports_external.enum(["low", "medium", "high", "critical"]))
|
|
7333
|
+
]).optional(),
|
|
7334
|
+
assigned_to: exports_external.string().optional(),
|
|
7335
|
+
agent_id: exports_external.string().optional(),
|
|
7336
|
+
created_after: exports_external.string().optional(),
|
|
7337
|
+
updated_after: exports_external.string().optional(),
|
|
7338
|
+
has_dependencies: exports_external.boolean().optional(),
|
|
7339
|
+
is_blocked: exports_external.boolean().optional()
|
|
7340
|
+
}, async ({ query, project_id, task_list_id, ...filters }) => {
|
|
6683
7341
|
try {
|
|
6684
7342
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
6685
7343
|
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
6686
|
-
const tasks = searchTasks(
|
|
7344
|
+
const tasks = searchTasks({
|
|
7345
|
+
query,
|
|
7346
|
+
project_id: resolvedProjectId,
|
|
7347
|
+
task_list_id: resolvedTaskListId,
|
|
7348
|
+
...filters
|
|
7349
|
+
});
|
|
6687
7350
|
if (tasks.length === 0) {
|
|
6688
7351
|
return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
|
|
6689
7352
|
}
|
|
@@ -6695,7 +7358,7 @@ ${text}` }] };
|
|
|
6695
7358
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6696
7359
|
}
|
|
6697
7360
|
});
|
|
6698
|
-
server.tool("sync", "Sync tasks
|
|
7361
|
+
server.tool("sync", "Sync tasks between local DB and agent task list.", {
|
|
6699
7362
|
task_list_id: exports_external.string().optional(),
|
|
6700
7363
|
agent: exports_external.string().optional(),
|
|
6701
7364
|
all_agents: exports_external.boolean().optional(),
|
|
@@ -6743,7 +7406,7 @@ server.tool("sync", "Sync tasks with an agent task list.", {
|
|
|
6743
7406
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6744
7407
|
}
|
|
6745
7408
|
});
|
|
6746
|
-
server.tool("register_agent", "Register an agent (idempotent by name).", {
|
|
7409
|
+
server.tool("register_agent", "Register an agent (idempotent by name). Updates last_seen_at.", {
|
|
6747
7410
|
name: exports_external.string(),
|
|
6748
7411
|
description: exports_external.string().optional()
|
|
6749
7412
|
}, async ({ name, description }) => {
|
|
@@ -6780,7 +7443,7 @@ ${text}` }] };
|
|
|
6780
7443
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6781
7444
|
}
|
|
6782
7445
|
});
|
|
6783
|
-
server.tool("get_agent", "Get agent details by ID or name", {
|
|
7446
|
+
server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
|
|
6784
7447
|
id: exports_external.string().optional(),
|
|
6785
7448
|
name: exports_external.string().optional()
|
|
6786
7449
|
}, async ({ id, name }) => {
|
|
@@ -6809,9 +7472,9 @@ server.tool("get_agent", "Get agent details by ID or name", {
|
|
|
6809
7472
|
}
|
|
6810
7473
|
});
|
|
6811
7474
|
server.tool("rename_agent", "Rename an agent. Resolve by id or current name.", {
|
|
6812
|
-
id: exports_external.string().optional()
|
|
6813
|
-
name: exports_external.string().optional()
|
|
6814
|
-
new_name: exports_external.string()
|
|
7475
|
+
id: exports_external.string().optional(),
|
|
7476
|
+
name: exports_external.string().optional(),
|
|
7477
|
+
new_name: exports_external.string()
|
|
6815
7478
|
}, async ({ id, name, new_name }) => {
|
|
6816
7479
|
try {
|
|
6817
7480
|
if (!id && !name) {
|
|
@@ -6834,8 +7497,8 @@ ID: ${updated.id}`
|
|
|
6834
7497
|
}
|
|
6835
7498
|
});
|
|
6836
7499
|
server.tool("delete_agent", "Delete an agent permanently. Resolve by id or name.", {
|
|
6837
|
-
id: exports_external.string().optional()
|
|
6838
|
-
name: exports_external.string().optional()
|
|
7500
|
+
id: exports_external.string().optional(),
|
|
7501
|
+
name: exports_external.string().optional()
|
|
6839
7502
|
}, async ({ id, name }) => {
|
|
6840
7503
|
try {
|
|
6841
7504
|
if (!id && !name) {
|
|
@@ -6857,7 +7520,7 @@ server.tool("delete_agent", "Delete an agent permanently. Resolve by id or name.
|
|
|
6857
7520
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6858
7521
|
}
|
|
6859
7522
|
});
|
|
6860
|
-
server.tool("create_task_list", "Create a
|
|
7523
|
+
server.tool("create_task_list", "Create a task list container for organizing tasks.", {
|
|
6861
7524
|
name: exports_external.string(),
|
|
6862
7525
|
slug: exports_external.string().optional(),
|
|
6863
7526
|
project_id: exports_external.string().optional(),
|
|
@@ -6883,7 +7546,7 @@ Description: ${list.description}` : ""}`
|
|
|
6883
7546
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6884
7547
|
}
|
|
6885
7548
|
});
|
|
6886
|
-
server.tool("list_task_lists", "List task lists, optionally filtered by project", {
|
|
7549
|
+
server.tool("list_task_lists", "List all task lists, optionally filtered by project.", {
|
|
6887
7550
|
project_id: exports_external.string().optional()
|
|
6888
7551
|
}, async ({ project_id }) => {
|
|
6889
7552
|
try {
|
|
@@ -6903,7 +7566,7 @@ ${text}` }] };
|
|
|
6903
7566
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6904
7567
|
}
|
|
6905
7568
|
});
|
|
6906
|
-
server.tool("get_task_list", "Get task list details", {
|
|
7569
|
+
server.tool("get_task_list", "Get task list details including slug and metadata.", {
|
|
6907
7570
|
id: exports_external.string()
|
|
6908
7571
|
}, async ({ id }) => {
|
|
6909
7572
|
try {
|
|
@@ -6931,7 +7594,7 @@ server.tool("get_task_list", "Get task list details", {
|
|
|
6931
7594
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6932
7595
|
}
|
|
6933
7596
|
});
|
|
6934
|
-
server.tool("update_task_list", "Update a task list", {
|
|
7597
|
+
server.tool("update_task_list", "Update a task list's name or description.", {
|
|
6935
7598
|
id: exports_external.string(),
|
|
6936
7599
|
name: exports_external.string().optional(),
|
|
6937
7600
|
description: exports_external.string().optional()
|
|
@@ -6952,7 +7615,7 @@ Slug: ${list.slug}`
|
|
|
6952
7615
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6953
7616
|
}
|
|
6954
7617
|
});
|
|
6955
|
-
server.tool("delete_task_list", "Delete a task list. Tasks
|
|
7618
|
+
server.tool("delete_task_list", "Delete a task list. Tasks are orphaned, not deleted.", {
|
|
6956
7619
|
id: exports_external.string()
|
|
6957
7620
|
}, async ({ id }) => {
|
|
6958
7621
|
try {
|
|
@@ -6968,7 +7631,7 @@ server.tool("delete_task_list", "Delete a task list. Tasks lose association but
|
|
|
6968
7631
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6969
7632
|
}
|
|
6970
7633
|
});
|
|
6971
|
-
server.tool("get_task_history", "Get audit log
|
|
7634
|
+
server.tool("get_task_history", "Get audit log \u2014 field changes with timestamps and actors.", {
|
|
6972
7635
|
task_id: exports_external.string()
|
|
6973
7636
|
}, async ({ task_id }) => {
|
|
6974
7637
|
try {
|
|
@@ -6985,7 +7648,7 @@ ${text}` }] };
|
|
|
6985
7648
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6986
7649
|
}
|
|
6987
7650
|
});
|
|
6988
|
-
server.tool("get_recent_activity", "Get recent task changes
|
|
7651
|
+
server.tool("get_recent_activity", "Get recent task changes \u2014 global activity feed.", {
|
|
6989
7652
|
limit: exports_external.number().optional()
|
|
6990
7653
|
}, async ({ limit }) => {
|
|
6991
7654
|
try {
|
|
@@ -7001,7 +7664,7 @@ ${text}` }] };
|
|
|
7001
7664
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7002
7665
|
}
|
|
7003
7666
|
});
|
|
7004
|
-
server.tool("create_webhook", "Register a webhook
|
|
7667
|
+
server.tool("create_webhook", "Register a webhook for task change events.", {
|
|
7005
7668
|
url: exports_external.string(),
|
|
7006
7669
|
events: exports_external.array(exports_external.string()).optional(),
|
|
7007
7670
|
secret: exports_external.string().optional()
|
|
@@ -7028,7 +7691,7 @@ ${text}` }] };
|
|
|
7028
7691
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7029
7692
|
}
|
|
7030
7693
|
});
|
|
7031
|
-
server.tool("delete_webhook", "Delete a webhook", {
|
|
7694
|
+
server.tool("delete_webhook", "Delete a webhook by ID.", {
|
|
7032
7695
|
id: exports_external.string()
|
|
7033
7696
|
}, async ({ id }) => {
|
|
7034
7697
|
try {
|
|
@@ -7094,7 +7757,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
7094
7757
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7095
7758
|
}
|
|
7096
7759
|
});
|
|
7097
|
-
server.tool("delete_template", "Delete a task template", { id: exports_external.string() }, async ({ id }) => {
|
|
7760
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
7098
7761
|
try {
|
|
7099
7762
|
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
7100
7763
|
const deleted = deleteTemplate2(id);
|
|
@@ -7103,7 +7766,7 @@ server.tool("delete_template", "Delete a task template", { id: exports_external.
|
|
|
7103
7766
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7104
7767
|
}
|
|
7105
7768
|
});
|
|
7106
|
-
server.tool("approve_task", "Approve a task
|
|
7769
|
+
server.tool("approve_task", "Approve a task with requires_approval=true.", {
|
|
7107
7770
|
id: exports_external.string(),
|
|
7108
7771
|
agent_id: exports_external.string().optional()
|
|
7109
7772
|
}, async ({ id, agent_id }) => {
|
|
@@ -7122,7 +7785,31 @@ server.tool("approve_task", "Approve a task that requires approval.", {
|
|
|
7122
7785
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7123
7786
|
}
|
|
7124
7787
|
});
|
|
7125
|
-
server.tool("
|
|
7788
|
+
server.tool("fail_task", "Mark a task as failed with structured reason and optional auto-retry.", {
|
|
7789
|
+
id: exports_external.string(),
|
|
7790
|
+
agent_id: exports_external.string().optional(),
|
|
7791
|
+
reason: exports_external.string().optional(),
|
|
7792
|
+
error_code: exports_external.string().optional(),
|
|
7793
|
+
retry: exports_external.boolean().optional(),
|
|
7794
|
+
retry_after: exports_external.string().optional()
|
|
7795
|
+
}, async ({ id, agent_id, reason, error_code, retry, retry_after }) => {
|
|
7796
|
+
try {
|
|
7797
|
+
const resolvedId = resolveId(id);
|
|
7798
|
+
const result = failTask(resolvedId, agent_id, reason, { retry, retry_after, error_code });
|
|
7799
|
+
let text = `failed: ${formatTask(result.task)}`;
|
|
7800
|
+
if (reason)
|
|
7801
|
+
text += `
|
|
7802
|
+
Reason: ${reason}`;
|
|
7803
|
+
if (result.retryTask) {
|
|
7804
|
+
text += `
|
|
7805
|
+
Retry task created: ${formatTask(result.retryTask)}`;
|
|
7806
|
+
}
|
|
7807
|
+
return { content: [{ type: "text", text }] };
|
|
7808
|
+
} catch (e) {
|
|
7809
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7810
|
+
}
|
|
7811
|
+
});
|
|
7812
|
+
server.tool("get_my_tasks", "Get tasks assigned to/created by an agent with stats.", {
|
|
7126
7813
|
agent_name: exports_external.string()
|
|
7127
7814
|
}, async ({ agent_name }) => {
|
|
7128
7815
|
try {
|
|
@@ -7155,7 +7842,7 @@ In Progress:`);
|
|
|
7155
7842
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7156
7843
|
}
|
|
7157
7844
|
});
|
|
7158
|
-
server.tool("get_org_chart", "Get agent org chart
|
|
7845
|
+
server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
|
|
7159
7846
|
try {
|
|
7160
7847
|
let render = function(nodes, indent = 0) {
|
|
7161
7848
|
return nodes.map((n) => {
|
|
@@ -7177,7 +7864,7 @@ server.tool("get_org_chart", "Get agent org chart \u2014 who reports to who.", {
|
|
|
7177
7864
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7178
7865
|
}
|
|
7179
7866
|
});
|
|
7180
|
-
server.tool("set_reports_to", "Set
|
|
7867
|
+
server.tool("set_reports_to", "Set agent reporting relationship in org chart.", {
|
|
7181
7868
|
agent_name: exports_external.string(),
|
|
7182
7869
|
manager_name: exports_external.string().optional()
|
|
7183
7870
|
}, async ({ agent_name, manager_name }) => {
|
|
@@ -7200,7 +7887,326 @@ server.tool("set_reports_to", "Set who an agent reports to in the org chart.", {
|
|
|
7200
7887
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7201
7888
|
}
|
|
7202
7889
|
});
|
|
7203
|
-
server.tool("
|
|
7890
|
+
server.tool("bulk_update_tasks", "Update multiple tasks at once with the same changes.", {
|
|
7891
|
+
task_ids: exports_external.array(exports_external.string()),
|
|
7892
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
7893
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
7894
|
+
assigned_to: exports_external.string().optional(),
|
|
7895
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
7896
|
+
}, async ({ task_ids, ...updates }) => {
|
|
7897
|
+
try {
|
|
7898
|
+
const resolvedIds = task_ids.map((id) => resolveId(id));
|
|
7899
|
+
const result = bulkUpdateTasks(resolvedIds, updates);
|
|
7900
|
+
const parts = [`Updated ${result.updated} task(s).`];
|
|
7901
|
+
if (result.failed.length > 0) {
|
|
7902
|
+
parts.push(`Failed ${result.failed.length}:`);
|
|
7903
|
+
for (const f of result.failed)
|
|
7904
|
+
parts.push(` ${f.id.slice(0, 8)}: ${f.error}`);
|
|
7905
|
+
}
|
|
7906
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
7907
|
+
`) }] };
|
|
7908
|
+
} catch (e) {
|
|
7909
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7910
|
+
}
|
|
7911
|
+
});
|
|
7912
|
+
server.tool("clone_task", "Duplicate a task with optional field overrides.", {
|
|
7913
|
+
task_id: exports_external.string(),
|
|
7914
|
+
title: exports_external.string().optional(),
|
|
7915
|
+
description: exports_external.string().optional(),
|
|
7916
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
7917
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
7918
|
+
project_id: exports_external.string().optional(),
|
|
7919
|
+
plan_id: exports_external.string().optional(),
|
|
7920
|
+
task_list_id: exports_external.string().optional(),
|
|
7921
|
+
assigned_to: exports_external.string().optional(),
|
|
7922
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
7923
|
+
estimated_minutes: exports_external.number().optional()
|
|
7924
|
+
}, async ({ task_id, ...overrides }) => {
|
|
7925
|
+
try {
|
|
7926
|
+
const resolvedId = resolveId(task_id);
|
|
7927
|
+
const resolved = { ...overrides };
|
|
7928
|
+
if (resolved.project_id)
|
|
7929
|
+
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
7930
|
+
if (resolved.plan_id)
|
|
7931
|
+
resolved.plan_id = resolveId(resolved.plan_id, "plans");
|
|
7932
|
+
if (resolved.task_list_id)
|
|
7933
|
+
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
7934
|
+
const task = cloneTask(resolvedId, resolved);
|
|
7935
|
+
return { content: [{ type: "text", text: `cloned: ${formatTask(task)}` }] };
|
|
7936
|
+
} catch (e) {
|
|
7937
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7938
|
+
}
|
|
7939
|
+
});
|
|
7940
|
+
server.tool("get_task_stats", "Get task analytics: counts by status, priority, agent.", {
|
|
7941
|
+
project_id: exports_external.string().optional(),
|
|
7942
|
+
task_list_id: exports_external.string().optional(),
|
|
7943
|
+
agent_id: exports_external.string().optional()
|
|
7944
|
+
}, async ({ project_id, task_list_id, agent_id }) => {
|
|
7945
|
+
try {
|
|
7946
|
+
const filters = {};
|
|
7947
|
+
if (project_id)
|
|
7948
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
7949
|
+
if (task_list_id)
|
|
7950
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
7951
|
+
if (agent_id)
|
|
7952
|
+
filters.agent_id = agent_id;
|
|
7953
|
+
const stats = getTaskStats(Object.keys(filters).length > 0 ? filters : undefined);
|
|
7954
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
7955
|
+
} catch (e) {
|
|
7956
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7957
|
+
}
|
|
7958
|
+
});
|
|
7959
|
+
server.tool("get_task_graph", "Get full dependency tree for a task.", {
|
|
7960
|
+
id: exports_external.string(),
|
|
7961
|
+
direction: exports_external.enum(["up", "down", "both"]).optional()
|
|
7962
|
+
}, async ({ id, direction }) => {
|
|
7963
|
+
try {
|
|
7964
|
+
let formatNode = function(node, indent) {
|
|
7965
|
+
const prefix = " ".repeat(indent);
|
|
7966
|
+
const idLabel = node.task.short_id || node.task.id.slice(0, 8);
|
|
7967
|
+
const blocked = node.task.is_blocked ? " (blocked: yes)" : "";
|
|
7968
|
+
let out = `${prefix}[${node.task.status}] ${idLabel} | ${node.task.title}${blocked}
|
|
7969
|
+
`;
|
|
7970
|
+
if (node.depends_on.length > 0) {
|
|
7971
|
+
out += `${prefix} Depends on:
|
|
7972
|
+
`;
|
|
7973
|
+
for (const dep of node.depends_on) {
|
|
7974
|
+
out += formatNode(dep, indent + 2);
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
if (node.blocks.length > 0) {
|
|
7978
|
+
out += `${prefix} Blocks:
|
|
7979
|
+
`;
|
|
7980
|
+
for (const dep of node.blocks) {
|
|
7981
|
+
out += formatNode(dep, indent + 2);
|
|
7982
|
+
}
|
|
7983
|
+
}
|
|
7984
|
+
return out;
|
|
7985
|
+
};
|
|
7986
|
+
const taskId = resolveId(id, "tasks");
|
|
7987
|
+
const graph = getTaskGraph(taskId, direction || "both");
|
|
7988
|
+
let text = `Task: ${formatNode(graph, 0)}`;
|
|
7989
|
+
if (graph.depends_on.length > 0) {
|
|
7990
|
+
text += `
|
|
7991
|
+
Depends on:
|
|
7992
|
+
`;
|
|
7993
|
+
for (const dep of graph.depends_on) {
|
|
7994
|
+
text += formatNode(dep, 1);
|
|
7995
|
+
}
|
|
7996
|
+
}
|
|
7997
|
+
if (graph.blocks.length > 0) {
|
|
7998
|
+
text += `
|
|
7999
|
+
Blocks:
|
|
8000
|
+
`;
|
|
8001
|
+
for (const dep of graph.blocks) {
|
|
8002
|
+
text += formatNode(dep, 1);
|
|
8003
|
+
}
|
|
8004
|
+
}
|
|
8005
|
+
return { content: [{ type: "text", text }] };
|
|
8006
|
+
} catch (e) {
|
|
8007
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8008
|
+
}
|
|
8009
|
+
});
|
|
8010
|
+
server.tool("bulk_create_tasks", "Create multiple tasks atomically with dependency support.", {
|
|
8011
|
+
tasks: exports_external.array(exports_external.object({
|
|
8012
|
+
temp_id: exports_external.string().optional(),
|
|
8013
|
+
title: exports_external.string(),
|
|
8014
|
+
description: exports_external.string().optional(),
|
|
8015
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
8016
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
8017
|
+
project_id: exports_external.string().optional(),
|
|
8018
|
+
plan_id: exports_external.string().optional(),
|
|
8019
|
+
task_list_id: exports_external.string().optional(),
|
|
8020
|
+
agent_id: exports_external.string().optional(),
|
|
8021
|
+
assigned_to: exports_external.string().optional(),
|
|
8022
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
8023
|
+
estimated_minutes: exports_external.number().optional(),
|
|
8024
|
+
depends_on_temp_ids: exports_external.array(exports_external.string()).optional()
|
|
8025
|
+
})),
|
|
8026
|
+
project_id: exports_external.string().optional(),
|
|
8027
|
+
plan_id: exports_external.string().optional(),
|
|
8028
|
+
task_list_id: exports_external.string().optional()
|
|
8029
|
+
}, async ({ tasks, project_id, plan_id, task_list_id }) => {
|
|
8030
|
+
try {
|
|
8031
|
+
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
8032
|
+
const resolvedPlanId = plan_id ? resolveId(plan_id, "plans") : undefined;
|
|
8033
|
+
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
8034
|
+
const enrichedTasks = tasks.map((t) => ({
|
|
8035
|
+
...t,
|
|
8036
|
+
project_id: t.project_id || resolvedProjectId,
|
|
8037
|
+
plan_id: t.plan_id || resolvedPlanId,
|
|
8038
|
+
task_list_id: t.task_list_id || resolvedTaskListId
|
|
8039
|
+
}));
|
|
8040
|
+
const result = bulkCreateTasks(enrichedTasks);
|
|
8041
|
+
const lines = result.created.map((t) => {
|
|
8042
|
+
const tid = t.temp_id ? `[${t.temp_id}] ` : "";
|
|
8043
|
+
const sid = t.short_id || t.id.slice(0, 8);
|
|
8044
|
+
return ` ${tid}${sid} | ${t.title}`;
|
|
8045
|
+
});
|
|
8046
|
+
return { content: [{ type: "text", text: `Created ${result.created.length} task(s):
|
|
8047
|
+
${lines.join(`
|
|
8048
|
+
`)}` }] };
|
|
8049
|
+
} catch (e) {
|
|
8050
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8051
|
+
}
|
|
8052
|
+
});
|
|
8053
|
+
server.tool("move_task", "Move a task to a different list, project, or plan.", {
|
|
8054
|
+
task_id: exports_external.string(),
|
|
8055
|
+
task_list_id: exports_external.string().nullable().optional(),
|
|
8056
|
+
project_id: exports_external.string().nullable().optional(),
|
|
8057
|
+
plan_id: exports_external.string().nullable().optional()
|
|
8058
|
+
}, async ({ task_id, ...target }) => {
|
|
8059
|
+
try {
|
|
8060
|
+
const resolvedId = resolveId(task_id);
|
|
8061
|
+
const resolvedTarget = {};
|
|
8062
|
+
if (target.task_list_id !== undefined)
|
|
8063
|
+
resolvedTarget.task_list_id = target.task_list_id ? resolveId(target.task_list_id, "task_lists") : null;
|
|
8064
|
+
if (target.project_id !== undefined)
|
|
8065
|
+
resolvedTarget.project_id = target.project_id ? resolveId(target.project_id, "projects") : null;
|
|
8066
|
+
if (target.plan_id !== undefined)
|
|
8067
|
+
resolvedTarget.plan_id = target.plan_id ? resolveId(target.plan_id, "plans") : null;
|
|
8068
|
+
const task = moveTask(resolvedId, resolvedTarget);
|
|
8069
|
+
return { content: [{ type: "text", text: `moved: ${formatTask(task)}` }] };
|
|
8070
|
+
} catch (e) {
|
|
8071
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8072
|
+
}
|
|
8073
|
+
});
|
|
8074
|
+
server.tool("get_next_task", "Get the best pending task to work on next.", {
|
|
8075
|
+
agent_id: exports_external.string().optional(),
|
|
8076
|
+
project_id: exports_external.string().optional(),
|
|
8077
|
+
task_list_id: exports_external.string().optional(),
|
|
8078
|
+
plan_id: exports_external.string().optional(),
|
|
8079
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
8080
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
8081
|
+
try {
|
|
8082
|
+
const filters = {};
|
|
8083
|
+
if (project_id)
|
|
8084
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8085
|
+
if (task_list_id)
|
|
8086
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8087
|
+
if (plan_id)
|
|
8088
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
8089
|
+
if (tags)
|
|
8090
|
+
filters.tags = tags;
|
|
8091
|
+
const task = getNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8092
|
+
if (!task) {
|
|
8093
|
+
return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
|
|
8094
|
+
}
|
|
8095
|
+
return { content: [{ type: "text", text: `next: ${formatTask(task)}
|
|
8096
|
+
${formatTaskDetail(task)}` }] };
|
|
8097
|
+
} catch (e) {
|
|
8098
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8099
|
+
}
|
|
8100
|
+
});
|
|
8101
|
+
server.tool("get_active_work", "See all in-progress tasks and who is working on them.", {
|
|
8102
|
+
project_id: exports_external.string().optional(),
|
|
8103
|
+
task_list_id: exports_external.string().optional()
|
|
8104
|
+
}, async ({ project_id, task_list_id }) => {
|
|
8105
|
+
try {
|
|
8106
|
+
const filters = {};
|
|
8107
|
+
if (project_id)
|
|
8108
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8109
|
+
if (task_list_id)
|
|
8110
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8111
|
+
const work = getActiveWork(Object.keys(filters).length > 0 ? filters : undefined);
|
|
8112
|
+
if (work.length === 0) {
|
|
8113
|
+
return { content: [{ type: "text", text: "No active work \u2014 no tasks are currently in progress." }] };
|
|
8114
|
+
}
|
|
8115
|
+
const text = work.map((w) => {
|
|
8116
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
8117
|
+
const agent = w.assigned_to || w.locked_by || "unassigned";
|
|
8118
|
+
const since = w.updated_at;
|
|
8119
|
+
return `${agent.padEnd(12)} | ${w.priority.padEnd(8)} | ${id} | ${w.title} (since ${since})`;
|
|
8120
|
+
}).join(`
|
|
8121
|
+
`);
|
|
8122
|
+
return { content: [{ type: "text", text: `${work.length} active task(s):
|
|
8123
|
+
${text}` }] };
|
|
8124
|
+
} catch (e) {
|
|
8125
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8126
|
+
}
|
|
8127
|
+
});
|
|
8128
|
+
server.tool("get_tasks_changed_since", "Get tasks modified after a timestamp for incremental sync.", {
|
|
8129
|
+
since: exports_external.string(),
|
|
8130
|
+
project_id: exports_external.string().optional(),
|
|
8131
|
+
task_list_id: exports_external.string().optional()
|
|
8132
|
+
}, async ({ since, project_id, task_list_id }) => {
|
|
8133
|
+
try {
|
|
8134
|
+
const filters = {};
|
|
8135
|
+
if (project_id)
|
|
8136
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8137
|
+
if (task_list_id)
|
|
8138
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8139
|
+
const tasks = getTasksChangedSince(since, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8140
|
+
if (tasks.length === 0) {
|
|
8141
|
+
return { content: [{ type: "text", text: `No tasks changed since ${since}.` }] };
|
|
8142
|
+
}
|
|
8143
|
+
const text = tasks.map((t) => {
|
|
8144
|
+
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
8145
|
+
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned} (updated: ${t.updated_at})`;
|
|
8146
|
+
}).join(`
|
|
8147
|
+
`);
|
|
8148
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) changed since ${since}:
|
|
8149
|
+
${text}` }] };
|
|
8150
|
+
} catch (e) {
|
|
8151
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8152
|
+
}
|
|
8153
|
+
});
|
|
8154
|
+
server.tool("claim_next_task", "Atomically claim, lock, and start the best pending task.", {
|
|
8155
|
+
agent_id: exports_external.string(),
|
|
8156
|
+
project_id: exports_external.string().optional(),
|
|
8157
|
+
task_list_id: exports_external.string().optional(),
|
|
8158
|
+
plan_id: exports_external.string().optional(),
|
|
8159
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
8160
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
8161
|
+
try {
|
|
8162
|
+
const filters = {};
|
|
8163
|
+
if (project_id)
|
|
8164
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8165
|
+
if (task_list_id)
|
|
8166
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8167
|
+
if (plan_id)
|
|
8168
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
8169
|
+
if (tags)
|
|
8170
|
+
filters.tags = tags;
|
|
8171
|
+
const task = claimNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8172
|
+
if (!task) {
|
|
8173
|
+
return { content: [{ type: "text", text: "No tasks available to claim." }] };
|
|
8174
|
+
}
|
|
8175
|
+
return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
|
|
8176
|
+
${formatTaskDetail(task)}` }] };
|
|
8177
|
+
} catch (e) {
|
|
8178
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8179
|
+
}
|
|
8180
|
+
});
|
|
8181
|
+
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
8182
|
+
stale_minutes: exports_external.number().optional(),
|
|
8183
|
+
project_id: exports_external.string().optional(),
|
|
8184
|
+
task_list_id: exports_external.string().optional()
|
|
8185
|
+
}, async ({ stale_minutes, project_id, task_list_id }) => {
|
|
8186
|
+
try {
|
|
8187
|
+
const filters = {};
|
|
8188
|
+
if (project_id)
|
|
8189
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8190
|
+
if (task_list_id)
|
|
8191
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8192
|
+
const tasks = getStaleTasks(stale_minutes || 30, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8193
|
+
if (tasks.length === 0) {
|
|
8194
|
+
return { content: [{ type: "text", text: "No stale tasks found." }] };
|
|
8195
|
+
}
|
|
8196
|
+
const text = tasks.map((t) => {
|
|
8197
|
+
const id = t.short_id || t.id.slice(0, 8);
|
|
8198
|
+
const agent = t.locked_by || t.assigned_to || "unknown";
|
|
8199
|
+
const staleFor = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
8200
|
+
return `${id} | ${agent} | ${t.title} (stale ${staleFor}min)`;
|
|
8201
|
+
}).join(`
|
|
8202
|
+
`);
|
|
8203
|
+
return { content: [{ type: "text", text: `${tasks.length} stale task(s):
|
|
8204
|
+
${text}` }] };
|
|
8205
|
+
} catch (e) {
|
|
8206
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8207
|
+
}
|
|
8208
|
+
});
|
|
8209
|
+
server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
|
|
7204
8210
|
const all = [
|
|
7205
8211
|
"create_task",
|
|
7206
8212
|
"list_tasks",
|
|
@@ -7209,6 +8215,7 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7209
8215
|
"delete_task",
|
|
7210
8216
|
"start_task",
|
|
7211
8217
|
"complete_task",
|
|
8218
|
+
"fail_task",
|
|
7212
8219
|
"lock_task",
|
|
7213
8220
|
"unlock_task",
|
|
7214
8221
|
"approve_task",
|
|
@@ -7225,7 +8232,11 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7225
8232
|
"register_agent",
|
|
7226
8233
|
"list_agents",
|
|
7227
8234
|
"get_agent",
|
|
8235
|
+
"rename_agent",
|
|
8236
|
+
"delete_agent",
|
|
7228
8237
|
"get_my_tasks",
|
|
8238
|
+
"get_org_chart",
|
|
8239
|
+
"set_reports_to",
|
|
7229
8240
|
"create_task_list",
|
|
7230
8241
|
"list_task_lists",
|
|
7231
8242
|
"get_task_list",
|
|
@@ -7233,6 +8244,10 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7233
8244
|
"delete_task_list",
|
|
7234
8245
|
"search_tasks",
|
|
7235
8246
|
"sync",
|
|
8247
|
+
"clone_task",
|
|
8248
|
+
"move_task",
|
|
8249
|
+
"get_next_task",
|
|
8250
|
+
"claim_next_task",
|
|
7236
8251
|
"get_task_history",
|
|
7237
8252
|
"get_recent_activity",
|
|
7238
8253
|
"create_webhook",
|
|
@@ -7242,6 +8257,13 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7242
8257
|
"list_templates",
|
|
7243
8258
|
"create_task_from_template",
|
|
7244
8259
|
"delete_template",
|
|
8260
|
+
"bulk_update_tasks",
|
|
8261
|
+
"bulk_create_tasks",
|
|
8262
|
+
"get_task_stats",
|
|
8263
|
+
"get_task_graph",
|
|
8264
|
+
"get_active_work",
|
|
8265
|
+
"get_tasks_changed_since",
|
|
8266
|
+
"get_stale_tasks",
|
|
7245
8267
|
"search_tools",
|
|
7246
8268
|
"describe_tools"
|
|
7247
8269
|
];
|
|
@@ -7249,27 +8271,175 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7249
8271
|
const matches = q ? all.filter((n) => n.includes(q)) : all;
|
|
7250
8272
|
return { content: [{ type: "text", text: matches.join(", ") }] };
|
|
7251
8273
|
});
|
|
7252
|
-
server.tool("describe_tools", "Get
|
|
8274
|
+
server.tool("describe_tools", "Get detailed parameter info for specific tools by name.", { names: exports_external.array(exports_external.string()) }, async ({ names }) => {
|
|
7253
8275
|
const descriptions = {
|
|
7254
|
-
create_task:
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
8276
|
+
create_task: `Create a new task.
|
|
8277
|
+
Params: title(string, req), description(string), priority(low|medium|high|critical, default:medium), status(pending|in_progress|completed|failed|cancelled, default:pending), project_id(string), parent_id(string \u2014 creates subtask), plan_id(string), task_list_id(string), agent_id(string), assigned_to(string), tags(string[]), metadata(object), estimated_minutes(number), requires_approval(boolean), recurrence_rule(string \u2014 e.g. 'every day', 'every weekday', 'every 2 weeks', 'every monday'), session_id(string), working_dir(string)
|
|
8278
|
+
Example: {title: 'Daily standup', recurrence_rule: 'every weekday', priority: 'medium'}`,
|
|
8279
|
+
list_tasks: `List tasks with optional filters. Supports pagination.
|
|
8280
|
+
Params: status(string|string[]), priority(string|string[]), project_id(string), plan_id(string), task_list_id(string), assigned_to(string), tags(string[]), has_recurrence(boolean \u2014 true=only recurring, false=only non-recurring), limit(number), offset(number)
|
|
8281
|
+
Example: {status: ['pending', 'in_progress'], has_recurrence: true, limit: 20}`,
|
|
8282
|
+
get_task: `Get full task details with subtasks, deps, and comments.
|
|
8283
|
+
Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
|
|
8284
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8285
|
+
update_task: `Update task fields. Requires version for optimistic locking (get it from get_task first).
|
|
8286
|
+
Params: id(string, req), version(number, req), title(string), description(string), status(pending|in_progress|completed|failed|cancelled), priority(low|medium|high|critical), assigned_to(string), tags(string[]), metadata(object), plan_id(string), task_list_id(string)
|
|
8287
|
+
Example: {id: 'a1b2c3d4', version: 3, status: 'completed'}`,
|
|
8288
|
+
delete_task: `Delete a task permanently. Subtasks cascade-delete. Dependencies removed.
|
|
8289
|
+
Params: id(string, req)
|
|
8290
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8291
|
+
start_task: `Claim, lock, and set task status to in_progress in one call.
|
|
8292
|
+
Params: id(string, req), agent_id(string, req \u2014 your 8-char agent ID)
|
|
8293
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8294
|
+
complete_task: `Mark task completed, release lock, set completed_at timestamp. For recurring tasks, auto-spawns next instance unless skip_recurrence is true.
|
|
8295
|
+
Params: id(string, req), agent_id(string, optional \u2014 required if locked by different agent), skip_recurrence(boolean \u2014 set true to prevent auto-creating next recurring instance)
|
|
8296
|
+
Example: {id: 'a1b2c3d4', skip_recurrence: false}`,
|
|
8297
|
+
lock_task: `Acquire exclusive lock on a task. Locks auto-expire after 30 min. Re-locking by same agent is idempotent.
|
|
8298
|
+
Params: id(string, req), agent_id(string, req)
|
|
8299
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8300
|
+
unlock_task: `Release exclusive lock on a task.
|
|
8301
|
+
Params: id(string, req), agent_id(string, optional \u2014 omit to force-unlock)
|
|
8302
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8303
|
+
approve_task: `Approve a task with requires_approval=true. Must be approved before completion.
|
|
8304
|
+
Params: id(string, req), agent_id(string, optional \u2014 defaults to 'system')
|
|
8305
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8306
|
+
fail_task: `Mark a task as failed with structured reason and optional auto-retry. Stores failure info in metadata._failure, releases lock.
|
|
8307
|
+
Params: id(string, req), agent_id(string, optional), reason(string), error_code(string \u2014 e.g. 'TIMEOUT'), retry(boolean \u2014 create new pending copy), retry_after(ISO date)
|
|
8308
|
+
Example: {id: 'a1b2c3d4', reason: 'Build timeout', error_code: 'TIMEOUT', retry: true}`,
|
|
8309
|
+
add_dependency: `Add a dependency: task_id depends on depends_on. Prevents cycles via BFS.
|
|
8310
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
8311
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
8312
|
+
remove_dependency: `Remove a dependency link between two tasks.
|
|
8313
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
8314
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
8315
|
+
add_comment: `Add a comment/note to a task. Comments are append-only.
|
|
8316
|
+
Params: task_id(string, req), content(string, req), agent_id(string), session_id(string)
|
|
8317
|
+
Example: {task_id: 'a1b2c3d4', content: 'Blocked by API rate limit'}`,
|
|
8318
|
+
create_project: `Register a new project. Auto-generates task prefix for short IDs (e.g. APP-00001).
|
|
8319
|
+
Params: name(string, req), path(string, req \u2014 unique absolute path), description(string), task_list_id(string)
|
|
8320
|
+
Example: {name: 'my-app', path: '/Users/dev/my-app'}`,
|
|
8321
|
+
list_projects: "List all registered projects. No params.",
|
|
8322
|
+
create_plan: `Create a plan to group related tasks.
|
|
8323
|
+
Params: name(string, req), project_id(string), description(string), status(active|completed|archived, default:active), task_list_id(string), agent_id(string)
|
|
8324
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
8325
|
+
list_plans: `List all plans, optionally filtered by project.
|
|
8326
|
+
Params: project_id(string)
|
|
8327
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8328
|
+
get_plan: `Get plan details (name, status, description, timestamps).
|
|
8329
|
+
Params: id(string, req)
|
|
8330
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8331
|
+
update_plan: `Update plan fields.
|
|
8332
|
+
Params: id(string, req), name(string), description(string), status(active|completed|archived), task_list_id(string), agent_id(string)
|
|
8333
|
+
Example: {id: 'a1b2c3d4', status: 'completed'}`,
|
|
8334
|
+
delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
|
|
8335
|
+
Params: id(string, req)
|
|
8336
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8337
|
+
register_agent: `Register an agent (idempotent by name). Returns existing agent if name matches.
|
|
8338
|
+
Params: name(string, req \u2014 e.g. 'maximus'), description(string)
|
|
8339
|
+
Example: {name: 'maximus', description: 'Backend developer'}`,
|
|
8340
|
+
list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
|
|
8341
|
+
get_agent: `Get agent details by ID or name. Provide one of id or name.
|
|
8342
|
+
Params: id(string), name(string)
|
|
8343
|
+
Example: {name: 'maximus'}`,
|
|
8344
|
+
rename_agent: `Rename an agent. Resolve by id or current name.
|
|
8345
|
+
Params: id(string), name(string \u2014 current name), new_name(string, req)
|
|
8346
|
+
Example: {name: 'old-name', new_name: 'new-name'}`,
|
|
8347
|
+
delete_agent: `Delete an agent permanently. Resolve by id or name.
|
|
8348
|
+
Params: id(string), name(string)
|
|
8349
|
+
Example: {name: 'maximus'}`,
|
|
8350
|
+
get_my_tasks: `Get all tasks assigned to/created by an agent, with stats (pending/active/done/rate).
|
|
8351
|
+
Params: agent_name(string, req)
|
|
8352
|
+
Example: {agent_name: 'maximus'}`,
|
|
8353
|
+
get_org_chart: "Get agent org chart showing reporting hierarchy. No params.",
|
|
8354
|
+
set_reports_to: `Set who an agent reports to in the org chart. Omit manager_name for top-level.
|
|
8355
|
+
Params: agent_name(string, req), manager_name(string, optional)
|
|
8356
|
+
Example: {agent_name: 'brutus', manager_name: 'maximus'}`,
|
|
8357
|
+
create_task_list: `Create a task list \u2014 a container/folder for organizing tasks.
|
|
8358
|
+
Params: name(string, req), slug(string \u2014 auto-generated if omitted), project_id(string), description(string)
|
|
8359
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
8360
|
+
list_task_lists: `List all task lists, optionally filtered by project.
|
|
8361
|
+
Params: project_id(string)
|
|
8362
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8363
|
+
get_task_list: `Get task list details (name, slug, project, metadata).
|
|
8364
|
+
Params: id(string, req)
|
|
8365
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8366
|
+
update_task_list: `Update a task list's name or description.
|
|
8367
|
+
Params: id(string, req), name(string), description(string)
|
|
8368
|
+
Example: {id: 'a1b2c3d4', name: 'Sprint 2'}`,
|
|
8369
|
+
delete_task_list: `Delete a task list. Tasks are orphaned (not deleted).
|
|
8370
|
+
Params: id(string, req)
|
|
8371
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8372
|
+
search_tasks: `Full-text search across task titles, descriptions, and tags. Supports filters.
|
|
8373
|
+
Params: query(string, req), project_id(string), task_list_id(string), status(string|string[]), priority(string|string[]), assigned_to(string), agent_id(string), created_after(ISO date), updated_after(ISO date), has_dependencies(boolean), is_blocked(boolean)
|
|
8374
|
+
Example: {query: 'auth bug', status: 'pending'}`,
|
|
8375
|
+
get_next_task: `Get the optimal next task to work on \u2014 finds highest-priority pending task that is not blocked or locked. Prefers tasks assigned to the given agent.
|
|
8376
|
+
Params: agent_id(string \u2014 prefers your tasks), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
8377
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
8378
|
+
claim_next_task: `Atomically find the best pending task, lock it, and start it \u2014 one call instead of get_next_task + start_task. Eliminates race conditions between agents.
|
|
8379
|
+
Params: agent_id(string, req \u2014 used for lock and assignment), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
8380
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
8381
|
+
sync: `Sync tasks between local DB and agent task list (e.g. Claude Code).
|
|
8382
|
+
Params: agent(string, default:'claude'), task_list_id(string), all_agents(boolean), project_id(string), direction(push|pull|both, default:both), prefer(local|remote, default:remote)
|
|
8383
|
+
Example: {agent: 'claude', direction: 'push'}`,
|
|
8384
|
+
clone_task: `Duplicate a task with optional field overrides. Creates new independent copy.
|
|
8385
|
+
Params: task_id(string, req), title(string), description(string), priority(low|medium|high|critical), status(pending|in_progress|completed|failed|cancelled), project_id(string), plan_id(string), task_list_id(string), assigned_to(string), tags(string[]), estimated_minutes(number)
|
|
8386
|
+
Example: {task_id: 'a1b2c3d4', title: 'Cloned task', assigned_to: 'brutus'}`,
|
|
8387
|
+
move_task: `Move a task to a different list, project, or plan.
|
|
8388
|
+
Params: task_id(string, req), task_list_id(string|null), project_id(string|null), plan_id(string|null)
|
|
8389
|
+
Example: {task_id: 'a1b2c3d4', task_list_id: 'e5f6g7h8'}`,
|
|
8390
|
+
bulk_update_tasks: `Update multiple tasks at once with the same changes.
|
|
8391
|
+
Params: task_ids(string[], req), status(pending|in_progress|completed|failed|cancelled), priority(low|medium|high|critical), assigned_to(string), tags(string[])
|
|
8392
|
+
Example: {task_ids: ['abc12345', 'def67890'], status: 'completed'}`,
|
|
8393
|
+
bulk_create_tasks: `Create multiple tasks atomically. Supports inter-task dependencies via temp_id references.
|
|
8394
|
+
Params: tasks(array, req \u2014 [{temp_id, title, description, priority, status, project_id, plan_id, task_list_id, agent_id, assigned_to, tags, estimated_minutes, depends_on_temp_ids}]), project_id(string \u2014 default for all), plan_id(string \u2014 default for all), task_list_id(string \u2014 default for all)
|
|
8395
|
+
Example: {tasks: [{temp_id: 'a', title: 'First'}, {temp_id: 'b', title: 'Second', depends_on_temp_ids: ['a']}]}`,
|
|
8396
|
+
get_task_stats: `Get task analytics: counts by status, priority, agent, and completion rate. All via SQL.
|
|
8397
|
+
Params: project_id(string), task_list_id(string), agent_id(string)
|
|
8398
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8399
|
+
get_task_graph: `Get full dependency tree for a task \u2014 upstream blockers and downstream dependents.
|
|
8400
|
+
Params: id(string, req), direction(up|down|both, default:both)
|
|
8401
|
+
Example: {id: 'a1b2c3d4', direction: 'up'}`,
|
|
8402
|
+
get_task_history: `Get audit log for a task \u2014 all field changes with timestamps and actors.
|
|
8403
|
+
Params: task_id(string, req)
|
|
8404
|
+
Example: {task_id: 'a1b2c3d4'}`,
|
|
8405
|
+
get_recent_activity: `Get recent task changes across all tasks \u2014 global activity feed.
|
|
8406
|
+
Params: limit(number, default:50)
|
|
8407
|
+
Example: {limit: 20}`,
|
|
8408
|
+
create_webhook: `Register a webhook for task change events.
|
|
8409
|
+
Params: url(string, req), events(string[] \u2014 empty=all), secret(string \u2014 HMAC signing)
|
|
8410
|
+
Example: {url: 'https://example.com/hook', events: ['task.created', 'task.completed']}`,
|
|
8411
|
+
list_webhooks: "List all registered webhooks. No params.",
|
|
8412
|
+
delete_webhook: `Delete a webhook by ID.
|
|
8413
|
+
Params: id(string, req)
|
|
8414
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8415
|
+
create_template: `Create a reusable task template.
|
|
8416
|
+
Params: name(string, req), title_pattern(string, req \u2014 e.g. 'Fix: {description}'), description(string), priority(low|medium|high|critical), tags(string[]), project_id(string), plan_id(string)
|
|
8417
|
+
Example: {name: 'Bug Report', title_pattern: 'Bug: {description}', priority: 'high', tags: ['bug']}`,
|
|
8418
|
+
list_templates: "List all task templates. No params.",
|
|
8419
|
+
create_task_from_template: `Create a task from a template with optional overrides.
|
|
8420
|
+
Params: template_id(string, req), title(string), description(string), priority(low|medium|high|critical), assigned_to(string), project_id(string)
|
|
8421
|
+
Example: {template_id: 'a1b2c3d4', assigned_to: 'maximus'}`,
|
|
8422
|
+
delete_template: `Delete a task template.
|
|
8423
|
+
Params: id(string, req)
|
|
8424
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8425
|
+
get_active_work: `See all in-progress tasks and who is working on them.
|
|
8426
|
+
Params: project_id(string, optional), task_list_id(string, optional)
|
|
8427
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8428
|
+
get_tasks_changed_since: `Get tasks modified after a timestamp \u2014 incremental delta sync.
|
|
8429
|
+
Params: since(string, req \u2014 ISO date), project_id(string, optional), task_list_id(string, optional)
|
|
8430
|
+
Example: {since: '2026-03-14T10:00:00Z'}`,
|
|
8431
|
+
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
8432
|
+
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
8433
|
+
Example: {stale_minutes: 60, project_id: 'a1b2c3d4'}`,
|
|
8434
|
+
search_tools: `List all tool names or filter by substring.
|
|
8435
|
+
Params: query(string, optional)
|
|
8436
|
+
Example: {query: 'task'}`,
|
|
8437
|
+
describe_tools: `Get detailed descriptions and parameter info for tools by name.
|
|
8438
|
+
Params: names(string[], req)
|
|
8439
|
+
Example: {names: ['create_task', 'update_task']}`
|
|
7271
8440
|
};
|
|
7272
|
-
const result = names.map((n) => `${n}: ${descriptions[n] || "
|
|
8441
|
+
const result = names.map((n) => `${n}: ${descriptions[n] || "Unknown tool. Use search_tools to list available tools."}`).join(`
|
|
8442
|
+
|
|
7273
8443
|
`);
|
|
7274
8444
|
return { content: [{ type: "text", text: result }] };
|
|
7275
8445
|
});
|