@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/cli/index.js
CHANGED
|
@@ -2258,6 +2258,8 @@ function ensureSchema(db) {
|
|
|
2258
2258
|
ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
|
|
2259
2259
|
ensureColumn("tasks", "approved_by", "TEXT");
|
|
2260
2260
|
ensureColumn("tasks", "approved_at", "TEXT");
|
|
2261
|
+
ensureColumn("tasks", "recurrence_rule", "TEXT");
|
|
2262
|
+
ensureColumn("tasks", "recurrence_parent_id", "TEXT REFERENCES tasks(id) ON DELETE SET NULL");
|
|
2261
2263
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
2262
2264
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
2263
2265
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -2282,6 +2284,8 @@ function ensureSchema(db) {
|
|
|
2282
2284
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
|
|
2283
2285
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
|
|
2284
2286
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
|
|
2287
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id)");
|
|
2288
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL");
|
|
2285
2289
|
}
|
|
2286
2290
|
function backfillTaskTags(db) {
|
|
2287
2291
|
try {
|
|
@@ -2581,17 +2585,26 @@ var init_database = __esm(() => {
|
|
|
2581
2585
|
ALTER TABLE agents ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
|
|
2582
2586
|
ALTER TABLE projects ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
|
|
2583
2587
|
INSERT OR IGNORE INTO _migrations (id) VALUES (12);
|
|
2588
|
+
`,
|
|
2589
|
+
`
|
|
2590
|
+
ALTER TABLE tasks ADD COLUMN recurrence_rule TEXT;
|
|
2591
|
+
ALTER TABLE tasks ADD COLUMN recurrence_parent_id TEXT REFERENCES tasks(id) ON DELETE SET NULL;
|
|
2592
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id);
|
|
2593
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL;
|
|
2594
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (13);
|
|
2584
2595
|
`
|
|
2585
2596
|
];
|
|
2586
2597
|
});
|
|
2587
2598
|
|
|
2588
2599
|
// src/types/index.ts
|
|
2589
|
-
var VersionConflictError, TaskNotFoundError, ProjectNotFoundError, PlanNotFoundError, LockError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError;
|
|
2600
|
+
var VersionConflictError, TaskNotFoundError, ProjectNotFoundError, PlanNotFoundError, LockError, AgentNotFoundError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError;
|
|
2590
2601
|
var init_types = __esm(() => {
|
|
2591
2602
|
VersionConflictError = class VersionConflictError extends Error {
|
|
2592
2603
|
taskId;
|
|
2593
2604
|
expectedVersion;
|
|
2594
2605
|
actualVersion;
|
|
2606
|
+
static code = "VERSION_CONFLICT";
|
|
2607
|
+
static suggestion = "Fetch the task with get_task to get the current version before updating.";
|
|
2595
2608
|
constructor(taskId, expectedVersion, actualVersion) {
|
|
2596
2609
|
super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
|
|
2597
2610
|
this.taskId = taskId;
|
|
@@ -2602,6 +2615,8 @@ var init_types = __esm(() => {
|
|
|
2602
2615
|
};
|
|
2603
2616
|
TaskNotFoundError = class TaskNotFoundError extends Error {
|
|
2604
2617
|
taskId;
|
|
2618
|
+
static code = "TASK_NOT_FOUND";
|
|
2619
|
+
static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
|
|
2605
2620
|
constructor(taskId) {
|
|
2606
2621
|
super(`Task not found: ${taskId}`);
|
|
2607
2622
|
this.taskId = taskId;
|
|
@@ -2610,6 +2625,8 @@ var init_types = __esm(() => {
|
|
|
2610
2625
|
};
|
|
2611
2626
|
ProjectNotFoundError = class ProjectNotFoundError extends Error {
|
|
2612
2627
|
projectId;
|
|
2628
|
+
static code = "PROJECT_NOT_FOUND";
|
|
2629
|
+
static suggestion = "Use list_projects to see available projects.";
|
|
2613
2630
|
constructor(projectId) {
|
|
2614
2631
|
super(`Project not found: ${projectId}`);
|
|
2615
2632
|
this.projectId = projectId;
|
|
@@ -2618,6 +2635,8 @@ var init_types = __esm(() => {
|
|
|
2618
2635
|
};
|
|
2619
2636
|
PlanNotFoundError = class PlanNotFoundError extends Error {
|
|
2620
2637
|
planId;
|
|
2638
|
+
static code = "PLAN_NOT_FOUND";
|
|
2639
|
+
static suggestion = "Use list_plans to see available plans.";
|
|
2621
2640
|
constructor(planId) {
|
|
2622
2641
|
super(`Plan not found: ${planId}`);
|
|
2623
2642
|
this.planId = planId;
|
|
@@ -2627,6 +2646,8 @@ var init_types = __esm(() => {
|
|
|
2627
2646
|
LockError = class LockError extends Error {
|
|
2628
2647
|
taskId;
|
|
2629
2648
|
lockedBy;
|
|
2649
|
+
static code = "LOCK_ERROR";
|
|
2650
|
+
static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
|
|
2630
2651
|
constructor(taskId, lockedBy) {
|
|
2631
2652
|
super(`Task ${taskId} is locked by ${lockedBy}`);
|
|
2632
2653
|
this.taskId = taskId;
|
|
@@ -2634,8 +2655,20 @@ var init_types = __esm(() => {
|
|
|
2634
2655
|
this.name = "LockError";
|
|
2635
2656
|
}
|
|
2636
2657
|
};
|
|
2658
|
+
AgentNotFoundError = class AgentNotFoundError extends Error {
|
|
2659
|
+
agentId;
|
|
2660
|
+
static code = "AGENT_NOT_FOUND";
|
|
2661
|
+
static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
|
|
2662
|
+
constructor(agentId) {
|
|
2663
|
+
super(`Agent not found: ${agentId}`);
|
|
2664
|
+
this.agentId = agentId;
|
|
2665
|
+
this.name = "AgentNotFoundError";
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2637
2668
|
TaskListNotFoundError = class TaskListNotFoundError extends Error {
|
|
2638
2669
|
taskListId;
|
|
2670
|
+
static code = "TASK_LIST_NOT_FOUND";
|
|
2671
|
+
static suggestion = "Use list_task_lists to see available lists.";
|
|
2639
2672
|
constructor(taskListId) {
|
|
2640
2673
|
super(`Task list not found: ${taskListId}`);
|
|
2641
2674
|
this.taskListId = taskListId;
|
|
@@ -2645,6 +2678,8 @@ var init_types = __esm(() => {
|
|
|
2645
2678
|
DependencyCycleError = class DependencyCycleError extends Error {
|
|
2646
2679
|
taskId;
|
|
2647
2680
|
dependsOn;
|
|
2681
|
+
static code = "DEPENDENCY_CYCLE";
|
|
2682
|
+
static suggestion = "Check the dependency chain with get_task to avoid circular references.";
|
|
2648
2683
|
constructor(taskId, dependsOn) {
|
|
2649
2684
|
super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
|
|
2650
2685
|
this.taskId = taskId;
|
|
@@ -2655,6 +2690,8 @@ var init_types = __esm(() => {
|
|
|
2655
2690
|
CompletionGuardError = class CompletionGuardError extends Error {
|
|
2656
2691
|
reason;
|
|
2657
2692
|
retryAfterSeconds;
|
|
2693
|
+
static code = "COMPLETION_BLOCKED";
|
|
2694
|
+
static suggestion = "Wait for the cooldown period, then retry.";
|
|
2658
2695
|
constructor(reason, retryAfterSeconds) {
|
|
2659
2696
|
super(reason);
|
|
2660
2697
|
this.reason = reason;
|
|
@@ -2974,6 +3011,96 @@ var init_audit = __esm(() => {
|
|
|
2974
3011
|
init_database();
|
|
2975
3012
|
});
|
|
2976
3013
|
|
|
3014
|
+
// src/lib/recurrence.ts
|
|
3015
|
+
function parseRecurrenceRule(rule) {
|
|
3016
|
+
const normalized = rule.trim().toLowerCase();
|
|
3017
|
+
if (normalized === "every weekday" || normalized === "every weekdays") {
|
|
3018
|
+
return { type: "specific_days", days: [1, 2, 3, 4, 5] };
|
|
3019
|
+
}
|
|
3020
|
+
if (normalized === "every day" || normalized === "daily") {
|
|
3021
|
+
return { type: "interval", interval: 1, unit: "day" };
|
|
3022
|
+
}
|
|
3023
|
+
if (normalized === "every week" || normalized === "weekly") {
|
|
3024
|
+
return { type: "interval", interval: 1, unit: "week" };
|
|
3025
|
+
}
|
|
3026
|
+
if (normalized === "every month" || normalized === "monthly") {
|
|
3027
|
+
return { type: "interval", interval: 1, unit: "month" };
|
|
3028
|
+
}
|
|
3029
|
+
const intervalMatch = normalized.match(/^every\s+(\d+)\s+(day|week|month)s?$/);
|
|
3030
|
+
if (intervalMatch) {
|
|
3031
|
+
return {
|
|
3032
|
+
type: "interval",
|
|
3033
|
+
interval: parseInt(intervalMatch[1], 10),
|
|
3034
|
+
unit: intervalMatch[2]
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
const daysMatch = normalized.match(/^every\s+(.+)$/);
|
|
3038
|
+
if (daysMatch) {
|
|
3039
|
+
const dayParts = daysMatch[1].split(/[,\s]+/).map((d) => d.trim()).filter(Boolean);
|
|
3040
|
+
const days = [];
|
|
3041
|
+
for (const part of dayParts) {
|
|
3042
|
+
const dayNum = DAY_NAMES[part];
|
|
3043
|
+
if (dayNum !== undefined) {
|
|
3044
|
+
days.push(dayNum);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
if (days.length > 0) {
|
|
3048
|
+
return { type: "specific_days", days: days.sort((a, b) => a - b) };
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
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"`);
|
|
3052
|
+
}
|
|
3053
|
+
function nextOccurrence(rule, from) {
|
|
3054
|
+
const parsed = parseRecurrenceRule(rule);
|
|
3055
|
+
const base = from || new Date;
|
|
3056
|
+
if (parsed.type === "interval") {
|
|
3057
|
+
const next = new Date(base);
|
|
3058
|
+
if (parsed.unit === "day") {
|
|
3059
|
+
next.setDate(next.getDate() + parsed.interval);
|
|
3060
|
+
} else if (parsed.unit === "week") {
|
|
3061
|
+
next.setDate(next.getDate() + parsed.interval * 7);
|
|
3062
|
+
} else if (parsed.unit === "month") {
|
|
3063
|
+
next.setMonth(next.getMonth() + parsed.interval);
|
|
3064
|
+
}
|
|
3065
|
+
return next.toISOString();
|
|
3066
|
+
}
|
|
3067
|
+
if (parsed.type === "specific_days") {
|
|
3068
|
+
const currentDay = base.getDay();
|
|
3069
|
+
const days = parsed.days;
|
|
3070
|
+
let daysToAdd = Infinity;
|
|
3071
|
+
for (const day of days) {
|
|
3072
|
+
let diff = day - currentDay;
|
|
3073
|
+
if (diff <= 0)
|
|
3074
|
+
diff += 7;
|
|
3075
|
+
if (diff < daysToAdd)
|
|
3076
|
+
daysToAdd = diff;
|
|
3077
|
+
}
|
|
3078
|
+
const next = new Date(base);
|
|
3079
|
+
next.setDate(next.getDate() + daysToAdd);
|
|
3080
|
+
return next.toISOString();
|
|
3081
|
+
}
|
|
3082
|
+
throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
|
|
3083
|
+
}
|
|
3084
|
+
var DAY_NAMES;
|
|
3085
|
+
var init_recurrence = __esm(() => {
|
|
3086
|
+
DAY_NAMES = {
|
|
3087
|
+
sunday: 0,
|
|
3088
|
+
sun: 0,
|
|
3089
|
+
monday: 1,
|
|
3090
|
+
mon: 1,
|
|
3091
|
+
tuesday: 2,
|
|
3092
|
+
tue: 2,
|
|
3093
|
+
wednesday: 3,
|
|
3094
|
+
wed: 3,
|
|
3095
|
+
thursday: 4,
|
|
3096
|
+
thu: 4,
|
|
3097
|
+
friday: 5,
|
|
3098
|
+
fri: 5,
|
|
3099
|
+
saturday: 6,
|
|
3100
|
+
sat: 6
|
|
3101
|
+
};
|
|
3102
|
+
});
|
|
3103
|
+
|
|
2977
3104
|
// src/db/tasks.ts
|
|
2978
3105
|
function rowToTask(row) {
|
|
2979
3106
|
return {
|
|
@@ -3005,8 +3132,8 @@ function createTask(input, db) {
|
|
|
3005
3132
|
const tags = input.tags || [];
|
|
3006
3133
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
3007
3134
|
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
3008
|
-
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)
|
|
3009
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3135
|
+
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)
|
|
3136
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3010
3137
|
id,
|
|
3011
3138
|
shortId,
|
|
3012
3139
|
input.project_id || null,
|
|
@@ -3029,7 +3156,9 @@ function createTask(input, db) {
|
|
|
3029
3156
|
input.estimated_minutes || null,
|
|
3030
3157
|
input.requires_approval ? 1 : 0,
|
|
3031
3158
|
null,
|
|
3032
|
-
null
|
|
3159
|
+
null,
|
|
3160
|
+
input.recurrence_rule || null,
|
|
3161
|
+
input.recurrence_parent_id || null
|
|
3033
3162
|
]);
|
|
3034
3163
|
if (tags.length > 0) {
|
|
3035
3164
|
insertTaskTags(id, tags, d);
|
|
@@ -3129,6 +3258,11 @@ function listTasks(filter = {}, db) {
|
|
|
3129
3258
|
conditions.push("task_list_id = ?");
|
|
3130
3259
|
params.push(filter.task_list_id);
|
|
3131
3260
|
}
|
|
3261
|
+
if (filter.has_recurrence === true) {
|
|
3262
|
+
conditions.push("recurrence_rule IS NOT NULL");
|
|
3263
|
+
} else if (filter.has_recurrence === false) {
|
|
3264
|
+
conditions.push("recurrence_rule IS NULL");
|
|
3265
|
+
}
|
|
3132
3266
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3133
3267
|
let limitClause = "";
|
|
3134
3268
|
if (filter.limit) {
|
|
@@ -3144,6 +3278,69 @@ function listTasks(filter = {}, db) {
|
|
|
3144
3278
|
created_at DESC${limitClause}`).all(...params);
|
|
3145
3279
|
return rows.map(rowToTask);
|
|
3146
3280
|
}
|
|
3281
|
+
function countTasks(filter = {}, db) {
|
|
3282
|
+
const d = db || getDatabase();
|
|
3283
|
+
const conditions = [];
|
|
3284
|
+
const params = [];
|
|
3285
|
+
if (filter.project_id) {
|
|
3286
|
+
conditions.push("project_id = ?");
|
|
3287
|
+
params.push(filter.project_id);
|
|
3288
|
+
}
|
|
3289
|
+
if (filter.parent_id !== undefined) {
|
|
3290
|
+
if (filter.parent_id === null) {
|
|
3291
|
+
conditions.push("parent_id IS NULL");
|
|
3292
|
+
} else {
|
|
3293
|
+
conditions.push("parent_id = ?");
|
|
3294
|
+
params.push(filter.parent_id);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
if (filter.status) {
|
|
3298
|
+
if (Array.isArray(filter.status)) {
|
|
3299
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
3300
|
+
params.push(...filter.status);
|
|
3301
|
+
} else {
|
|
3302
|
+
conditions.push("status = ?");
|
|
3303
|
+
params.push(filter.status);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
if (filter.priority) {
|
|
3307
|
+
if (Array.isArray(filter.priority)) {
|
|
3308
|
+
conditions.push(`priority IN (${filter.priority.map(() => "?").join(",")})`);
|
|
3309
|
+
params.push(...filter.priority);
|
|
3310
|
+
} else {
|
|
3311
|
+
conditions.push("priority = ?");
|
|
3312
|
+
params.push(filter.priority);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
if (filter.assigned_to) {
|
|
3316
|
+
conditions.push("assigned_to = ?");
|
|
3317
|
+
params.push(filter.assigned_to);
|
|
3318
|
+
}
|
|
3319
|
+
if (filter.agent_id) {
|
|
3320
|
+
conditions.push("agent_id = ?");
|
|
3321
|
+
params.push(filter.agent_id);
|
|
3322
|
+
}
|
|
3323
|
+
if (filter.session_id) {
|
|
3324
|
+
conditions.push("session_id = ?");
|
|
3325
|
+
params.push(filter.session_id);
|
|
3326
|
+
}
|
|
3327
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
3328
|
+
const placeholders = filter.tags.map(() => "?").join(",");
|
|
3329
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
3330
|
+
params.push(...filter.tags);
|
|
3331
|
+
}
|
|
3332
|
+
if (filter.plan_id) {
|
|
3333
|
+
conditions.push("plan_id = ?");
|
|
3334
|
+
params.push(filter.plan_id);
|
|
3335
|
+
}
|
|
3336
|
+
if (filter.task_list_id) {
|
|
3337
|
+
conditions.push("task_list_id = ?");
|
|
3338
|
+
params.push(filter.task_list_id);
|
|
3339
|
+
}
|
|
3340
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3341
|
+
const row = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
3342
|
+
return row.count;
|
|
3343
|
+
}
|
|
3147
3344
|
function updateTask(id, input, db) {
|
|
3148
3345
|
const d = db || getDatabase();
|
|
3149
3346
|
const task = getTask(id, d);
|
|
@@ -3215,6 +3412,10 @@ function updateTask(id, input, db) {
|
|
|
3215
3412
|
sets.push("approved_at = ?");
|
|
3216
3413
|
params.push(now());
|
|
3217
3414
|
}
|
|
3415
|
+
if (input.recurrence_rule !== undefined) {
|
|
3416
|
+
sets.push("recurrence_rule = ?");
|
|
3417
|
+
params.push(input.recurrence_rule);
|
|
3418
|
+
}
|
|
3218
3419
|
params.push(id, input.version);
|
|
3219
3420
|
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
3220
3421
|
if (result.changes === 0) {
|
|
@@ -3288,7 +3489,7 @@ function startTask(id, agentId, db) {
|
|
|
3288
3489
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
3289
3490
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
3290
3491
|
}
|
|
3291
|
-
function completeTask(id, agentId, db,
|
|
3492
|
+
function completeTask(id, agentId, db, options) {
|
|
3292
3493
|
const d = db || getDatabase();
|
|
3293
3494
|
const task = getTask(id, d);
|
|
3294
3495
|
if (!task)
|
|
@@ -3297,7 +3498,9 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
3297
3498
|
throw new LockError(id, task.locked_by);
|
|
3298
3499
|
}
|
|
3299
3500
|
checkCompletionGuard(task, agentId || null, d);
|
|
3300
|
-
|
|
3501
|
+
const evidence = options ? { files_changed: options.files_changed, test_results: options.test_results, commit_hash: options.commit_hash, notes: options.notes } : undefined;
|
|
3502
|
+
const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes);
|
|
3503
|
+
if (hasEvidence) {
|
|
3301
3504
|
const meta2 = { ...task.metadata, _evidence: evidence };
|
|
3302
3505
|
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
|
|
3303
3506
|
}
|
|
@@ -3305,7 +3508,14 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
3305
3508
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
3306
3509
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
3307
3510
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
3308
|
-
|
|
3511
|
+
let spawnedTask = null;
|
|
3512
|
+
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
3513
|
+
spawnedTask = spawnNextRecurrence(task, d);
|
|
3514
|
+
}
|
|
3515
|
+
const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
|
|
3516
|
+
if (spawnedTask) {
|
|
3517
|
+
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
3518
|
+
}
|
|
3309
3519
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
3310
3520
|
}
|
|
3311
3521
|
function lockTask(id, agentId, db) {
|
|
@@ -3368,6 +3578,266 @@ function getTaskDependencies(taskId, db) {
|
|
|
3368
3578
|
const d = db || getDatabase();
|
|
3369
3579
|
return d.query("SELECT * FROM task_dependencies WHERE task_id = ?").all(taskId);
|
|
3370
3580
|
}
|
|
3581
|
+
function cloneTask(taskId, overrides, db) {
|
|
3582
|
+
const d = db || getDatabase();
|
|
3583
|
+
const source = getTask(taskId, d);
|
|
3584
|
+
if (!source)
|
|
3585
|
+
throw new TaskNotFoundError(taskId);
|
|
3586
|
+
const input = {
|
|
3587
|
+
title: overrides?.title ?? source.title,
|
|
3588
|
+
description: overrides?.description ?? source.description ?? undefined,
|
|
3589
|
+
priority: overrides?.priority ?? source.priority,
|
|
3590
|
+
project_id: overrides?.project_id ?? source.project_id ?? undefined,
|
|
3591
|
+
parent_id: overrides?.parent_id ?? source.parent_id ?? undefined,
|
|
3592
|
+
plan_id: overrides?.plan_id ?? source.plan_id ?? undefined,
|
|
3593
|
+
task_list_id: overrides?.task_list_id ?? source.task_list_id ?? undefined,
|
|
3594
|
+
status: overrides?.status ?? "pending",
|
|
3595
|
+
agent_id: overrides?.agent_id ?? source.agent_id ?? undefined,
|
|
3596
|
+
assigned_to: overrides?.assigned_to ?? source.assigned_to ?? undefined,
|
|
3597
|
+
tags: overrides?.tags ?? source.tags,
|
|
3598
|
+
metadata: overrides?.metadata ?? source.metadata,
|
|
3599
|
+
estimated_minutes: overrides?.estimated_minutes ?? source.estimated_minutes ?? undefined,
|
|
3600
|
+
recurrence_rule: overrides?.recurrence_rule ?? source.recurrence_rule ?? undefined
|
|
3601
|
+
};
|
|
3602
|
+
return createTask(input, d);
|
|
3603
|
+
}
|
|
3604
|
+
function getTaskGraph(taskId, direction = "both", db) {
|
|
3605
|
+
const d = db || getDatabase();
|
|
3606
|
+
const task = getTask(taskId, d);
|
|
3607
|
+
if (!task)
|
|
3608
|
+
throw new TaskNotFoundError(taskId);
|
|
3609
|
+
function toNode(t) {
|
|
3610
|
+
const deps = getTaskDependencies(t.id, d);
|
|
3611
|
+
const hasUnfinishedDeps = deps.some((dep) => {
|
|
3612
|
+
const depTask = getTask(dep.depends_on, d);
|
|
3613
|
+
return depTask && depTask.status !== "completed";
|
|
3614
|
+
});
|
|
3615
|
+
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
3616
|
+
}
|
|
3617
|
+
function buildUp(id, visited) {
|
|
3618
|
+
if (visited.has(id))
|
|
3619
|
+
return [];
|
|
3620
|
+
visited.add(id);
|
|
3621
|
+
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
3622
|
+
return deps.map((dep) => {
|
|
3623
|
+
const depTask = getTask(dep.depends_on, d);
|
|
3624
|
+
if (!depTask)
|
|
3625
|
+
return null;
|
|
3626
|
+
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
3627
|
+
}).filter(Boolean);
|
|
3628
|
+
}
|
|
3629
|
+
function buildDown(id, visited) {
|
|
3630
|
+
if (visited.has(id))
|
|
3631
|
+
return [];
|
|
3632
|
+
visited.add(id);
|
|
3633
|
+
const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
|
|
3634
|
+
return dependents.map((dep) => {
|
|
3635
|
+
const depTask = getTask(dep.task_id, d);
|
|
3636
|
+
if (!depTask)
|
|
3637
|
+
return null;
|
|
3638
|
+
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
3639
|
+
}).filter(Boolean);
|
|
3640
|
+
}
|
|
3641
|
+
const rootNode = toNode(task);
|
|
3642
|
+
const depends_on = direction === "up" || direction === "both" ? buildUp(taskId, new Set) : [];
|
|
3643
|
+
const blocks = direction === "down" || direction === "both" ? buildDown(taskId, new Set) : [];
|
|
3644
|
+
return { task: rootNode, depends_on, blocks };
|
|
3645
|
+
}
|
|
3646
|
+
function moveTask(taskId, target, db) {
|
|
3647
|
+
const d = db || getDatabase();
|
|
3648
|
+
const task = getTask(taskId, d);
|
|
3649
|
+
if (!task)
|
|
3650
|
+
throw new TaskNotFoundError(taskId);
|
|
3651
|
+
const sets = ["updated_at = ?", "version = version + 1"];
|
|
3652
|
+
const params = [now()];
|
|
3653
|
+
if (target.task_list_id !== undefined) {
|
|
3654
|
+
sets.push("task_list_id = ?");
|
|
3655
|
+
params.push(target.task_list_id);
|
|
3656
|
+
}
|
|
3657
|
+
if (target.project_id !== undefined) {
|
|
3658
|
+
sets.push("project_id = ?");
|
|
3659
|
+
params.push(target.project_id);
|
|
3660
|
+
}
|
|
3661
|
+
if (target.plan_id !== undefined) {
|
|
3662
|
+
sets.push("plan_id = ?");
|
|
3663
|
+
params.push(target.plan_id);
|
|
3664
|
+
}
|
|
3665
|
+
params.push(taskId);
|
|
3666
|
+
d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3667
|
+
return getTask(taskId, d);
|
|
3668
|
+
}
|
|
3669
|
+
function spawnNextRecurrence(completedTask, db) {
|
|
3670
|
+
const dueAt = nextOccurrence(completedTask.recurrence_rule, new Date);
|
|
3671
|
+
let title = completedTask.title;
|
|
3672
|
+
if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
|
|
3673
|
+
title = title.slice(completedTask.short_id.length + 2);
|
|
3674
|
+
}
|
|
3675
|
+
const recurrenceParentId = completedTask.recurrence_parent_id || completedTask.id;
|
|
3676
|
+
return createTask({
|
|
3677
|
+
title,
|
|
3678
|
+
description: completedTask.description ?? undefined,
|
|
3679
|
+
priority: completedTask.priority,
|
|
3680
|
+
project_id: completedTask.project_id ?? undefined,
|
|
3681
|
+
task_list_id: completedTask.task_list_id ?? undefined,
|
|
3682
|
+
plan_id: completedTask.plan_id ?? undefined,
|
|
3683
|
+
assigned_to: completedTask.assigned_to ?? undefined,
|
|
3684
|
+
tags: completedTask.tags,
|
|
3685
|
+
metadata: completedTask.metadata,
|
|
3686
|
+
estimated_minutes: completedTask.estimated_minutes ?? undefined,
|
|
3687
|
+
recurrence_rule: completedTask.recurrence_rule,
|
|
3688
|
+
recurrence_parent_id: recurrenceParentId,
|
|
3689
|
+
due_at: dueAt
|
|
3690
|
+
}, db);
|
|
3691
|
+
}
|
|
3692
|
+
function claimNextTask(agentId, filters, db) {
|
|
3693
|
+
const d = db || getDatabase();
|
|
3694
|
+
const tx = d.transaction(() => {
|
|
3695
|
+
const task = getNextTask(agentId, filters, d);
|
|
3696
|
+
if (!task)
|
|
3697
|
+
return null;
|
|
3698
|
+
return startTask(task.id, agentId, d);
|
|
3699
|
+
});
|
|
3700
|
+
return tx();
|
|
3701
|
+
}
|
|
3702
|
+
function getNextTask(agentId, filters, db) {
|
|
3703
|
+
const d = db || getDatabase();
|
|
3704
|
+
clearExpiredLocks(d);
|
|
3705
|
+
const conditions = ["status = 'pending'", "(locked_by IS NULL OR locked_at < ?)"];
|
|
3706
|
+
const params = [lockExpiryCutoff()];
|
|
3707
|
+
if (filters?.project_id) {
|
|
3708
|
+
conditions.push("project_id = ?");
|
|
3709
|
+
params.push(filters.project_id);
|
|
3710
|
+
}
|
|
3711
|
+
if (filters?.task_list_id) {
|
|
3712
|
+
conditions.push("task_list_id = ?");
|
|
3713
|
+
params.push(filters.task_list_id);
|
|
3714
|
+
}
|
|
3715
|
+
if (filters?.plan_id) {
|
|
3716
|
+
conditions.push("plan_id = ?");
|
|
3717
|
+
params.push(filters.plan_id);
|
|
3718
|
+
}
|
|
3719
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
3720
|
+
const placeholders = filters.tags.map(() => "?").join(",");
|
|
3721
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
3722
|
+
params.push(...filters.tags);
|
|
3723
|
+
}
|
|
3724
|
+
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')");
|
|
3725
|
+
const where = conditions.join(" AND ");
|
|
3726
|
+
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
3727
|
+
if (agentId) {
|
|
3728
|
+
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
3729
|
+
params.push(agentId);
|
|
3730
|
+
}
|
|
3731
|
+
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`;
|
|
3732
|
+
const row = d.query(sql).get(...params);
|
|
3733
|
+
return row ? rowToTask(row) : null;
|
|
3734
|
+
}
|
|
3735
|
+
function getActiveWork(filters, db) {
|
|
3736
|
+
const d = db || getDatabase();
|
|
3737
|
+
clearExpiredLocks(d);
|
|
3738
|
+
const conditions = ["status = 'in_progress'"];
|
|
3739
|
+
const params = [];
|
|
3740
|
+
if (filters?.project_id) {
|
|
3741
|
+
conditions.push("project_id = ?");
|
|
3742
|
+
params.push(filters.project_id);
|
|
3743
|
+
}
|
|
3744
|
+
if (filters?.task_list_id) {
|
|
3745
|
+
conditions.push("task_list_id = ?");
|
|
3746
|
+
params.push(filters.task_list_id);
|
|
3747
|
+
}
|
|
3748
|
+
const where = conditions.join(" AND ");
|
|
3749
|
+
const rows = d.query(`SELECT id, short_id, title, priority, assigned_to, locked_by, locked_at, updated_at FROM tasks WHERE ${where} ORDER BY
|
|
3750
|
+
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
3751
|
+
updated_at DESC`).all(...params);
|
|
3752
|
+
return rows;
|
|
3753
|
+
}
|
|
3754
|
+
function getTasksChangedSince(since, filters, db) {
|
|
3755
|
+
const d = db || getDatabase();
|
|
3756
|
+
const conditions = ["updated_at > ?"];
|
|
3757
|
+
const params = [since];
|
|
3758
|
+
if (filters?.project_id) {
|
|
3759
|
+
conditions.push("project_id = ?");
|
|
3760
|
+
params.push(filters.project_id);
|
|
3761
|
+
}
|
|
3762
|
+
if (filters?.task_list_id) {
|
|
3763
|
+
conditions.push("task_list_id = ?");
|
|
3764
|
+
params.push(filters.task_list_id);
|
|
3765
|
+
}
|
|
3766
|
+
const where = conditions.join(" AND ");
|
|
3767
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at DESC`).all(...params);
|
|
3768
|
+
return rows.map(rowToTask);
|
|
3769
|
+
}
|
|
3770
|
+
function failTask(id, agentId, reason, options, db) {
|
|
3771
|
+
const d = db || getDatabase();
|
|
3772
|
+
const task = getTask(id, d);
|
|
3773
|
+
if (!task)
|
|
3774
|
+
throw new TaskNotFoundError(id);
|
|
3775
|
+
const meta = {
|
|
3776
|
+
...task.metadata,
|
|
3777
|
+
_failure: {
|
|
3778
|
+
reason: reason || "Unknown failure",
|
|
3779
|
+
error_code: options?.error_code || null,
|
|
3780
|
+
failed_by: agentId || null,
|
|
3781
|
+
failed_at: now(),
|
|
3782
|
+
retry_requested: options?.retry || false
|
|
3783
|
+
}
|
|
3784
|
+
};
|
|
3785
|
+
const timestamp = now();
|
|
3786
|
+
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
3787
|
+
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
3788
|
+
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
3789
|
+
const failedTask = {
|
|
3790
|
+
...task,
|
|
3791
|
+
status: "failed",
|
|
3792
|
+
locked_by: null,
|
|
3793
|
+
locked_at: null,
|
|
3794
|
+
metadata: meta,
|
|
3795
|
+
version: task.version + 1,
|
|
3796
|
+
updated_at: timestamp
|
|
3797
|
+
};
|
|
3798
|
+
let retryTask;
|
|
3799
|
+
if (options?.retry) {
|
|
3800
|
+
let title = task.title;
|
|
3801
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
3802
|
+
title = title.slice(task.short_id.length + 2);
|
|
3803
|
+
}
|
|
3804
|
+
retryTask = createTask({
|
|
3805
|
+
title,
|
|
3806
|
+
description: task.description ?? undefined,
|
|
3807
|
+
priority: task.priority,
|
|
3808
|
+
project_id: task.project_id ?? undefined,
|
|
3809
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
3810
|
+
plan_id: task.plan_id ?? undefined,
|
|
3811
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
3812
|
+
tags: task.tags,
|
|
3813
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
|
|
3814
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
3815
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
3816
|
+
due_at: options.retry_after || task.due_at || undefined
|
|
3817
|
+
}, d);
|
|
3818
|
+
}
|
|
3819
|
+
return { task: failedTask, retryTask };
|
|
3820
|
+
}
|
|
3821
|
+
function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
3822
|
+
const d = db || getDatabase();
|
|
3823
|
+
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
3824
|
+
const conditions = [
|
|
3825
|
+
"status = 'in_progress'",
|
|
3826
|
+
"(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
|
|
3827
|
+
];
|
|
3828
|
+
const params = [cutoff, cutoff];
|
|
3829
|
+
if (filters?.project_id) {
|
|
3830
|
+
conditions.push("project_id = ?");
|
|
3831
|
+
params.push(filters.project_id);
|
|
3832
|
+
}
|
|
3833
|
+
if (filters?.task_list_id) {
|
|
3834
|
+
conditions.push("task_list_id = ?");
|
|
3835
|
+
params.push(filters.task_list_id);
|
|
3836
|
+
}
|
|
3837
|
+
const where = conditions.join(" AND ");
|
|
3838
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
3839
|
+
return rows.map(rowToTask);
|
|
3840
|
+
}
|
|
3371
3841
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
3372
3842
|
const visited = new Set;
|
|
3373
3843
|
const queue = [dependsOn];
|
|
@@ -3385,12 +3855,98 @@ function wouldCreateCycle(taskId, dependsOn, db) {
|
|
|
3385
3855
|
}
|
|
3386
3856
|
return false;
|
|
3387
3857
|
}
|
|
3858
|
+
function getTaskStats(filters, db) {
|
|
3859
|
+
const d = db || getDatabase();
|
|
3860
|
+
const conditions = [];
|
|
3861
|
+
const params = [];
|
|
3862
|
+
if (filters?.project_id) {
|
|
3863
|
+
conditions.push("project_id = ?");
|
|
3864
|
+
params.push(filters.project_id);
|
|
3865
|
+
}
|
|
3866
|
+
if (filters?.task_list_id) {
|
|
3867
|
+
conditions.push("task_list_id = ?");
|
|
3868
|
+
params.push(filters.task_list_id);
|
|
3869
|
+
}
|
|
3870
|
+
if (filters?.agent_id) {
|
|
3871
|
+
conditions.push("(agent_id = ? OR assigned_to = ?)");
|
|
3872
|
+
params.push(filters.agent_id, filters.agent_id);
|
|
3873
|
+
}
|
|
3874
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3875
|
+
const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
3876
|
+
const statusRows = d.query(`SELECT status, COUNT(*) as count FROM tasks ${where} GROUP BY status`).all(...params);
|
|
3877
|
+
const by_status = {};
|
|
3878
|
+
for (const r of statusRows)
|
|
3879
|
+
by_status[r.status] = r.count;
|
|
3880
|
+
const priorityRows = d.query(`SELECT priority, COUNT(*) as count FROM tasks ${where} GROUP BY priority`).all(...params);
|
|
3881
|
+
const by_priority = {};
|
|
3882
|
+
for (const r of priorityRows)
|
|
3883
|
+
by_priority[r.priority] = r.count;
|
|
3884
|
+
const agentRows = d.query(`SELECT COALESCE(assigned_to, agent_id, 'unassigned') as agent, COUNT(*) as count FROM tasks ${where} GROUP BY agent`).all(...params);
|
|
3885
|
+
const by_agent = {};
|
|
3886
|
+
for (const r of agentRows)
|
|
3887
|
+
by_agent[r.agent] = r.count;
|
|
3888
|
+
const completed = by_status["completed"] || 0;
|
|
3889
|
+
const completion_rate = totalRow.count > 0 ? Math.round(completed / totalRow.count * 100) : 0;
|
|
3890
|
+
return { total: totalRow.count, by_status, by_priority, completion_rate, by_agent };
|
|
3891
|
+
}
|
|
3892
|
+
function bulkCreateTasks(inputs, db) {
|
|
3893
|
+
const d = db || getDatabase();
|
|
3894
|
+
const tempIdToRealId = new Map;
|
|
3895
|
+
const created = [];
|
|
3896
|
+
const tx = d.transaction(() => {
|
|
3897
|
+
for (const input of inputs) {
|
|
3898
|
+
const { temp_id, depends_on_temp_ids: _deps, ...createInput } = input;
|
|
3899
|
+
const task = createTask(createInput, d);
|
|
3900
|
+
if (temp_id)
|
|
3901
|
+
tempIdToRealId.set(temp_id, task.id);
|
|
3902
|
+
created.push({ temp_id: temp_id || null, id: task.id, short_id: task.short_id, title: task.title });
|
|
3903
|
+
}
|
|
3904
|
+
for (const input of inputs) {
|
|
3905
|
+
if (input.depends_on_temp_ids && input.depends_on_temp_ids.length > 0) {
|
|
3906
|
+
const taskId = input.temp_id ? tempIdToRealId.get(input.temp_id) : null;
|
|
3907
|
+
if (!taskId)
|
|
3908
|
+
continue;
|
|
3909
|
+
for (const depTempId of input.depends_on_temp_ids) {
|
|
3910
|
+
const depRealId = tempIdToRealId.get(depTempId);
|
|
3911
|
+
if (depRealId) {
|
|
3912
|
+
addDependency(taskId, depRealId, d);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
});
|
|
3918
|
+
tx();
|
|
3919
|
+
return { created };
|
|
3920
|
+
}
|
|
3921
|
+
function bulkUpdateTasks(taskIds, updates, db) {
|
|
3922
|
+
const d = db || getDatabase();
|
|
3923
|
+
let updated = 0;
|
|
3924
|
+
const failed = [];
|
|
3925
|
+
const tx = d.transaction(() => {
|
|
3926
|
+
for (const id of taskIds) {
|
|
3927
|
+
try {
|
|
3928
|
+
const task = getTask(id, d);
|
|
3929
|
+
if (!task) {
|
|
3930
|
+
failed.push({ id, error: "Task not found" });
|
|
3931
|
+
continue;
|
|
3932
|
+
}
|
|
3933
|
+
updateTask(id, { ...updates, version: task.version }, d);
|
|
3934
|
+
updated++;
|
|
3935
|
+
} catch (e) {
|
|
3936
|
+
failed.push({ id, error: e instanceof Error ? e.message : String(e) });
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
});
|
|
3940
|
+
tx();
|
|
3941
|
+
return { updated, failed };
|
|
3942
|
+
}
|
|
3388
3943
|
var init_tasks = __esm(() => {
|
|
3389
3944
|
init_types();
|
|
3390
3945
|
init_database();
|
|
3391
3946
|
init_projects();
|
|
3392
3947
|
init_completion_guard();
|
|
3393
3948
|
init_audit();
|
|
3949
|
+
init_recurrence();
|
|
3394
3950
|
});
|
|
3395
3951
|
|
|
3396
3952
|
// src/db/agents.ts
|
|
@@ -3715,19 +4271,64 @@ function rowToTask2(row) {
|
|
|
3715
4271
|
requires_approval: Boolean(row.requires_approval)
|
|
3716
4272
|
};
|
|
3717
4273
|
}
|
|
3718
|
-
function searchTasks(
|
|
4274
|
+
function searchTasks(options, projectId, taskListId, db) {
|
|
4275
|
+
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
3719
4276
|
const d = db || getDatabase();
|
|
3720
4277
|
clearExpiredLocks(d);
|
|
3721
|
-
const pattern = `%${query}%`;
|
|
4278
|
+
const pattern = `%${opts.query}%`;
|
|
3722
4279
|
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 ?))`;
|
|
3723
4280
|
const params = [pattern, pattern, pattern];
|
|
3724
|
-
if (
|
|
4281
|
+
if (opts.project_id) {
|
|
3725
4282
|
sql += " AND project_id = ?";
|
|
3726
|
-
params.push(
|
|
4283
|
+
params.push(opts.project_id);
|
|
3727
4284
|
}
|
|
3728
|
-
if (
|
|
4285
|
+
if (opts.task_list_id) {
|
|
3729
4286
|
sql += " AND task_list_id = ?";
|
|
3730
|
-
params.push(
|
|
4287
|
+
params.push(opts.task_list_id);
|
|
4288
|
+
}
|
|
4289
|
+
if (opts.status) {
|
|
4290
|
+
if (Array.isArray(opts.status)) {
|
|
4291
|
+
sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
|
|
4292
|
+
params.push(...opts.status);
|
|
4293
|
+
} else {
|
|
4294
|
+
sql += " AND status = ?";
|
|
4295
|
+
params.push(opts.status);
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
if (opts.priority) {
|
|
4299
|
+
if (Array.isArray(opts.priority)) {
|
|
4300
|
+
sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
4301
|
+
params.push(...opts.priority);
|
|
4302
|
+
} else {
|
|
4303
|
+
sql += " AND priority = ?";
|
|
4304
|
+
params.push(opts.priority);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
if (opts.assigned_to) {
|
|
4308
|
+
sql += " AND assigned_to = ?";
|
|
4309
|
+
params.push(opts.assigned_to);
|
|
4310
|
+
}
|
|
4311
|
+
if (opts.agent_id) {
|
|
4312
|
+
sql += " AND agent_id = ?";
|
|
4313
|
+
params.push(opts.agent_id);
|
|
4314
|
+
}
|
|
4315
|
+
if (opts.created_after) {
|
|
4316
|
+
sql += " AND created_at > ?";
|
|
4317
|
+
params.push(opts.created_after);
|
|
4318
|
+
}
|
|
4319
|
+
if (opts.updated_after) {
|
|
4320
|
+
sql += " AND updated_at > ?";
|
|
4321
|
+
params.push(opts.updated_after);
|
|
4322
|
+
}
|
|
4323
|
+
if (opts.has_dependencies === true) {
|
|
4324
|
+
sql += " AND id IN (SELECT task_id FROM task_dependencies)";
|
|
4325
|
+
} else if (opts.has_dependencies === false) {
|
|
4326
|
+
sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
4327
|
+
}
|
|
4328
|
+
if (opts.is_blocked === true) {
|
|
4329
|
+
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')";
|
|
4330
|
+
} else if (opts.is_blocked === false) {
|
|
4331
|
+
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')";
|
|
3731
4332
|
}
|
|
3732
4333
|
sql += ` ORDER BY
|
|
3733
4334
|
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
@@ -8360,25 +8961,38 @@ var exports_mcp = {};
|
|
|
8360
8961
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8361
8962
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8362
8963
|
function formatError(error) {
|
|
8363
|
-
if (error instanceof VersionConflictError)
|
|
8364
|
-
return
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
if (error instanceof
|
|
8370
|
-
return
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8964
|
+
if (error instanceof VersionConflictError) {
|
|
8965
|
+
return JSON.stringify({ code: VersionConflictError.code, message: error.message, suggestion: VersionConflictError.suggestion });
|
|
8966
|
+
}
|
|
8967
|
+
if (error instanceof TaskNotFoundError) {
|
|
8968
|
+
return JSON.stringify({ code: TaskNotFoundError.code, message: error.message, suggestion: TaskNotFoundError.suggestion });
|
|
8969
|
+
}
|
|
8970
|
+
if (error instanceof ProjectNotFoundError) {
|
|
8971
|
+
return JSON.stringify({ code: ProjectNotFoundError.code, message: error.message, suggestion: ProjectNotFoundError.suggestion });
|
|
8972
|
+
}
|
|
8973
|
+
if (error instanceof PlanNotFoundError) {
|
|
8974
|
+
return JSON.stringify({ code: PlanNotFoundError.code, message: error.message, suggestion: PlanNotFoundError.suggestion });
|
|
8975
|
+
}
|
|
8976
|
+
if (error instanceof TaskListNotFoundError) {
|
|
8977
|
+
return JSON.stringify({ code: TaskListNotFoundError.code, message: error.message, suggestion: TaskListNotFoundError.suggestion });
|
|
8978
|
+
}
|
|
8979
|
+
if (error instanceof LockError) {
|
|
8980
|
+
return JSON.stringify({ code: LockError.code, message: error.message, suggestion: LockError.suggestion });
|
|
8981
|
+
}
|
|
8982
|
+
if (error instanceof AgentNotFoundError) {
|
|
8983
|
+
return JSON.stringify({ code: AgentNotFoundError.code, message: error.message, suggestion: AgentNotFoundError.suggestion });
|
|
8984
|
+
}
|
|
8985
|
+
if (error instanceof DependencyCycleError) {
|
|
8986
|
+
return JSON.stringify({ code: DependencyCycleError.code, message: error.message, suggestion: DependencyCycleError.suggestion });
|
|
8987
|
+
}
|
|
8375
8988
|
if (error instanceof CompletionGuardError) {
|
|
8376
|
-
const retry = error.retryAfterSeconds ?
|
|
8377
|
-
return
|
|
8989
|
+
const retry = error.retryAfterSeconds ? { retryAfterSeconds: error.retryAfterSeconds } : {};
|
|
8990
|
+
return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
|
|
8991
|
+
}
|
|
8992
|
+
if (error instanceof Error) {
|
|
8993
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: error.message });
|
|
8378
8994
|
}
|
|
8379
|
-
|
|
8380
|
-
return error.message;
|
|
8381
|
-
return String(error);
|
|
8995
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
|
|
8382
8996
|
}
|
|
8383
8997
|
function resolveId(partialId, table = "tasks") {
|
|
8384
8998
|
const db = getDatabase();
|
|
@@ -8401,7 +9015,8 @@ function formatTask(task) {
|
|
|
8401
9015
|
const id = task.short_id || task.id.slice(0, 8);
|
|
8402
9016
|
const assigned = task.assigned_to ? ` -> ${task.assigned_to}` : "";
|
|
8403
9017
|
const lock = task.locked_by ? ` [locked:${task.locked_by}]` : "";
|
|
8404
|
-
|
|
9018
|
+
const recur = task.recurrence_rule ? ` [\u21BB]` : "";
|
|
9019
|
+
return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
|
|
8405
9020
|
}
|
|
8406
9021
|
function formatTaskDetail(task) {
|
|
8407
9022
|
const parts = [
|
|
@@ -8426,6 +9041,10 @@ function formatTaskDetail(task) {
|
|
|
8426
9041
|
parts.push(`Plan: ${task.plan_id}`);
|
|
8427
9042
|
if (task.tags.length > 0)
|
|
8428
9043
|
parts.push(`Tags: ${task.tags.join(", ")}`);
|
|
9044
|
+
if (task.recurrence_rule)
|
|
9045
|
+
parts.push(`Recurrence: ${task.recurrence_rule}`);
|
|
9046
|
+
if (task.recurrence_parent_id)
|
|
9047
|
+
parts.push(`Recurrence parent: ${task.recurrence_parent_id}`);
|
|
8429
9048
|
parts.push(`Version: ${task.version}`);
|
|
8430
9049
|
parts.push(`Created: ${task.created_at}`);
|
|
8431
9050
|
if (task.completed_at)
|
|
@@ -8453,7 +9072,7 @@ var init_mcp = __esm(() => {
|
|
|
8453
9072
|
init_types();
|
|
8454
9073
|
server = new McpServer({
|
|
8455
9074
|
name: "todos",
|
|
8456
|
-
version: "0.9.
|
|
9075
|
+
version: "0.9.35"
|
|
8457
9076
|
});
|
|
8458
9077
|
server.tool("create_task", "Create a new task", {
|
|
8459
9078
|
title: exports_external.string(),
|
|
@@ -8471,7 +9090,8 @@ var init_mcp = __esm(() => {
|
|
|
8471
9090
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
8472
9091
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
8473
9092
|
estimated_minutes: exports_external.number().optional(),
|
|
8474
|
-
requires_approval: exports_external.boolean().optional()
|
|
9093
|
+
requires_approval: exports_external.boolean().optional(),
|
|
9094
|
+
recurrence_rule: exports_external.string().optional()
|
|
8475
9095
|
}, async (params) => {
|
|
8476
9096
|
try {
|
|
8477
9097
|
const resolved = { ...params };
|
|
@@ -8489,7 +9109,7 @@ var init_mcp = __esm(() => {
|
|
|
8489
9109
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8490
9110
|
}
|
|
8491
9111
|
});
|
|
8492
|
-
server.tool("list_tasks", "List tasks with optional filters", {
|
|
9112
|
+
server.tool("list_tasks", "List tasks with optional filters and pagination.", {
|
|
8493
9113
|
project_id: exports_external.string().optional(),
|
|
8494
9114
|
status: exports_external.union([
|
|
8495
9115
|
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
@@ -8502,7 +9122,10 @@ var init_mcp = __esm(() => {
|
|
|
8502
9122
|
assigned_to: exports_external.string().optional(),
|
|
8503
9123
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
8504
9124
|
plan_id: exports_external.string().optional(),
|
|
8505
|
-
task_list_id: exports_external.string().optional()
|
|
9125
|
+
task_list_id: exports_external.string().optional(),
|
|
9126
|
+
has_recurrence: exports_external.boolean().optional(),
|
|
9127
|
+
limit: exports_external.number().optional(),
|
|
9128
|
+
offset: exports_external.number().optional()
|
|
8506
9129
|
}, async (params) => {
|
|
8507
9130
|
try {
|
|
8508
9131
|
const resolved = { ...params };
|
|
@@ -8513,8 +9136,10 @@ var init_mcp = __esm(() => {
|
|
|
8513
9136
|
if (resolved.task_list_id)
|
|
8514
9137
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
8515
9138
|
const tasks = listTasks(resolved);
|
|
9139
|
+
const { limit: _limit, offset: _offset, ...countFilter } = resolved;
|
|
9140
|
+
const total = countTasks(countFilter);
|
|
8516
9141
|
if (tasks.length === 0) {
|
|
8517
|
-
return { content: [{ type: "text", text: "No tasks found." }] };
|
|
9142
|
+
return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
|
|
8518
9143
|
}
|
|
8519
9144
|
const text = tasks.map((t) => {
|
|
8520
9145
|
const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
|
|
@@ -8522,13 +9147,15 @@ var init_mcp = __esm(() => {
|
|
|
8522
9147
|
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}`;
|
|
8523
9148
|
}).join(`
|
|
8524
9149
|
`);
|
|
9150
|
+
const pagination = resolved.limit ? `
|
|
9151
|
+
(showing ${tasks.length} of ${total}, offset: ${resolved.offset || 0})` : "";
|
|
8525
9152
|
return { content: [{ type: "text", text: `${tasks.length} task(s):
|
|
8526
|
-
${text}` }] };
|
|
9153
|
+
${text}${pagination}` }] };
|
|
8527
9154
|
} catch (e) {
|
|
8528
9155
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8529
9156
|
}
|
|
8530
9157
|
});
|
|
8531
|
-
server.tool("get_task", "Get full task details with
|
|
9158
|
+
server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
|
|
8532
9159
|
id: exports_external.string()
|
|
8533
9160
|
}, async ({ id }) => {
|
|
8534
9161
|
try {
|
|
@@ -8597,7 +9224,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8597
9224
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8598
9225
|
}
|
|
8599
9226
|
});
|
|
8600
|
-
server.tool("delete_task", "Delete a task permanently", {
|
|
9227
|
+
server.tool("delete_task", "Delete a task permanently. Subtasks cascade-deleted.", {
|
|
8601
9228
|
id: exports_external.string()
|
|
8602
9229
|
}, async ({ id }) => {
|
|
8603
9230
|
try {
|
|
@@ -8613,7 +9240,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8613
9240
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8614
9241
|
}
|
|
8615
9242
|
});
|
|
8616
|
-
server.tool("start_task", "Claim, lock, and set task
|
|
9243
|
+
server.tool("start_task", "Claim, lock, and set task to in_progress.", {
|
|
8617
9244
|
id: exports_external.string(),
|
|
8618
9245
|
agent_id: exports_external.string()
|
|
8619
9246
|
}, async ({ id, agent_id }) => {
|
|
@@ -8625,19 +9252,26 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8625
9252
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8626
9253
|
}
|
|
8627
9254
|
});
|
|
8628
|
-
server.tool("complete_task", "
|
|
9255
|
+
server.tool("complete_task", "Complete a task. For recurring tasks, auto-spawns next instance.", {
|
|
8629
9256
|
id: exports_external.string(),
|
|
8630
|
-
agent_id: exports_external.string().optional()
|
|
8631
|
-
|
|
9257
|
+
agent_id: exports_external.string().optional(),
|
|
9258
|
+
skip_recurrence: exports_external.boolean().optional()
|
|
9259
|
+
}, async ({ id, agent_id, skip_recurrence }) => {
|
|
8632
9260
|
try {
|
|
8633
9261
|
const resolvedId = resolveId(id);
|
|
8634
|
-
const task = completeTask(resolvedId, agent_id);
|
|
8635
|
-
|
|
9262
|
+
const task = completeTask(resolvedId, agent_id, undefined, { skip_recurrence });
|
|
9263
|
+
let text = `completed: ${formatTask(task)}`;
|
|
9264
|
+
if (task.metadata._next_recurrence) {
|
|
9265
|
+
const next = task.metadata._next_recurrence;
|
|
9266
|
+
text += `
|
|
9267
|
+
next: ${next.short_id || next.id.slice(0, 8)} due ${next.due_at}`;
|
|
9268
|
+
}
|
|
9269
|
+
return { content: [{ type: "text", text }] };
|
|
8636
9270
|
} catch (e) {
|
|
8637
9271
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8638
9272
|
}
|
|
8639
9273
|
});
|
|
8640
|
-
server.tool("lock_task", "Acquire exclusive lock
|
|
9274
|
+
server.tool("lock_task", "Acquire exclusive lock. Expires after 30 min. Idempotent per agent.", {
|
|
8641
9275
|
id: exports_external.string(),
|
|
8642
9276
|
agent_id: exports_external.string()
|
|
8643
9277
|
}, async ({ id, agent_id }) => {
|
|
@@ -8652,7 +9286,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8652
9286
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8653
9287
|
}
|
|
8654
9288
|
});
|
|
8655
|
-
server.tool("unlock_task", "Release exclusive lock on a task", {
|
|
9289
|
+
server.tool("unlock_task", "Release exclusive lock on a task.", {
|
|
8656
9290
|
id: exports_external.string(),
|
|
8657
9291
|
agent_id: exports_external.string().optional()
|
|
8658
9292
|
}, async ({ id, agent_id }) => {
|
|
@@ -8664,7 +9298,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8664
9298
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8665
9299
|
}
|
|
8666
9300
|
});
|
|
8667
|
-
server.tool("add_dependency", "Add a dependency
|
|
9301
|
+
server.tool("add_dependency", "Add a dependency. Prevents cycles via BFS detection.", {
|
|
8668
9302
|
task_id: exports_external.string(),
|
|
8669
9303
|
depends_on: exports_external.string()
|
|
8670
9304
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -8677,7 +9311,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8677
9311
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8678
9312
|
}
|
|
8679
9313
|
});
|
|
8680
|
-
server.tool("remove_dependency", "Remove a dependency between tasks", {
|
|
9314
|
+
server.tool("remove_dependency", "Remove a dependency link between two tasks.", {
|
|
8681
9315
|
task_id: exports_external.string(),
|
|
8682
9316
|
depends_on: exports_external.string()
|
|
8683
9317
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -8695,7 +9329,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8695
9329
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8696
9330
|
}
|
|
8697
9331
|
});
|
|
8698
|
-
server.tool("add_comment", "Add a comment
|
|
9332
|
+
server.tool("add_comment", "Add a comment or note to a task. Comments are append-only.", {
|
|
8699
9333
|
task_id: exports_external.string(),
|
|
8700
9334
|
content: exports_external.string(),
|
|
8701
9335
|
agent_id: exports_external.string().optional(),
|
|
@@ -8726,7 +9360,7 @@ ${text}` }] };
|
|
|
8726
9360
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8727
9361
|
}
|
|
8728
9362
|
});
|
|
8729
|
-
server.tool("create_project", "Register a new project", {
|
|
9363
|
+
server.tool("create_project", "Register a new project with auto-generated task prefix.", {
|
|
8730
9364
|
name: exports_external.string(),
|
|
8731
9365
|
path: exports_external.string(),
|
|
8732
9366
|
description: exports_external.string().optional(),
|
|
@@ -8745,7 +9379,7 @@ ${text}` }] };
|
|
|
8745
9379
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8746
9380
|
}
|
|
8747
9381
|
});
|
|
8748
|
-
server.tool("create_plan", "Create a
|
|
9382
|
+
server.tool("create_plan", "Create a plan to group related tasks.", {
|
|
8749
9383
|
name: exports_external.string(),
|
|
8750
9384
|
project_id: exports_external.string().optional(),
|
|
8751
9385
|
description: exports_external.string().optional(),
|
|
@@ -8770,7 +9404,7 @@ ${text}` }] };
|
|
|
8770
9404
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8771
9405
|
}
|
|
8772
9406
|
});
|
|
8773
|
-
server.tool("list_plans", "List plans
|
|
9407
|
+
server.tool("list_plans", "List all plans, optionally filtered by project.", {
|
|
8774
9408
|
project_id: exports_external.string().optional()
|
|
8775
9409
|
}, async ({ project_id }) => {
|
|
8776
9410
|
try {
|
|
@@ -8790,7 +9424,7 @@ ${text}` }] };
|
|
|
8790
9424
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8791
9425
|
}
|
|
8792
9426
|
});
|
|
8793
|
-
server.tool("get_plan", "Get plan details", {
|
|
9427
|
+
server.tool("get_plan", "Get plan details including status and timestamps.", {
|
|
8794
9428
|
id: exports_external.string()
|
|
8795
9429
|
}, async ({ id }) => {
|
|
8796
9430
|
try {
|
|
@@ -8815,7 +9449,7 @@ ${text}` }] };
|
|
|
8815
9449
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8816
9450
|
}
|
|
8817
9451
|
});
|
|
8818
|
-
server.tool("update_plan", "Update
|
|
9452
|
+
server.tool("update_plan", "Update plan fields (name, description, status).", {
|
|
8819
9453
|
id: exports_external.string(),
|
|
8820
9454
|
name: exports_external.string().optional(),
|
|
8821
9455
|
description: exports_external.string().optional(),
|
|
@@ -8839,7 +9473,7 @@ ${text}` }] };
|
|
|
8839
9473
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8840
9474
|
}
|
|
8841
9475
|
});
|
|
8842
|
-
server.tool("delete_plan", "Delete a plan", {
|
|
9476
|
+
server.tool("delete_plan", "Delete a plan. Tasks in the plan are orphaned (not deleted).", {
|
|
8843
9477
|
id: exports_external.string()
|
|
8844
9478
|
}, async ({ id }) => {
|
|
8845
9479
|
try {
|
|
@@ -8855,15 +9489,34 @@ ${text}` }] };
|
|
|
8855
9489
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8856
9490
|
}
|
|
8857
9491
|
});
|
|
8858
|
-
server.tool("search_tasks", "Full-text search across
|
|
9492
|
+
server.tool("search_tasks", "Full-text search across tasks with filters.", {
|
|
8859
9493
|
query: exports_external.string(),
|
|
8860
9494
|
project_id: exports_external.string().optional(),
|
|
8861
|
-
task_list_id: exports_external.string().optional()
|
|
8862
|
-
|
|
9495
|
+
task_list_id: exports_external.string().optional(),
|
|
9496
|
+
status: exports_external.union([
|
|
9497
|
+
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
9498
|
+
exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))
|
|
9499
|
+
]).optional(),
|
|
9500
|
+
priority: exports_external.union([
|
|
9501
|
+
exports_external.enum(["low", "medium", "high", "critical"]),
|
|
9502
|
+
exports_external.array(exports_external.enum(["low", "medium", "high", "critical"]))
|
|
9503
|
+
]).optional(),
|
|
9504
|
+
assigned_to: exports_external.string().optional(),
|
|
9505
|
+
agent_id: exports_external.string().optional(),
|
|
9506
|
+
created_after: exports_external.string().optional(),
|
|
9507
|
+
updated_after: exports_external.string().optional(),
|
|
9508
|
+
has_dependencies: exports_external.boolean().optional(),
|
|
9509
|
+
is_blocked: exports_external.boolean().optional()
|
|
9510
|
+
}, async ({ query, project_id, task_list_id, ...filters }) => {
|
|
8863
9511
|
try {
|
|
8864
9512
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
8865
9513
|
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
8866
|
-
const tasks = searchTasks(
|
|
9514
|
+
const tasks = searchTasks({
|
|
9515
|
+
query,
|
|
9516
|
+
project_id: resolvedProjectId,
|
|
9517
|
+
task_list_id: resolvedTaskListId,
|
|
9518
|
+
...filters
|
|
9519
|
+
});
|
|
8867
9520
|
if (tasks.length === 0) {
|
|
8868
9521
|
return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
|
|
8869
9522
|
}
|
|
@@ -8875,7 +9528,7 @@ ${text}` }] };
|
|
|
8875
9528
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8876
9529
|
}
|
|
8877
9530
|
});
|
|
8878
|
-
server.tool("sync", "Sync tasks
|
|
9531
|
+
server.tool("sync", "Sync tasks between local DB and agent task list.", {
|
|
8879
9532
|
task_list_id: exports_external.string().optional(),
|
|
8880
9533
|
agent: exports_external.string().optional(),
|
|
8881
9534
|
all_agents: exports_external.boolean().optional(),
|
|
@@ -8923,7 +9576,7 @@ ${text}` }] };
|
|
|
8923
9576
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8924
9577
|
}
|
|
8925
9578
|
});
|
|
8926
|
-
server.tool("register_agent", "Register an agent (idempotent by name).", {
|
|
9579
|
+
server.tool("register_agent", "Register an agent (idempotent by name). Updates last_seen_at.", {
|
|
8927
9580
|
name: exports_external.string(),
|
|
8928
9581
|
description: exports_external.string().optional()
|
|
8929
9582
|
}, async ({ name, description }) => {
|
|
@@ -8960,7 +9613,7 @@ ${text}` }] };
|
|
|
8960
9613
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8961
9614
|
}
|
|
8962
9615
|
});
|
|
8963
|
-
server.tool("get_agent", "Get agent details by ID or name", {
|
|
9616
|
+
server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
|
|
8964
9617
|
id: exports_external.string().optional(),
|
|
8965
9618
|
name: exports_external.string().optional()
|
|
8966
9619
|
}, async ({ id, name }) => {
|
|
@@ -8989,9 +9642,9 @@ ${text}` }] };
|
|
|
8989
9642
|
}
|
|
8990
9643
|
});
|
|
8991
9644
|
server.tool("rename_agent", "Rename an agent. Resolve by id or current name.", {
|
|
8992
|
-
id: exports_external.string().optional()
|
|
8993
|
-
name: exports_external.string().optional()
|
|
8994
|
-
new_name: exports_external.string()
|
|
9645
|
+
id: exports_external.string().optional(),
|
|
9646
|
+
name: exports_external.string().optional(),
|
|
9647
|
+
new_name: exports_external.string()
|
|
8995
9648
|
}, async ({ id, name, new_name }) => {
|
|
8996
9649
|
try {
|
|
8997
9650
|
if (!id && !name) {
|
|
@@ -9014,8 +9667,8 @@ ID: ${updated.id}`
|
|
|
9014
9667
|
}
|
|
9015
9668
|
});
|
|
9016
9669
|
server.tool("delete_agent", "Delete an agent permanently. Resolve by id or name.", {
|
|
9017
|
-
id: exports_external.string().optional()
|
|
9018
|
-
name: exports_external.string().optional()
|
|
9670
|
+
id: exports_external.string().optional(),
|
|
9671
|
+
name: exports_external.string().optional()
|
|
9019
9672
|
}, async ({ id, name }) => {
|
|
9020
9673
|
try {
|
|
9021
9674
|
if (!id && !name) {
|
|
@@ -9037,7 +9690,7 @@ ID: ${updated.id}`
|
|
|
9037
9690
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9038
9691
|
}
|
|
9039
9692
|
});
|
|
9040
|
-
server.tool("create_task_list", "Create a
|
|
9693
|
+
server.tool("create_task_list", "Create a task list container for organizing tasks.", {
|
|
9041
9694
|
name: exports_external.string(),
|
|
9042
9695
|
slug: exports_external.string().optional(),
|
|
9043
9696
|
project_id: exports_external.string().optional(),
|
|
@@ -9063,7 +9716,7 @@ Description: ${list.description}` : ""}`
|
|
|
9063
9716
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9064
9717
|
}
|
|
9065
9718
|
});
|
|
9066
|
-
server.tool("list_task_lists", "List task lists, optionally filtered by project", {
|
|
9719
|
+
server.tool("list_task_lists", "List all task lists, optionally filtered by project.", {
|
|
9067
9720
|
project_id: exports_external.string().optional()
|
|
9068
9721
|
}, async ({ project_id }) => {
|
|
9069
9722
|
try {
|
|
@@ -9083,7 +9736,7 @@ ${text}` }] };
|
|
|
9083
9736
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9084
9737
|
}
|
|
9085
9738
|
});
|
|
9086
|
-
server.tool("get_task_list", "Get task list details", {
|
|
9739
|
+
server.tool("get_task_list", "Get task list details including slug and metadata.", {
|
|
9087
9740
|
id: exports_external.string()
|
|
9088
9741
|
}, async ({ id }) => {
|
|
9089
9742
|
try {
|
|
@@ -9111,7 +9764,7 @@ ${text}` }] };
|
|
|
9111
9764
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9112
9765
|
}
|
|
9113
9766
|
});
|
|
9114
|
-
server.tool("update_task_list", "Update a task list", {
|
|
9767
|
+
server.tool("update_task_list", "Update a task list's name or description.", {
|
|
9115
9768
|
id: exports_external.string(),
|
|
9116
9769
|
name: exports_external.string().optional(),
|
|
9117
9770
|
description: exports_external.string().optional()
|
|
@@ -9132,7 +9785,7 @@ Slug: ${list.slug}`
|
|
|
9132
9785
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9133
9786
|
}
|
|
9134
9787
|
});
|
|
9135
|
-
server.tool("delete_task_list", "Delete a task list. Tasks
|
|
9788
|
+
server.tool("delete_task_list", "Delete a task list. Tasks are orphaned, not deleted.", {
|
|
9136
9789
|
id: exports_external.string()
|
|
9137
9790
|
}, async ({ id }) => {
|
|
9138
9791
|
try {
|
|
@@ -9148,7 +9801,7 @@ Slug: ${list.slug}`
|
|
|
9148
9801
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9149
9802
|
}
|
|
9150
9803
|
});
|
|
9151
|
-
server.tool("get_task_history", "Get audit log
|
|
9804
|
+
server.tool("get_task_history", "Get audit log \u2014 field changes with timestamps and actors.", {
|
|
9152
9805
|
task_id: exports_external.string()
|
|
9153
9806
|
}, async ({ task_id }) => {
|
|
9154
9807
|
try {
|
|
@@ -9165,7 +9818,7 @@ ${text}` }] };
|
|
|
9165
9818
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9166
9819
|
}
|
|
9167
9820
|
});
|
|
9168
|
-
server.tool("get_recent_activity", "Get recent task changes
|
|
9821
|
+
server.tool("get_recent_activity", "Get recent task changes \u2014 global activity feed.", {
|
|
9169
9822
|
limit: exports_external.number().optional()
|
|
9170
9823
|
}, async ({ limit }) => {
|
|
9171
9824
|
try {
|
|
@@ -9181,7 +9834,7 @@ ${text}` }] };
|
|
|
9181
9834
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9182
9835
|
}
|
|
9183
9836
|
});
|
|
9184
|
-
server.tool("create_webhook", "Register a webhook
|
|
9837
|
+
server.tool("create_webhook", "Register a webhook for task change events.", {
|
|
9185
9838
|
url: exports_external.string(),
|
|
9186
9839
|
events: exports_external.array(exports_external.string()).optional(),
|
|
9187
9840
|
secret: exports_external.string().optional()
|
|
@@ -9208,7 +9861,7 @@ ${text}` }] };
|
|
|
9208
9861
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9209
9862
|
}
|
|
9210
9863
|
});
|
|
9211
|
-
server.tool("delete_webhook", "Delete a webhook", {
|
|
9864
|
+
server.tool("delete_webhook", "Delete a webhook by ID.", {
|
|
9212
9865
|
id: exports_external.string()
|
|
9213
9866
|
}, async ({ id }) => {
|
|
9214
9867
|
try {
|
|
@@ -9274,7 +9927,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
9274
9927
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9275
9928
|
}
|
|
9276
9929
|
});
|
|
9277
|
-
server.tool("delete_template", "Delete a task template", { id: exports_external.string() }, async ({ id }) => {
|
|
9930
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
9278
9931
|
try {
|
|
9279
9932
|
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
9280
9933
|
const deleted = deleteTemplate2(id);
|
|
@@ -9283,7 +9936,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
9283
9936
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9284
9937
|
}
|
|
9285
9938
|
});
|
|
9286
|
-
server.tool("approve_task", "Approve a task
|
|
9939
|
+
server.tool("approve_task", "Approve a task with requires_approval=true.", {
|
|
9287
9940
|
id: exports_external.string(),
|
|
9288
9941
|
agent_id: exports_external.string().optional()
|
|
9289
9942
|
}, async ({ id, agent_id }) => {
|
|
@@ -9302,7 +9955,31 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
9302
9955
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9303
9956
|
}
|
|
9304
9957
|
});
|
|
9305
|
-
server.tool("
|
|
9958
|
+
server.tool("fail_task", "Mark a task as failed with structured reason and optional auto-retry.", {
|
|
9959
|
+
id: exports_external.string(),
|
|
9960
|
+
agent_id: exports_external.string().optional(),
|
|
9961
|
+
reason: exports_external.string().optional(),
|
|
9962
|
+
error_code: exports_external.string().optional(),
|
|
9963
|
+
retry: exports_external.boolean().optional(),
|
|
9964
|
+
retry_after: exports_external.string().optional()
|
|
9965
|
+
}, async ({ id, agent_id, reason, error_code, retry, retry_after }) => {
|
|
9966
|
+
try {
|
|
9967
|
+
const resolvedId = resolveId(id);
|
|
9968
|
+
const result = failTask(resolvedId, agent_id, reason, { retry, retry_after, error_code });
|
|
9969
|
+
let text = `failed: ${formatTask(result.task)}`;
|
|
9970
|
+
if (reason)
|
|
9971
|
+
text += `
|
|
9972
|
+
Reason: ${reason}`;
|
|
9973
|
+
if (result.retryTask) {
|
|
9974
|
+
text += `
|
|
9975
|
+
Retry task created: ${formatTask(result.retryTask)}`;
|
|
9976
|
+
}
|
|
9977
|
+
return { content: [{ type: "text", text }] };
|
|
9978
|
+
} catch (e) {
|
|
9979
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9980
|
+
}
|
|
9981
|
+
});
|
|
9982
|
+
server.tool("get_my_tasks", "Get tasks assigned to/created by an agent with stats.", {
|
|
9306
9983
|
agent_name: exports_external.string()
|
|
9307
9984
|
}, async ({ agent_name }) => {
|
|
9308
9985
|
try {
|
|
@@ -9335,7 +10012,7 @@ In Progress:`);
|
|
|
9335
10012
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9336
10013
|
}
|
|
9337
10014
|
});
|
|
9338
|
-
server.tool("get_org_chart", "Get agent org chart
|
|
10015
|
+
server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
|
|
9339
10016
|
try {
|
|
9340
10017
|
let render = function(nodes, indent = 0) {
|
|
9341
10018
|
return nodes.map((n) => {
|
|
@@ -9357,7 +10034,7 @@ In Progress:`);
|
|
|
9357
10034
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9358
10035
|
}
|
|
9359
10036
|
});
|
|
9360
|
-
server.tool("set_reports_to", "Set
|
|
10037
|
+
server.tool("set_reports_to", "Set agent reporting relationship in org chart.", {
|
|
9361
10038
|
agent_name: exports_external.string(),
|
|
9362
10039
|
manager_name: exports_external.string().optional()
|
|
9363
10040
|
}, async ({ agent_name, manager_name }) => {
|
|
@@ -9380,7 +10057,326 @@ In Progress:`);
|
|
|
9380
10057
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9381
10058
|
}
|
|
9382
10059
|
});
|
|
9383
|
-
server.tool("
|
|
10060
|
+
server.tool("bulk_update_tasks", "Update multiple tasks at once with the same changes.", {
|
|
10061
|
+
task_ids: exports_external.array(exports_external.string()),
|
|
10062
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
10063
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
10064
|
+
assigned_to: exports_external.string().optional(),
|
|
10065
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
10066
|
+
}, async ({ task_ids, ...updates }) => {
|
|
10067
|
+
try {
|
|
10068
|
+
const resolvedIds = task_ids.map((id) => resolveId(id));
|
|
10069
|
+
const result = bulkUpdateTasks(resolvedIds, updates);
|
|
10070
|
+
const parts = [`Updated ${result.updated} task(s).`];
|
|
10071
|
+
if (result.failed.length > 0) {
|
|
10072
|
+
parts.push(`Failed ${result.failed.length}:`);
|
|
10073
|
+
for (const f of result.failed)
|
|
10074
|
+
parts.push(` ${f.id.slice(0, 8)}: ${f.error}`);
|
|
10075
|
+
}
|
|
10076
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
10077
|
+
`) }] };
|
|
10078
|
+
} catch (e) {
|
|
10079
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10080
|
+
}
|
|
10081
|
+
});
|
|
10082
|
+
server.tool("clone_task", "Duplicate a task with optional field overrides.", {
|
|
10083
|
+
task_id: exports_external.string(),
|
|
10084
|
+
title: exports_external.string().optional(),
|
|
10085
|
+
description: exports_external.string().optional(),
|
|
10086
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
10087
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
10088
|
+
project_id: exports_external.string().optional(),
|
|
10089
|
+
plan_id: exports_external.string().optional(),
|
|
10090
|
+
task_list_id: exports_external.string().optional(),
|
|
10091
|
+
assigned_to: exports_external.string().optional(),
|
|
10092
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
10093
|
+
estimated_minutes: exports_external.number().optional()
|
|
10094
|
+
}, async ({ task_id, ...overrides }) => {
|
|
10095
|
+
try {
|
|
10096
|
+
const resolvedId = resolveId(task_id);
|
|
10097
|
+
const resolved = { ...overrides };
|
|
10098
|
+
if (resolved.project_id)
|
|
10099
|
+
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
10100
|
+
if (resolved.plan_id)
|
|
10101
|
+
resolved.plan_id = resolveId(resolved.plan_id, "plans");
|
|
10102
|
+
if (resolved.task_list_id)
|
|
10103
|
+
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
10104
|
+
const task = cloneTask(resolvedId, resolved);
|
|
10105
|
+
return { content: [{ type: "text", text: `cloned: ${formatTask(task)}` }] };
|
|
10106
|
+
} catch (e) {
|
|
10107
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10108
|
+
}
|
|
10109
|
+
});
|
|
10110
|
+
server.tool("get_task_stats", "Get task analytics: counts by status, priority, agent.", {
|
|
10111
|
+
project_id: exports_external.string().optional(),
|
|
10112
|
+
task_list_id: exports_external.string().optional(),
|
|
10113
|
+
agent_id: exports_external.string().optional()
|
|
10114
|
+
}, async ({ project_id, task_list_id, agent_id }) => {
|
|
10115
|
+
try {
|
|
10116
|
+
const filters = {};
|
|
10117
|
+
if (project_id)
|
|
10118
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10119
|
+
if (task_list_id)
|
|
10120
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10121
|
+
if (agent_id)
|
|
10122
|
+
filters.agent_id = agent_id;
|
|
10123
|
+
const stats = getTaskStats(Object.keys(filters).length > 0 ? filters : undefined);
|
|
10124
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
10125
|
+
} catch (e) {
|
|
10126
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10127
|
+
}
|
|
10128
|
+
});
|
|
10129
|
+
server.tool("get_task_graph", "Get full dependency tree for a task.", {
|
|
10130
|
+
id: exports_external.string(),
|
|
10131
|
+
direction: exports_external.enum(["up", "down", "both"]).optional()
|
|
10132
|
+
}, async ({ id, direction }) => {
|
|
10133
|
+
try {
|
|
10134
|
+
let formatNode = function(node, indent) {
|
|
10135
|
+
const prefix = " ".repeat(indent);
|
|
10136
|
+
const idLabel = node.task.short_id || node.task.id.slice(0, 8);
|
|
10137
|
+
const blocked = node.task.is_blocked ? " (blocked: yes)" : "";
|
|
10138
|
+
let out = `${prefix}[${node.task.status}] ${idLabel} | ${node.task.title}${blocked}
|
|
10139
|
+
`;
|
|
10140
|
+
if (node.depends_on.length > 0) {
|
|
10141
|
+
out += `${prefix} Depends on:
|
|
10142
|
+
`;
|
|
10143
|
+
for (const dep of node.depends_on) {
|
|
10144
|
+
out += formatNode(dep, indent + 2);
|
|
10145
|
+
}
|
|
10146
|
+
}
|
|
10147
|
+
if (node.blocks.length > 0) {
|
|
10148
|
+
out += `${prefix} Blocks:
|
|
10149
|
+
`;
|
|
10150
|
+
for (const dep of node.blocks) {
|
|
10151
|
+
out += formatNode(dep, indent + 2);
|
|
10152
|
+
}
|
|
10153
|
+
}
|
|
10154
|
+
return out;
|
|
10155
|
+
};
|
|
10156
|
+
const taskId = resolveId(id, "tasks");
|
|
10157
|
+
const graph = getTaskGraph(taskId, direction || "both");
|
|
10158
|
+
let text = `Task: ${formatNode(graph, 0)}`;
|
|
10159
|
+
if (graph.depends_on.length > 0) {
|
|
10160
|
+
text += `
|
|
10161
|
+
Depends on:
|
|
10162
|
+
`;
|
|
10163
|
+
for (const dep of graph.depends_on) {
|
|
10164
|
+
text += formatNode(dep, 1);
|
|
10165
|
+
}
|
|
10166
|
+
}
|
|
10167
|
+
if (graph.blocks.length > 0) {
|
|
10168
|
+
text += `
|
|
10169
|
+
Blocks:
|
|
10170
|
+
`;
|
|
10171
|
+
for (const dep of graph.blocks) {
|
|
10172
|
+
text += formatNode(dep, 1);
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
return { content: [{ type: "text", text }] };
|
|
10176
|
+
} catch (e) {
|
|
10177
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10178
|
+
}
|
|
10179
|
+
});
|
|
10180
|
+
server.tool("bulk_create_tasks", "Create multiple tasks atomically with dependency support.", {
|
|
10181
|
+
tasks: exports_external.array(exports_external.object({
|
|
10182
|
+
temp_id: exports_external.string().optional(),
|
|
10183
|
+
title: exports_external.string(),
|
|
10184
|
+
description: exports_external.string().optional(),
|
|
10185
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
10186
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
10187
|
+
project_id: exports_external.string().optional(),
|
|
10188
|
+
plan_id: exports_external.string().optional(),
|
|
10189
|
+
task_list_id: exports_external.string().optional(),
|
|
10190
|
+
agent_id: exports_external.string().optional(),
|
|
10191
|
+
assigned_to: exports_external.string().optional(),
|
|
10192
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
10193
|
+
estimated_minutes: exports_external.number().optional(),
|
|
10194
|
+
depends_on_temp_ids: exports_external.array(exports_external.string()).optional()
|
|
10195
|
+
})),
|
|
10196
|
+
project_id: exports_external.string().optional(),
|
|
10197
|
+
plan_id: exports_external.string().optional(),
|
|
10198
|
+
task_list_id: exports_external.string().optional()
|
|
10199
|
+
}, async ({ tasks, project_id, plan_id, task_list_id }) => {
|
|
10200
|
+
try {
|
|
10201
|
+
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
10202
|
+
const resolvedPlanId = plan_id ? resolveId(plan_id, "plans") : undefined;
|
|
10203
|
+
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
10204
|
+
const enrichedTasks = tasks.map((t) => ({
|
|
10205
|
+
...t,
|
|
10206
|
+
project_id: t.project_id || resolvedProjectId,
|
|
10207
|
+
plan_id: t.plan_id || resolvedPlanId,
|
|
10208
|
+
task_list_id: t.task_list_id || resolvedTaskListId
|
|
10209
|
+
}));
|
|
10210
|
+
const result = bulkCreateTasks(enrichedTasks);
|
|
10211
|
+
const lines = result.created.map((t) => {
|
|
10212
|
+
const tid = t.temp_id ? `[${t.temp_id}] ` : "";
|
|
10213
|
+
const sid = t.short_id || t.id.slice(0, 8);
|
|
10214
|
+
return ` ${tid}${sid} | ${t.title}`;
|
|
10215
|
+
});
|
|
10216
|
+
return { content: [{ type: "text", text: `Created ${result.created.length} task(s):
|
|
10217
|
+
${lines.join(`
|
|
10218
|
+
`)}` }] };
|
|
10219
|
+
} catch (e) {
|
|
10220
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10221
|
+
}
|
|
10222
|
+
});
|
|
10223
|
+
server.tool("move_task", "Move a task to a different list, project, or plan.", {
|
|
10224
|
+
task_id: exports_external.string(),
|
|
10225
|
+
task_list_id: exports_external.string().nullable().optional(),
|
|
10226
|
+
project_id: exports_external.string().nullable().optional(),
|
|
10227
|
+
plan_id: exports_external.string().nullable().optional()
|
|
10228
|
+
}, async ({ task_id, ...target }) => {
|
|
10229
|
+
try {
|
|
10230
|
+
const resolvedId = resolveId(task_id);
|
|
10231
|
+
const resolvedTarget = {};
|
|
10232
|
+
if (target.task_list_id !== undefined)
|
|
10233
|
+
resolvedTarget.task_list_id = target.task_list_id ? resolveId(target.task_list_id, "task_lists") : null;
|
|
10234
|
+
if (target.project_id !== undefined)
|
|
10235
|
+
resolvedTarget.project_id = target.project_id ? resolveId(target.project_id, "projects") : null;
|
|
10236
|
+
if (target.plan_id !== undefined)
|
|
10237
|
+
resolvedTarget.plan_id = target.plan_id ? resolveId(target.plan_id, "plans") : null;
|
|
10238
|
+
const task = moveTask(resolvedId, resolvedTarget);
|
|
10239
|
+
return { content: [{ type: "text", text: `moved: ${formatTask(task)}` }] };
|
|
10240
|
+
} catch (e) {
|
|
10241
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10242
|
+
}
|
|
10243
|
+
});
|
|
10244
|
+
server.tool("get_next_task", "Get the best pending task to work on next.", {
|
|
10245
|
+
agent_id: exports_external.string().optional(),
|
|
10246
|
+
project_id: exports_external.string().optional(),
|
|
10247
|
+
task_list_id: exports_external.string().optional(),
|
|
10248
|
+
plan_id: exports_external.string().optional(),
|
|
10249
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
10250
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
10251
|
+
try {
|
|
10252
|
+
const filters = {};
|
|
10253
|
+
if (project_id)
|
|
10254
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10255
|
+
if (task_list_id)
|
|
10256
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10257
|
+
if (plan_id)
|
|
10258
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
10259
|
+
if (tags)
|
|
10260
|
+
filters.tags = tags;
|
|
10261
|
+
const task = getNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10262
|
+
if (!task) {
|
|
10263
|
+
return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
|
|
10264
|
+
}
|
|
10265
|
+
return { content: [{ type: "text", text: `next: ${formatTask(task)}
|
|
10266
|
+
${formatTaskDetail(task)}` }] };
|
|
10267
|
+
} catch (e) {
|
|
10268
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10269
|
+
}
|
|
10270
|
+
});
|
|
10271
|
+
server.tool("get_active_work", "See all in-progress tasks and who is working on them.", {
|
|
10272
|
+
project_id: exports_external.string().optional(),
|
|
10273
|
+
task_list_id: exports_external.string().optional()
|
|
10274
|
+
}, async ({ project_id, task_list_id }) => {
|
|
10275
|
+
try {
|
|
10276
|
+
const filters = {};
|
|
10277
|
+
if (project_id)
|
|
10278
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10279
|
+
if (task_list_id)
|
|
10280
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10281
|
+
const work = getActiveWork(Object.keys(filters).length > 0 ? filters : undefined);
|
|
10282
|
+
if (work.length === 0) {
|
|
10283
|
+
return { content: [{ type: "text", text: "No active work \u2014 no tasks are currently in progress." }] };
|
|
10284
|
+
}
|
|
10285
|
+
const text = work.map((w) => {
|
|
10286
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
10287
|
+
const agent = w.assigned_to || w.locked_by || "unassigned";
|
|
10288
|
+
const since = w.updated_at;
|
|
10289
|
+
return `${agent.padEnd(12)} | ${w.priority.padEnd(8)} | ${id} | ${w.title} (since ${since})`;
|
|
10290
|
+
}).join(`
|
|
10291
|
+
`);
|
|
10292
|
+
return { content: [{ type: "text", text: `${work.length} active task(s):
|
|
10293
|
+
${text}` }] };
|
|
10294
|
+
} catch (e) {
|
|
10295
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10296
|
+
}
|
|
10297
|
+
});
|
|
10298
|
+
server.tool("get_tasks_changed_since", "Get tasks modified after a timestamp for incremental sync.", {
|
|
10299
|
+
since: exports_external.string(),
|
|
10300
|
+
project_id: exports_external.string().optional(),
|
|
10301
|
+
task_list_id: exports_external.string().optional()
|
|
10302
|
+
}, async ({ since, project_id, task_list_id }) => {
|
|
10303
|
+
try {
|
|
10304
|
+
const filters = {};
|
|
10305
|
+
if (project_id)
|
|
10306
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10307
|
+
if (task_list_id)
|
|
10308
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10309
|
+
const tasks = getTasksChangedSince(since, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10310
|
+
if (tasks.length === 0) {
|
|
10311
|
+
return { content: [{ type: "text", text: `No tasks changed since ${since}.` }] };
|
|
10312
|
+
}
|
|
10313
|
+
const text = tasks.map((t) => {
|
|
10314
|
+
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
10315
|
+
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned} (updated: ${t.updated_at})`;
|
|
10316
|
+
}).join(`
|
|
10317
|
+
`);
|
|
10318
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) changed since ${since}:
|
|
10319
|
+
${text}` }] };
|
|
10320
|
+
} catch (e) {
|
|
10321
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10322
|
+
}
|
|
10323
|
+
});
|
|
10324
|
+
server.tool("claim_next_task", "Atomically claim, lock, and start the best pending task.", {
|
|
10325
|
+
agent_id: exports_external.string(),
|
|
10326
|
+
project_id: exports_external.string().optional(),
|
|
10327
|
+
task_list_id: exports_external.string().optional(),
|
|
10328
|
+
plan_id: exports_external.string().optional(),
|
|
10329
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
10330
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
10331
|
+
try {
|
|
10332
|
+
const filters = {};
|
|
10333
|
+
if (project_id)
|
|
10334
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10335
|
+
if (task_list_id)
|
|
10336
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10337
|
+
if (plan_id)
|
|
10338
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
10339
|
+
if (tags)
|
|
10340
|
+
filters.tags = tags;
|
|
10341
|
+
const task = claimNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10342
|
+
if (!task) {
|
|
10343
|
+
return { content: [{ type: "text", text: "No tasks available to claim." }] };
|
|
10344
|
+
}
|
|
10345
|
+
return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
|
|
10346
|
+
${formatTaskDetail(task)}` }] };
|
|
10347
|
+
} catch (e) {
|
|
10348
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10349
|
+
}
|
|
10350
|
+
});
|
|
10351
|
+
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
10352
|
+
stale_minutes: exports_external.number().optional(),
|
|
10353
|
+
project_id: exports_external.string().optional(),
|
|
10354
|
+
task_list_id: exports_external.string().optional()
|
|
10355
|
+
}, async ({ stale_minutes, project_id, task_list_id }) => {
|
|
10356
|
+
try {
|
|
10357
|
+
const filters = {};
|
|
10358
|
+
if (project_id)
|
|
10359
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10360
|
+
if (task_list_id)
|
|
10361
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10362
|
+
const tasks = getStaleTasks(stale_minutes || 30, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10363
|
+
if (tasks.length === 0) {
|
|
10364
|
+
return { content: [{ type: "text", text: "No stale tasks found." }] };
|
|
10365
|
+
}
|
|
10366
|
+
const text = tasks.map((t) => {
|
|
10367
|
+
const id = t.short_id || t.id.slice(0, 8);
|
|
10368
|
+
const agent = t.locked_by || t.assigned_to || "unknown";
|
|
10369
|
+
const staleFor = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
10370
|
+
return `${id} | ${agent} | ${t.title} (stale ${staleFor}min)`;
|
|
10371
|
+
}).join(`
|
|
10372
|
+
`);
|
|
10373
|
+
return { content: [{ type: "text", text: `${tasks.length} stale task(s):
|
|
10374
|
+
${text}` }] };
|
|
10375
|
+
} catch (e) {
|
|
10376
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10377
|
+
}
|
|
10378
|
+
});
|
|
10379
|
+
server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
|
|
9384
10380
|
const all = [
|
|
9385
10381
|
"create_task",
|
|
9386
10382
|
"list_tasks",
|
|
@@ -9389,6 +10385,7 @@ In Progress:`);
|
|
|
9389
10385
|
"delete_task",
|
|
9390
10386
|
"start_task",
|
|
9391
10387
|
"complete_task",
|
|
10388
|
+
"fail_task",
|
|
9392
10389
|
"lock_task",
|
|
9393
10390
|
"unlock_task",
|
|
9394
10391
|
"approve_task",
|
|
@@ -9405,7 +10402,11 @@ In Progress:`);
|
|
|
9405
10402
|
"register_agent",
|
|
9406
10403
|
"list_agents",
|
|
9407
10404
|
"get_agent",
|
|
10405
|
+
"rename_agent",
|
|
10406
|
+
"delete_agent",
|
|
9408
10407
|
"get_my_tasks",
|
|
10408
|
+
"get_org_chart",
|
|
10409
|
+
"set_reports_to",
|
|
9409
10410
|
"create_task_list",
|
|
9410
10411
|
"list_task_lists",
|
|
9411
10412
|
"get_task_list",
|
|
@@ -9413,6 +10414,10 @@ In Progress:`);
|
|
|
9413
10414
|
"delete_task_list",
|
|
9414
10415
|
"search_tasks",
|
|
9415
10416
|
"sync",
|
|
10417
|
+
"clone_task",
|
|
10418
|
+
"move_task",
|
|
10419
|
+
"get_next_task",
|
|
10420
|
+
"claim_next_task",
|
|
9416
10421
|
"get_task_history",
|
|
9417
10422
|
"get_recent_activity",
|
|
9418
10423
|
"create_webhook",
|
|
@@ -9422,6 +10427,13 @@ In Progress:`);
|
|
|
9422
10427
|
"list_templates",
|
|
9423
10428
|
"create_task_from_template",
|
|
9424
10429
|
"delete_template",
|
|
10430
|
+
"bulk_update_tasks",
|
|
10431
|
+
"bulk_create_tasks",
|
|
10432
|
+
"get_task_stats",
|
|
10433
|
+
"get_task_graph",
|
|
10434
|
+
"get_active_work",
|
|
10435
|
+
"get_tasks_changed_since",
|
|
10436
|
+
"get_stale_tasks",
|
|
9425
10437
|
"search_tools",
|
|
9426
10438
|
"describe_tools"
|
|
9427
10439
|
];
|
|
@@ -9429,27 +10441,175 @@ In Progress:`);
|
|
|
9429
10441
|
const matches = q ? all.filter((n) => n.includes(q)) : all;
|
|
9430
10442
|
return { content: [{ type: "text", text: matches.join(", ") }] };
|
|
9431
10443
|
});
|
|
9432
|
-
server.tool("describe_tools", "Get
|
|
10444
|
+
server.tool("describe_tools", "Get detailed parameter info for specific tools by name.", { names: exports_external.array(exports_external.string()) }, async ({ names }) => {
|
|
9433
10445
|
const descriptions = {
|
|
9434
|
-
create_task:
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
10446
|
+
create_task: `Create a new task.
|
|
10447
|
+
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)
|
|
10448
|
+
Example: {title: 'Daily standup', recurrence_rule: 'every weekday', priority: 'medium'}`,
|
|
10449
|
+
list_tasks: `List tasks with optional filters. Supports pagination.
|
|
10450
|
+
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)
|
|
10451
|
+
Example: {status: ['pending', 'in_progress'], has_recurrence: true, limit: 20}`,
|
|
10452
|
+
get_task: `Get full task details with subtasks, deps, and comments.
|
|
10453
|
+
Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
|
|
10454
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10455
|
+
update_task: `Update task fields. Requires version for optimistic locking (get it from get_task first).
|
|
10456
|
+
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)
|
|
10457
|
+
Example: {id: 'a1b2c3d4', version: 3, status: 'completed'}`,
|
|
10458
|
+
delete_task: `Delete a task permanently. Subtasks cascade-delete. Dependencies removed.
|
|
10459
|
+
Params: id(string, req)
|
|
10460
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10461
|
+
start_task: `Claim, lock, and set task status to in_progress in one call.
|
|
10462
|
+
Params: id(string, req), agent_id(string, req \u2014 your 8-char agent ID)
|
|
10463
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10464
|
+
complete_task: `Mark task completed, release lock, set completed_at timestamp. For recurring tasks, auto-spawns next instance unless skip_recurrence is true.
|
|
10465
|
+
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)
|
|
10466
|
+
Example: {id: 'a1b2c3d4', skip_recurrence: false}`,
|
|
10467
|
+
lock_task: `Acquire exclusive lock on a task. Locks auto-expire after 30 min. Re-locking by same agent is idempotent.
|
|
10468
|
+
Params: id(string, req), agent_id(string, req)
|
|
10469
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10470
|
+
unlock_task: `Release exclusive lock on a task.
|
|
10471
|
+
Params: id(string, req), agent_id(string, optional \u2014 omit to force-unlock)
|
|
10472
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10473
|
+
approve_task: `Approve a task with requires_approval=true. Must be approved before completion.
|
|
10474
|
+
Params: id(string, req), agent_id(string, optional \u2014 defaults to 'system')
|
|
10475
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10476
|
+
fail_task: `Mark a task as failed with structured reason and optional auto-retry. Stores failure info in metadata._failure, releases lock.
|
|
10477
|
+
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)
|
|
10478
|
+
Example: {id: 'a1b2c3d4', reason: 'Build timeout', error_code: 'TIMEOUT', retry: true}`,
|
|
10479
|
+
add_dependency: `Add a dependency: task_id depends on depends_on. Prevents cycles via BFS.
|
|
10480
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
10481
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
10482
|
+
remove_dependency: `Remove a dependency link between two tasks.
|
|
10483
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
10484
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
10485
|
+
add_comment: `Add a comment/note to a task. Comments are append-only.
|
|
10486
|
+
Params: task_id(string, req), content(string, req), agent_id(string), session_id(string)
|
|
10487
|
+
Example: {task_id: 'a1b2c3d4', content: 'Blocked by API rate limit'}`,
|
|
10488
|
+
create_project: `Register a new project. Auto-generates task prefix for short IDs (e.g. APP-00001).
|
|
10489
|
+
Params: name(string, req), path(string, req \u2014 unique absolute path), description(string), task_list_id(string)
|
|
10490
|
+
Example: {name: 'my-app', path: '/Users/dev/my-app'}`,
|
|
10491
|
+
list_projects: "List all registered projects. No params.",
|
|
10492
|
+
create_plan: `Create a plan to group related tasks.
|
|
10493
|
+
Params: name(string, req), project_id(string), description(string), status(active|completed|archived, default:active), task_list_id(string), agent_id(string)
|
|
10494
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
10495
|
+
list_plans: `List all plans, optionally filtered by project.
|
|
10496
|
+
Params: project_id(string)
|
|
10497
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10498
|
+
get_plan: `Get plan details (name, status, description, timestamps).
|
|
10499
|
+
Params: id(string, req)
|
|
10500
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10501
|
+
update_plan: `Update plan fields.
|
|
10502
|
+
Params: id(string, req), name(string), description(string), status(active|completed|archived), task_list_id(string), agent_id(string)
|
|
10503
|
+
Example: {id: 'a1b2c3d4', status: 'completed'}`,
|
|
10504
|
+
delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
|
|
10505
|
+
Params: id(string, req)
|
|
10506
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10507
|
+
register_agent: `Register an agent (idempotent by name). Returns existing agent if name matches.
|
|
10508
|
+
Params: name(string, req \u2014 e.g. 'maximus'), description(string)
|
|
10509
|
+
Example: {name: 'maximus', description: 'Backend developer'}`,
|
|
10510
|
+
list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
|
|
10511
|
+
get_agent: `Get agent details by ID or name. Provide one of id or name.
|
|
10512
|
+
Params: id(string), name(string)
|
|
10513
|
+
Example: {name: 'maximus'}`,
|
|
10514
|
+
rename_agent: `Rename an agent. Resolve by id or current name.
|
|
10515
|
+
Params: id(string), name(string \u2014 current name), new_name(string, req)
|
|
10516
|
+
Example: {name: 'old-name', new_name: 'new-name'}`,
|
|
10517
|
+
delete_agent: `Delete an agent permanently. Resolve by id or name.
|
|
10518
|
+
Params: id(string), name(string)
|
|
10519
|
+
Example: {name: 'maximus'}`,
|
|
10520
|
+
get_my_tasks: `Get all tasks assigned to/created by an agent, with stats (pending/active/done/rate).
|
|
10521
|
+
Params: agent_name(string, req)
|
|
10522
|
+
Example: {agent_name: 'maximus'}`,
|
|
10523
|
+
get_org_chart: "Get agent org chart showing reporting hierarchy. No params.",
|
|
10524
|
+
set_reports_to: `Set who an agent reports to in the org chart. Omit manager_name for top-level.
|
|
10525
|
+
Params: agent_name(string, req), manager_name(string, optional)
|
|
10526
|
+
Example: {agent_name: 'brutus', manager_name: 'maximus'}`,
|
|
10527
|
+
create_task_list: `Create a task list \u2014 a container/folder for organizing tasks.
|
|
10528
|
+
Params: name(string, req), slug(string \u2014 auto-generated if omitted), project_id(string), description(string)
|
|
10529
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
10530
|
+
list_task_lists: `List all task lists, optionally filtered by project.
|
|
10531
|
+
Params: project_id(string)
|
|
10532
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10533
|
+
get_task_list: `Get task list details (name, slug, project, metadata).
|
|
10534
|
+
Params: id(string, req)
|
|
10535
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10536
|
+
update_task_list: `Update a task list's name or description.
|
|
10537
|
+
Params: id(string, req), name(string), description(string)
|
|
10538
|
+
Example: {id: 'a1b2c3d4', name: 'Sprint 2'}`,
|
|
10539
|
+
delete_task_list: `Delete a task list. Tasks are orphaned (not deleted).
|
|
10540
|
+
Params: id(string, req)
|
|
10541
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10542
|
+
search_tasks: `Full-text search across task titles, descriptions, and tags. Supports filters.
|
|
10543
|
+
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)
|
|
10544
|
+
Example: {query: 'auth bug', status: 'pending'}`,
|
|
10545
|
+
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.
|
|
10546
|
+
Params: agent_id(string \u2014 prefers your tasks), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
10547
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
10548
|
+
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.
|
|
10549
|
+
Params: agent_id(string, req \u2014 used for lock and assignment), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
10550
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
10551
|
+
sync: `Sync tasks between local DB and agent task list (e.g. Claude Code).
|
|
10552
|
+
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)
|
|
10553
|
+
Example: {agent: 'claude', direction: 'push'}`,
|
|
10554
|
+
clone_task: `Duplicate a task with optional field overrides. Creates new independent copy.
|
|
10555
|
+
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)
|
|
10556
|
+
Example: {task_id: 'a1b2c3d4', title: 'Cloned task', assigned_to: 'brutus'}`,
|
|
10557
|
+
move_task: `Move a task to a different list, project, or plan.
|
|
10558
|
+
Params: task_id(string, req), task_list_id(string|null), project_id(string|null), plan_id(string|null)
|
|
10559
|
+
Example: {task_id: 'a1b2c3d4', task_list_id: 'e5f6g7h8'}`,
|
|
10560
|
+
bulk_update_tasks: `Update multiple tasks at once with the same changes.
|
|
10561
|
+
Params: task_ids(string[], req), status(pending|in_progress|completed|failed|cancelled), priority(low|medium|high|critical), assigned_to(string), tags(string[])
|
|
10562
|
+
Example: {task_ids: ['abc12345', 'def67890'], status: 'completed'}`,
|
|
10563
|
+
bulk_create_tasks: `Create multiple tasks atomically. Supports inter-task dependencies via temp_id references.
|
|
10564
|
+
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)
|
|
10565
|
+
Example: {tasks: [{temp_id: 'a', title: 'First'}, {temp_id: 'b', title: 'Second', depends_on_temp_ids: ['a']}]}`,
|
|
10566
|
+
get_task_stats: `Get task analytics: counts by status, priority, agent, and completion rate. All via SQL.
|
|
10567
|
+
Params: project_id(string), task_list_id(string), agent_id(string)
|
|
10568
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10569
|
+
get_task_graph: `Get full dependency tree for a task \u2014 upstream blockers and downstream dependents.
|
|
10570
|
+
Params: id(string, req), direction(up|down|both, default:both)
|
|
10571
|
+
Example: {id: 'a1b2c3d4', direction: 'up'}`,
|
|
10572
|
+
get_task_history: `Get audit log for a task \u2014 all field changes with timestamps and actors.
|
|
10573
|
+
Params: task_id(string, req)
|
|
10574
|
+
Example: {task_id: 'a1b2c3d4'}`,
|
|
10575
|
+
get_recent_activity: `Get recent task changes across all tasks \u2014 global activity feed.
|
|
10576
|
+
Params: limit(number, default:50)
|
|
10577
|
+
Example: {limit: 20}`,
|
|
10578
|
+
create_webhook: `Register a webhook for task change events.
|
|
10579
|
+
Params: url(string, req), events(string[] \u2014 empty=all), secret(string \u2014 HMAC signing)
|
|
10580
|
+
Example: {url: 'https://example.com/hook', events: ['task.created', 'task.completed']}`,
|
|
10581
|
+
list_webhooks: "List all registered webhooks. No params.",
|
|
10582
|
+
delete_webhook: `Delete a webhook by ID.
|
|
10583
|
+
Params: id(string, req)
|
|
10584
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10585
|
+
create_template: `Create a reusable task template.
|
|
10586
|
+
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)
|
|
10587
|
+
Example: {name: 'Bug Report', title_pattern: 'Bug: {description}', priority: 'high', tags: ['bug']}`,
|
|
10588
|
+
list_templates: "List all task templates. No params.",
|
|
10589
|
+
create_task_from_template: `Create a task from a template with optional overrides.
|
|
10590
|
+
Params: template_id(string, req), title(string), description(string), priority(low|medium|high|critical), assigned_to(string), project_id(string)
|
|
10591
|
+
Example: {template_id: 'a1b2c3d4', assigned_to: 'maximus'}`,
|
|
10592
|
+
delete_template: `Delete a task template.
|
|
10593
|
+
Params: id(string, req)
|
|
10594
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10595
|
+
get_active_work: `See all in-progress tasks and who is working on them.
|
|
10596
|
+
Params: project_id(string, optional), task_list_id(string, optional)
|
|
10597
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10598
|
+
get_tasks_changed_since: `Get tasks modified after a timestamp \u2014 incremental delta sync.
|
|
10599
|
+
Params: since(string, req \u2014 ISO date), project_id(string, optional), task_list_id(string, optional)
|
|
10600
|
+
Example: {since: '2026-03-14T10:00:00Z'}`,
|
|
10601
|
+
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
10602
|
+
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
10603
|
+
Example: {stale_minutes: 60, project_id: 'a1b2c3d4'}`,
|
|
10604
|
+
search_tools: `List all tool names or filter by substring.
|
|
10605
|
+
Params: query(string, optional)
|
|
10606
|
+
Example: {query: 'task'}`,
|
|
10607
|
+
describe_tools: `Get detailed descriptions and parameter info for tools by name.
|
|
10608
|
+
Params: names(string[], req)
|
|
10609
|
+
Example: {names: ['create_task', 'update_task']}`
|
|
9451
10610
|
};
|
|
9452
|
-
const result = names.map((n) => `${n}: ${descriptions[n] || "
|
|
10611
|
+
const result = names.map((n) => `${n}: ${descriptions[n] || "Unknown tool. Use search_tools to list available tools."}`).join(`
|
|
10612
|
+
|
|
9453
10613
|
`);
|
|
9454
10614
|
return { content: [{ type: "text", text: result }] };
|
|
9455
10615
|
});
|