@hasna/todos 0.9.34 → 0.9.37
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 +1402 -154
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +127 -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 +709 -52
- 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 +1450 -192
- package/dist/server/index.js +239 -58
- 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,147 @@ 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
|
+
|
|
3104
|
+
// src/db/webhooks.ts
|
|
3105
|
+
var exports_webhooks = {};
|
|
3106
|
+
__export(exports_webhooks, {
|
|
3107
|
+
listWebhooks: () => listWebhooks,
|
|
3108
|
+
getWebhook: () => getWebhook,
|
|
3109
|
+
dispatchWebhook: () => dispatchWebhook,
|
|
3110
|
+
deleteWebhook: () => deleteWebhook,
|
|
3111
|
+
createWebhook: () => createWebhook
|
|
3112
|
+
});
|
|
3113
|
+
function rowToWebhook(row) {
|
|
3114
|
+
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
3115
|
+
}
|
|
3116
|
+
function createWebhook(input, db) {
|
|
3117
|
+
const d = db || getDatabase();
|
|
3118
|
+
const id = uuid();
|
|
3119
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
3120
|
+
return getWebhook(id, d);
|
|
3121
|
+
}
|
|
3122
|
+
function getWebhook(id, db) {
|
|
3123
|
+
const d = db || getDatabase();
|
|
3124
|
+
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
3125
|
+
return row ? rowToWebhook(row) : null;
|
|
3126
|
+
}
|
|
3127
|
+
function listWebhooks(db) {
|
|
3128
|
+
const d = db || getDatabase();
|
|
3129
|
+
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
3130
|
+
}
|
|
3131
|
+
function deleteWebhook(id, db) {
|
|
3132
|
+
const d = db || getDatabase();
|
|
3133
|
+
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
3134
|
+
}
|
|
3135
|
+
async function dispatchWebhook(event, payload, db) {
|
|
3136
|
+
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
3137
|
+
for (const wh of webhooks) {
|
|
3138
|
+
try {
|
|
3139
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
3140
|
+
const headers = { "Content-Type": "application/json" };
|
|
3141
|
+
if (wh.secret) {
|
|
3142
|
+
const encoder = new TextEncoder;
|
|
3143
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
3144
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
3145
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3146
|
+
}
|
|
3147
|
+
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
3148
|
+
} catch {}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
var init_webhooks = __esm(() => {
|
|
3152
|
+
init_database();
|
|
3153
|
+
});
|
|
3154
|
+
|
|
2977
3155
|
// src/db/tasks.ts
|
|
2978
3156
|
function rowToTask(row) {
|
|
2979
3157
|
return {
|
|
@@ -3005,8 +3183,8 @@ function createTask(input, db) {
|
|
|
3005
3183
|
const tags = input.tags || [];
|
|
3006
3184
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
3007
3185
|
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, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3186
|
+
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)
|
|
3187
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3010
3188
|
id,
|
|
3011
3189
|
shortId,
|
|
3012
3190
|
input.project_id || null,
|
|
@@ -3029,12 +3207,16 @@ function createTask(input, db) {
|
|
|
3029
3207
|
input.estimated_minutes || null,
|
|
3030
3208
|
input.requires_approval ? 1 : 0,
|
|
3031
3209
|
null,
|
|
3032
|
-
null
|
|
3210
|
+
null,
|
|
3211
|
+
input.recurrence_rule || null,
|
|
3212
|
+
input.recurrence_parent_id || null
|
|
3033
3213
|
]);
|
|
3034
3214
|
if (tags.length > 0) {
|
|
3035
3215
|
insertTaskTags(id, tags, d);
|
|
3036
3216
|
}
|
|
3037
|
-
|
|
3217
|
+
const task = getTask(id, d);
|
|
3218
|
+
dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
3219
|
+
return task;
|
|
3038
3220
|
}
|
|
3039
3221
|
function getTask(id, db) {
|
|
3040
3222
|
const d = db || getDatabase();
|
|
@@ -3129,6 +3311,11 @@ function listTasks(filter = {}, db) {
|
|
|
3129
3311
|
conditions.push("task_list_id = ?");
|
|
3130
3312
|
params.push(filter.task_list_id);
|
|
3131
3313
|
}
|
|
3314
|
+
if (filter.has_recurrence === true) {
|
|
3315
|
+
conditions.push("recurrence_rule IS NOT NULL");
|
|
3316
|
+
} else if (filter.has_recurrence === false) {
|
|
3317
|
+
conditions.push("recurrence_rule IS NULL");
|
|
3318
|
+
}
|
|
3132
3319
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3133
3320
|
let limitClause = "";
|
|
3134
3321
|
if (filter.limit) {
|
|
@@ -3144,6 +3331,69 @@ function listTasks(filter = {}, db) {
|
|
|
3144
3331
|
created_at DESC${limitClause}`).all(...params);
|
|
3145
3332
|
return rows.map(rowToTask);
|
|
3146
3333
|
}
|
|
3334
|
+
function countTasks(filter = {}, db) {
|
|
3335
|
+
const d = db || getDatabase();
|
|
3336
|
+
const conditions = [];
|
|
3337
|
+
const params = [];
|
|
3338
|
+
if (filter.project_id) {
|
|
3339
|
+
conditions.push("project_id = ?");
|
|
3340
|
+
params.push(filter.project_id);
|
|
3341
|
+
}
|
|
3342
|
+
if (filter.parent_id !== undefined) {
|
|
3343
|
+
if (filter.parent_id === null) {
|
|
3344
|
+
conditions.push("parent_id IS NULL");
|
|
3345
|
+
} else {
|
|
3346
|
+
conditions.push("parent_id = ?");
|
|
3347
|
+
params.push(filter.parent_id);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
if (filter.status) {
|
|
3351
|
+
if (Array.isArray(filter.status)) {
|
|
3352
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
3353
|
+
params.push(...filter.status);
|
|
3354
|
+
} else {
|
|
3355
|
+
conditions.push("status = ?");
|
|
3356
|
+
params.push(filter.status);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
if (filter.priority) {
|
|
3360
|
+
if (Array.isArray(filter.priority)) {
|
|
3361
|
+
conditions.push(`priority IN (${filter.priority.map(() => "?").join(",")})`);
|
|
3362
|
+
params.push(...filter.priority);
|
|
3363
|
+
} else {
|
|
3364
|
+
conditions.push("priority = ?");
|
|
3365
|
+
params.push(filter.priority);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
if (filter.assigned_to) {
|
|
3369
|
+
conditions.push("assigned_to = ?");
|
|
3370
|
+
params.push(filter.assigned_to);
|
|
3371
|
+
}
|
|
3372
|
+
if (filter.agent_id) {
|
|
3373
|
+
conditions.push("agent_id = ?");
|
|
3374
|
+
params.push(filter.agent_id);
|
|
3375
|
+
}
|
|
3376
|
+
if (filter.session_id) {
|
|
3377
|
+
conditions.push("session_id = ?");
|
|
3378
|
+
params.push(filter.session_id);
|
|
3379
|
+
}
|
|
3380
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
3381
|
+
const placeholders = filter.tags.map(() => "?").join(",");
|
|
3382
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
3383
|
+
params.push(...filter.tags);
|
|
3384
|
+
}
|
|
3385
|
+
if (filter.plan_id) {
|
|
3386
|
+
conditions.push("plan_id = ?");
|
|
3387
|
+
params.push(filter.plan_id);
|
|
3388
|
+
}
|
|
3389
|
+
if (filter.task_list_id) {
|
|
3390
|
+
conditions.push("task_list_id = ?");
|
|
3391
|
+
params.push(filter.task_list_id);
|
|
3392
|
+
}
|
|
3393
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3394
|
+
const row = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
3395
|
+
return row.count;
|
|
3396
|
+
}
|
|
3147
3397
|
function updateTask(id, input, db) {
|
|
3148
3398
|
const d = db || getDatabase();
|
|
3149
3399
|
const task = getTask(id, d);
|
|
@@ -3215,6 +3465,10 @@ function updateTask(id, input, db) {
|
|
|
3215
3465
|
sets.push("approved_at = ?");
|
|
3216
3466
|
params.push(now());
|
|
3217
3467
|
}
|
|
3468
|
+
if (input.recurrence_rule !== undefined) {
|
|
3469
|
+
sets.push("recurrence_rule = ?");
|
|
3470
|
+
params.push(input.recurrence_rule);
|
|
3471
|
+
}
|
|
3218
3472
|
params.push(id, input.version);
|
|
3219
3473
|
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
3220
3474
|
if (result.changes === 0) {
|
|
@@ -3235,6 +3489,12 @@ function updateTask(id, input, db) {
|
|
|
3235
3489
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
3236
3490
|
if (input.approved_by !== undefined)
|
|
3237
3491
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
3492
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
3493
|
+
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
3494
|
+
}
|
|
3495
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
3496
|
+
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
3497
|
+
}
|
|
3238
3498
|
return {
|
|
3239
3499
|
...task,
|
|
3240
3500
|
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
@@ -3286,9 +3546,10 @@ function startTask(id, agentId, db) {
|
|
|
3286
3546
|
}
|
|
3287
3547
|
}
|
|
3288
3548
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
3549
|
+
dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
3289
3550
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
3290
3551
|
}
|
|
3291
|
-
function completeTask(id, agentId, db,
|
|
3552
|
+
function completeTask(id, agentId, db, options) {
|
|
3292
3553
|
const d = db || getDatabase();
|
|
3293
3554
|
const task = getTask(id, d);
|
|
3294
3555
|
if (!task)
|
|
@@ -3297,7 +3558,9 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
3297
3558
|
throw new LockError(id, task.locked_by);
|
|
3298
3559
|
}
|
|
3299
3560
|
checkCompletionGuard(task, agentId || null, d);
|
|
3300
|
-
|
|
3561
|
+
const evidence = options ? { files_changed: options.files_changed, test_results: options.test_results, commit_hash: options.commit_hash, notes: options.notes } : undefined;
|
|
3562
|
+
const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes);
|
|
3563
|
+
if (hasEvidence) {
|
|
3301
3564
|
const meta2 = { ...task.metadata, _evidence: evidence };
|
|
3302
3565
|
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
|
|
3303
3566
|
}
|
|
@@ -3305,7 +3568,15 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
3305
3568
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
3306
3569
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
3307
3570
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
3308
|
-
|
|
3571
|
+
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
3572
|
+
let spawnedTask = null;
|
|
3573
|
+
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
3574
|
+
spawnedTask = spawnNextRecurrence(task, d);
|
|
3575
|
+
}
|
|
3576
|
+
const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
|
|
3577
|
+
if (spawnedTask) {
|
|
3578
|
+
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
3579
|
+
}
|
|
3309
3580
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
3310
3581
|
}
|
|
3311
3582
|
function lockTask(id, agentId, db) {
|
|
@@ -3368,6 +3639,298 @@ function getTaskDependencies(taskId, db) {
|
|
|
3368
3639
|
const d = db || getDatabase();
|
|
3369
3640
|
return d.query("SELECT * FROM task_dependencies WHERE task_id = ?").all(taskId);
|
|
3370
3641
|
}
|
|
3642
|
+
function cloneTask(taskId, overrides, db) {
|
|
3643
|
+
const d = db || getDatabase();
|
|
3644
|
+
const source = getTask(taskId, d);
|
|
3645
|
+
if (!source)
|
|
3646
|
+
throw new TaskNotFoundError(taskId);
|
|
3647
|
+
const input = {
|
|
3648
|
+
title: overrides?.title ?? source.title,
|
|
3649
|
+
description: overrides?.description ?? source.description ?? undefined,
|
|
3650
|
+
priority: overrides?.priority ?? source.priority,
|
|
3651
|
+
project_id: overrides?.project_id ?? source.project_id ?? undefined,
|
|
3652
|
+
parent_id: overrides?.parent_id ?? source.parent_id ?? undefined,
|
|
3653
|
+
plan_id: overrides?.plan_id ?? source.plan_id ?? undefined,
|
|
3654
|
+
task_list_id: overrides?.task_list_id ?? source.task_list_id ?? undefined,
|
|
3655
|
+
status: overrides?.status ?? "pending",
|
|
3656
|
+
agent_id: overrides?.agent_id ?? source.agent_id ?? undefined,
|
|
3657
|
+
assigned_to: overrides?.assigned_to ?? source.assigned_to ?? undefined,
|
|
3658
|
+
tags: overrides?.tags ?? source.tags,
|
|
3659
|
+
metadata: overrides?.metadata ?? source.metadata,
|
|
3660
|
+
estimated_minutes: overrides?.estimated_minutes ?? source.estimated_minutes ?? undefined,
|
|
3661
|
+
recurrence_rule: overrides?.recurrence_rule ?? source.recurrence_rule ?? undefined
|
|
3662
|
+
};
|
|
3663
|
+
return createTask(input, d);
|
|
3664
|
+
}
|
|
3665
|
+
function getTaskGraph(taskId, direction = "both", db) {
|
|
3666
|
+
const d = db || getDatabase();
|
|
3667
|
+
const task = getTask(taskId, d);
|
|
3668
|
+
if (!task)
|
|
3669
|
+
throw new TaskNotFoundError(taskId);
|
|
3670
|
+
function toNode(t) {
|
|
3671
|
+
const deps = getTaskDependencies(t.id, d);
|
|
3672
|
+
const hasUnfinishedDeps = deps.some((dep) => {
|
|
3673
|
+
const depTask = getTask(dep.depends_on, d);
|
|
3674
|
+
return depTask && depTask.status !== "completed";
|
|
3675
|
+
});
|
|
3676
|
+
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
3677
|
+
}
|
|
3678
|
+
function buildUp(id, visited) {
|
|
3679
|
+
if (visited.has(id))
|
|
3680
|
+
return [];
|
|
3681
|
+
visited.add(id);
|
|
3682
|
+
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
3683
|
+
return deps.map((dep) => {
|
|
3684
|
+
const depTask = getTask(dep.depends_on, d);
|
|
3685
|
+
if (!depTask)
|
|
3686
|
+
return null;
|
|
3687
|
+
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
3688
|
+
}).filter(Boolean);
|
|
3689
|
+
}
|
|
3690
|
+
function buildDown(id, visited) {
|
|
3691
|
+
if (visited.has(id))
|
|
3692
|
+
return [];
|
|
3693
|
+
visited.add(id);
|
|
3694
|
+
const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
|
|
3695
|
+
return dependents.map((dep) => {
|
|
3696
|
+
const depTask = getTask(dep.task_id, d);
|
|
3697
|
+
if (!depTask)
|
|
3698
|
+
return null;
|
|
3699
|
+
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
3700
|
+
}).filter(Boolean);
|
|
3701
|
+
}
|
|
3702
|
+
const rootNode = toNode(task);
|
|
3703
|
+
const depends_on = direction === "up" || direction === "both" ? buildUp(taskId, new Set) : [];
|
|
3704
|
+
const blocks = direction === "down" || direction === "both" ? buildDown(taskId, new Set) : [];
|
|
3705
|
+
return { task: rootNode, depends_on, blocks };
|
|
3706
|
+
}
|
|
3707
|
+
function moveTask(taskId, target, db) {
|
|
3708
|
+
const d = db || getDatabase();
|
|
3709
|
+
const task = getTask(taskId, d);
|
|
3710
|
+
if (!task)
|
|
3711
|
+
throw new TaskNotFoundError(taskId);
|
|
3712
|
+
const sets = ["updated_at = ?", "version = version + 1"];
|
|
3713
|
+
const params = [now()];
|
|
3714
|
+
if (target.task_list_id !== undefined) {
|
|
3715
|
+
sets.push("task_list_id = ?");
|
|
3716
|
+
params.push(target.task_list_id);
|
|
3717
|
+
}
|
|
3718
|
+
if (target.project_id !== undefined) {
|
|
3719
|
+
sets.push("project_id = ?");
|
|
3720
|
+
params.push(target.project_id);
|
|
3721
|
+
}
|
|
3722
|
+
if (target.plan_id !== undefined) {
|
|
3723
|
+
sets.push("plan_id = ?");
|
|
3724
|
+
params.push(target.plan_id);
|
|
3725
|
+
}
|
|
3726
|
+
params.push(taskId);
|
|
3727
|
+
d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3728
|
+
return getTask(taskId, d);
|
|
3729
|
+
}
|
|
3730
|
+
function spawnNextRecurrence(completedTask, db) {
|
|
3731
|
+
const dueAt = nextOccurrence(completedTask.recurrence_rule, new Date);
|
|
3732
|
+
let title = completedTask.title;
|
|
3733
|
+
if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
|
|
3734
|
+
title = title.slice(completedTask.short_id.length + 2);
|
|
3735
|
+
}
|
|
3736
|
+
const recurrenceParentId = completedTask.recurrence_parent_id || completedTask.id;
|
|
3737
|
+
return createTask({
|
|
3738
|
+
title,
|
|
3739
|
+
description: completedTask.description ?? undefined,
|
|
3740
|
+
priority: completedTask.priority,
|
|
3741
|
+
project_id: completedTask.project_id ?? undefined,
|
|
3742
|
+
task_list_id: completedTask.task_list_id ?? undefined,
|
|
3743
|
+
plan_id: completedTask.plan_id ?? undefined,
|
|
3744
|
+
assigned_to: completedTask.assigned_to ?? undefined,
|
|
3745
|
+
tags: completedTask.tags,
|
|
3746
|
+
metadata: completedTask.metadata,
|
|
3747
|
+
estimated_minutes: completedTask.estimated_minutes ?? undefined,
|
|
3748
|
+
recurrence_rule: completedTask.recurrence_rule,
|
|
3749
|
+
recurrence_parent_id: recurrenceParentId,
|
|
3750
|
+
due_at: dueAt
|
|
3751
|
+
}, db);
|
|
3752
|
+
}
|
|
3753
|
+
function claimNextTask(agentId, filters, db) {
|
|
3754
|
+
const d = db || getDatabase();
|
|
3755
|
+
const tx = d.transaction(() => {
|
|
3756
|
+
const task = getNextTask(agentId, filters, d);
|
|
3757
|
+
if (!task)
|
|
3758
|
+
return null;
|
|
3759
|
+
return startTask(task.id, agentId, d);
|
|
3760
|
+
});
|
|
3761
|
+
return tx();
|
|
3762
|
+
}
|
|
3763
|
+
function getNextTask(agentId, filters, db) {
|
|
3764
|
+
const d = db || getDatabase();
|
|
3765
|
+
clearExpiredLocks(d);
|
|
3766
|
+
const conditions = ["status = 'pending'", "(locked_by IS NULL OR locked_at < ?)"];
|
|
3767
|
+
const params = [lockExpiryCutoff()];
|
|
3768
|
+
if (filters?.project_id) {
|
|
3769
|
+
conditions.push("project_id = ?");
|
|
3770
|
+
params.push(filters.project_id);
|
|
3771
|
+
}
|
|
3772
|
+
if (filters?.task_list_id) {
|
|
3773
|
+
conditions.push("task_list_id = ?");
|
|
3774
|
+
params.push(filters.task_list_id);
|
|
3775
|
+
}
|
|
3776
|
+
if (filters?.plan_id) {
|
|
3777
|
+
conditions.push("plan_id = ?");
|
|
3778
|
+
params.push(filters.plan_id);
|
|
3779
|
+
}
|
|
3780
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
3781
|
+
const placeholders = filters.tags.map(() => "?").join(",");
|
|
3782
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
3783
|
+
params.push(...filters.tags);
|
|
3784
|
+
}
|
|
3785
|
+
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')");
|
|
3786
|
+
const where = conditions.join(" AND ");
|
|
3787
|
+
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
3788
|
+
if (agentId) {
|
|
3789
|
+
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
3790
|
+
params.push(agentId);
|
|
3791
|
+
}
|
|
3792
|
+
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`;
|
|
3793
|
+
const row = d.query(sql).get(...params);
|
|
3794
|
+
return row ? rowToTask(row) : null;
|
|
3795
|
+
}
|
|
3796
|
+
function getActiveWork(filters, db) {
|
|
3797
|
+
const d = db || getDatabase();
|
|
3798
|
+
clearExpiredLocks(d);
|
|
3799
|
+
const conditions = ["status = 'in_progress'"];
|
|
3800
|
+
const params = [];
|
|
3801
|
+
if (filters?.project_id) {
|
|
3802
|
+
conditions.push("project_id = ?");
|
|
3803
|
+
params.push(filters.project_id);
|
|
3804
|
+
}
|
|
3805
|
+
if (filters?.task_list_id) {
|
|
3806
|
+
conditions.push("task_list_id = ?");
|
|
3807
|
+
params.push(filters.task_list_id);
|
|
3808
|
+
}
|
|
3809
|
+
const where = conditions.join(" AND ");
|
|
3810
|
+
const rows = d.query(`SELECT id, short_id, title, priority, assigned_to, locked_by, locked_at, updated_at FROM tasks WHERE ${where} ORDER BY
|
|
3811
|
+
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
3812
|
+
updated_at DESC`).all(...params);
|
|
3813
|
+
return rows;
|
|
3814
|
+
}
|
|
3815
|
+
function getTasksChangedSince(since, filters, db) {
|
|
3816
|
+
const d = db || getDatabase();
|
|
3817
|
+
const conditions = ["updated_at > ?"];
|
|
3818
|
+
const params = [since];
|
|
3819
|
+
if (filters?.project_id) {
|
|
3820
|
+
conditions.push("project_id = ?");
|
|
3821
|
+
params.push(filters.project_id);
|
|
3822
|
+
}
|
|
3823
|
+
if (filters?.task_list_id) {
|
|
3824
|
+
conditions.push("task_list_id = ?");
|
|
3825
|
+
params.push(filters.task_list_id);
|
|
3826
|
+
}
|
|
3827
|
+
const where = conditions.join(" AND ");
|
|
3828
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at DESC`).all(...params);
|
|
3829
|
+
return rows.map(rowToTask);
|
|
3830
|
+
}
|
|
3831
|
+
function failTask(id, agentId, reason, options, db) {
|
|
3832
|
+
const d = db || getDatabase();
|
|
3833
|
+
const task = getTask(id, d);
|
|
3834
|
+
if (!task)
|
|
3835
|
+
throw new TaskNotFoundError(id);
|
|
3836
|
+
const meta = {
|
|
3837
|
+
...task.metadata,
|
|
3838
|
+
_failure: {
|
|
3839
|
+
reason: reason || "Unknown failure",
|
|
3840
|
+
error_code: options?.error_code || null,
|
|
3841
|
+
failed_by: agentId || null,
|
|
3842
|
+
failed_at: now(),
|
|
3843
|
+
retry_requested: options?.retry || false
|
|
3844
|
+
}
|
|
3845
|
+
};
|
|
3846
|
+
const timestamp = now();
|
|
3847
|
+
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
3848
|
+
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
3849
|
+
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
3850
|
+
dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
3851
|
+
const failedTask = {
|
|
3852
|
+
...task,
|
|
3853
|
+
status: "failed",
|
|
3854
|
+
locked_by: null,
|
|
3855
|
+
locked_at: null,
|
|
3856
|
+
metadata: meta,
|
|
3857
|
+
version: task.version + 1,
|
|
3858
|
+
updated_at: timestamp
|
|
3859
|
+
};
|
|
3860
|
+
let retryTask;
|
|
3861
|
+
if (options?.retry) {
|
|
3862
|
+
let title = task.title;
|
|
3863
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
3864
|
+
title = title.slice(task.short_id.length + 2);
|
|
3865
|
+
}
|
|
3866
|
+
retryTask = createTask({
|
|
3867
|
+
title,
|
|
3868
|
+
description: task.description ?? undefined,
|
|
3869
|
+
priority: task.priority,
|
|
3870
|
+
project_id: task.project_id ?? undefined,
|
|
3871
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
3872
|
+
plan_id: task.plan_id ?? undefined,
|
|
3873
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
3874
|
+
tags: task.tags,
|
|
3875
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
|
|
3876
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
3877
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
3878
|
+
due_at: options.retry_after || task.due_at || undefined
|
|
3879
|
+
}, d);
|
|
3880
|
+
}
|
|
3881
|
+
return { task: failedTask, retryTask };
|
|
3882
|
+
}
|
|
3883
|
+
function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
3884
|
+
const d = db || getDatabase();
|
|
3885
|
+
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
3886
|
+
const conditions = [
|
|
3887
|
+
"status = 'in_progress'",
|
|
3888
|
+
"(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
|
|
3889
|
+
];
|
|
3890
|
+
const params = [cutoff, cutoff];
|
|
3891
|
+
if (filters?.project_id) {
|
|
3892
|
+
conditions.push("project_id = ?");
|
|
3893
|
+
params.push(filters.project_id);
|
|
3894
|
+
}
|
|
3895
|
+
if (filters?.task_list_id) {
|
|
3896
|
+
conditions.push("task_list_id = ?");
|
|
3897
|
+
params.push(filters.task_list_id);
|
|
3898
|
+
}
|
|
3899
|
+
const where = conditions.join(" AND ");
|
|
3900
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
3901
|
+
return rows.map(rowToTask);
|
|
3902
|
+
}
|
|
3903
|
+
function getStatus(filters, agentId, db) {
|
|
3904
|
+
const d = db || getDatabase();
|
|
3905
|
+
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
3906
|
+
const in_progress = countTasks({ ...filters, status: "in_progress" }, d);
|
|
3907
|
+
const completed = countTasks({ ...filters, status: "completed" }, d);
|
|
3908
|
+
const total = countTasks(filters || {}, d);
|
|
3909
|
+
const active_work = getActiveWork(filters, d);
|
|
3910
|
+
const next_task = getNextTask(agentId, filters, d);
|
|
3911
|
+
const stale = getStaleTasks(30, filters, d);
|
|
3912
|
+
const conditions = ["recurrence_rule IS NOT NULL", "status = 'pending'", "due_at < ?"];
|
|
3913
|
+
const params = [now()];
|
|
3914
|
+
if (filters?.project_id) {
|
|
3915
|
+
conditions.push("project_id = ?");
|
|
3916
|
+
params.push(filters.project_id);
|
|
3917
|
+
}
|
|
3918
|
+
if (filters?.task_list_id) {
|
|
3919
|
+
conditions.push("task_list_id = ?");
|
|
3920
|
+
params.push(filters.task_list_id);
|
|
3921
|
+
}
|
|
3922
|
+
const overdueRow = d.query(`SELECT COUNT(*) as count FROM tasks WHERE ${conditions.join(" AND ")}`).get(...params);
|
|
3923
|
+
return {
|
|
3924
|
+
pending,
|
|
3925
|
+
in_progress,
|
|
3926
|
+
completed,
|
|
3927
|
+
total,
|
|
3928
|
+
active_work,
|
|
3929
|
+
next_task,
|
|
3930
|
+
stale_count: stale.length,
|
|
3931
|
+
overdue_recurring: overdueRow.count
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3371
3934
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
3372
3935
|
const visited = new Set;
|
|
3373
3936
|
const queue = [dependsOn];
|
|
@@ -3385,12 +3948,99 @@ function wouldCreateCycle(taskId, dependsOn, db) {
|
|
|
3385
3948
|
}
|
|
3386
3949
|
return false;
|
|
3387
3950
|
}
|
|
3951
|
+
function getTaskStats(filters, db) {
|
|
3952
|
+
const d = db || getDatabase();
|
|
3953
|
+
const conditions = [];
|
|
3954
|
+
const params = [];
|
|
3955
|
+
if (filters?.project_id) {
|
|
3956
|
+
conditions.push("project_id = ?");
|
|
3957
|
+
params.push(filters.project_id);
|
|
3958
|
+
}
|
|
3959
|
+
if (filters?.task_list_id) {
|
|
3960
|
+
conditions.push("task_list_id = ?");
|
|
3961
|
+
params.push(filters.task_list_id);
|
|
3962
|
+
}
|
|
3963
|
+
if (filters?.agent_id) {
|
|
3964
|
+
conditions.push("(agent_id = ? OR assigned_to = ?)");
|
|
3965
|
+
params.push(filters.agent_id, filters.agent_id);
|
|
3966
|
+
}
|
|
3967
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3968
|
+
const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
3969
|
+
const statusRows = d.query(`SELECT status, COUNT(*) as count FROM tasks ${where} GROUP BY status`).all(...params);
|
|
3970
|
+
const by_status = {};
|
|
3971
|
+
for (const r of statusRows)
|
|
3972
|
+
by_status[r.status] = r.count;
|
|
3973
|
+
const priorityRows = d.query(`SELECT priority, COUNT(*) as count FROM tasks ${where} GROUP BY priority`).all(...params);
|
|
3974
|
+
const by_priority = {};
|
|
3975
|
+
for (const r of priorityRows)
|
|
3976
|
+
by_priority[r.priority] = r.count;
|
|
3977
|
+
const agentRows = d.query(`SELECT COALESCE(assigned_to, agent_id, 'unassigned') as agent, COUNT(*) as count FROM tasks ${where} GROUP BY agent`).all(...params);
|
|
3978
|
+
const by_agent = {};
|
|
3979
|
+
for (const r of agentRows)
|
|
3980
|
+
by_agent[r.agent] = r.count;
|
|
3981
|
+
const completed = by_status["completed"] || 0;
|
|
3982
|
+
const completion_rate = totalRow.count > 0 ? Math.round(completed / totalRow.count * 100) : 0;
|
|
3983
|
+
return { total: totalRow.count, by_status, by_priority, completion_rate, by_agent };
|
|
3984
|
+
}
|
|
3985
|
+
function bulkCreateTasks(inputs, db) {
|
|
3986
|
+
const d = db || getDatabase();
|
|
3987
|
+
const tempIdToRealId = new Map;
|
|
3988
|
+
const created = [];
|
|
3989
|
+
const tx = d.transaction(() => {
|
|
3990
|
+
for (const input of inputs) {
|
|
3991
|
+
const { temp_id, depends_on_temp_ids: _deps, ...createInput } = input;
|
|
3992
|
+
const task = createTask(createInput, d);
|
|
3993
|
+
if (temp_id)
|
|
3994
|
+
tempIdToRealId.set(temp_id, task.id);
|
|
3995
|
+
created.push({ temp_id: temp_id || null, id: task.id, short_id: task.short_id, title: task.title });
|
|
3996
|
+
}
|
|
3997
|
+
for (const input of inputs) {
|
|
3998
|
+
if (input.depends_on_temp_ids && input.depends_on_temp_ids.length > 0) {
|
|
3999
|
+
const taskId = input.temp_id ? tempIdToRealId.get(input.temp_id) : null;
|
|
4000
|
+
if (!taskId)
|
|
4001
|
+
continue;
|
|
4002
|
+
for (const depTempId of input.depends_on_temp_ids) {
|
|
4003
|
+
const depRealId = tempIdToRealId.get(depTempId);
|
|
4004
|
+
if (depRealId) {
|
|
4005
|
+
addDependency(taskId, depRealId, d);
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
});
|
|
4011
|
+
tx();
|
|
4012
|
+
return { created };
|
|
4013
|
+
}
|
|
4014
|
+
function bulkUpdateTasks(taskIds, updates, db) {
|
|
4015
|
+
const d = db || getDatabase();
|
|
4016
|
+
let updated = 0;
|
|
4017
|
+
const failed = [];
|
|
4018
|
+
const tx = d.transaction(() => {
|
|
4019
|
+
for (const id of taskIds) {
|
|
4020
|
+
try {
|
|
4021
|
+
const task = getTask(id, d);
|
|
4022
|
+
if (!task) {
|
|
4023
|
+
failed.push({ id, error: "Task not found" });
|
|
4024
|
+
continue;
|
|
4025
|
+
}
|
|
4026
|
+
updateTask(id, { ...updates, version: task.version }, d);
|
|
4027
|
+
updated++;
|
|
4028
|
+
} catch (e) {
|
|
4029
|
+
failed.push({ id, error: e instanceof Error ? e.message : String(e) });
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
});
|
|
4033
|
+
tx();
|
|
4034
|
+
return { updated, failed };
|
|
4035
|
+
}
|
|
3388
4036
|
var init_tasks = __esm(() => {
|
|
3389
4037
|
init_types();
|
|
3390
4038
|
init_database();
|
|
3391
4039
|
init_projects();
|
|
3392
4040
|
init_completion_guard();
|
|
3393
4041
|
init_audit();
|
|
4042
|
+
init_recurrence();
|
|
4043
|
+
init_webhooks();
|
|
3394
4044
|
});
|
|
3395
4045
|
|
|
3396
4046
|
// src/db/agents.ts
|
|
@@ -3715,19 +4365,64 @@ function rowToTask2(row) {
|
|
|
3715
4365
|
requires_approval: Boolean(row.requires_approval)
|
|
3716
4366
|
};
|
|
3717
4367
|
}
|
|
3718
|
-
function searchTasks(
|
|
4368
|
+
function searchTasks(options, projectId, taskListId, db) {
|
|
4369
|
+
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
3719
4370
|
const d = db || getDatabase();
|
|
3720
4371
|
clearExpiredLocks(d);
|
|
3721
|
-
const pattern = `%${query}%`;
|
|
4372
|
+
const pattern = `%${opts.query}%`;
|
|
3722
4373
|
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
4374
|
const params = [pattern, pattern, pattern];
|
|
3724
|
-
if (
|
|
4375
|
+
if (opts.project_id) {
|
|
3725
4376
|
sql += " AND project_id = ?";
|
|
3726
|
-
params.push(
|
|
4377
|
+
params.push(opts.project_id);
|
|
3727
4378
|
}
|
|
3728
|
-
if (
|
|
4379
|
+
if (opts.task_list_id) {
|
|
3729
4380
|
sql += " AND task_list_id = ?";
|
|
3730
|
-
params.push(
|
|
4381
|
+
params.push(opts.task_list_id);
|
|
4382
|
+
}
|
|
4383
|
+
if (opts.status) {
|
|
4384
|
+
if (Array.isArray(opts.status)) {
|
|
4385
|
+
sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
|
|
4386
|
+
params.push(...opts.status);
|
|
4387
|
+
} else {
|
|
4388
|
+
sql += " AND status = ?";
|
|
4389
|
+
params.push(opts.status);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
if (opts.priority) {
|
|
4393
|
+
if (Array.isArray(opts.priority)) {
|
|
4394
|
+
sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
4395
|
+
params.push(...opts.priority);
|
|
4396
|
+
} else {
|
|
4397
|
+
sql += " AND priority = ?";
|
|
4398
|
+
params.push(opts.priority);
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
if (opts.assigned_to) {
|
|
4402
|
+
sql += " AND assigned_to = ?";
|
|
4403
|
+
params.push(opts.assigned_to);
|
|
4404
|
+
}
|
|
4405
|
+
if (opts.agent_id) {
|
|
4406
|
+
sql += " AND agent_id = ?";
|
|
4407
|
+
params.push(opts.agent_id);
|
|
4408
|
+
}
|
|
4409
|
+
if (opts.created_after) {
|
|
4410
|
+
sql += " AND created_at > ?";
|
|
4411
|
+
params.push(opts.created_after);
|
|
4412
|
+
}
|
|
4413
|
+
if (opts.updated_after) {
|
|
4414
|
+
sql += " AND updated_at > ?";
|
|
4415
|
+
params.push(opts.updated_after);
|
|
4416
|
+
}
|
|
4417
|
+
if (opts.has_dependencies === true) {
|
|
4418
|
+
sql += " AND id IN (SELECT task_id FROM task_dependencies)";
|
|
4419
|
+
} else if (opts.has_dependencies === false) {
|
|
4420
|
+
sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
4421
|
+
}
|
|
4422
|
+
if (opts.is_blocked === true) {
|
|
4423
|
+
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')";
|
|
4424
|
+
} else if (opts.is_blocked === false) {
|
|
4425
|
+
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
4426
|
}
|
|
3732
4427
|
sql += ` ORDER BY
|
|
3733
4428
|
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
@@ -8304,81 +8999,43 @@ var init_zod = __esm(() => {
|
|
|
8304
8999
|
init_external();
|
|
8305
9000
|
});
|
|
8306
9001
|
|
|
8307
|
-
// src/db/webhooks.ts
|
|
8308
|
-
var exports_webhooks = {};
|
|
8309
|
-
__export(exports_webhooks, {
|
|
8310
|
-
listWebhooks: () => listWebhooks,
|
|
8311
|
-
getWebhook: () => getWebhook,
|
|
8312
|
-
dispatchWebhook: () => dispatchWebhook,
|
|
8313
|
-
deleteWebhook: () => deleteWebhook,
|
|
8314
|
-
createWebhook: () => createWebhook
|
|
8315
|
-
});
|
|
8316
|
-
function rowToWebhook(row) {
|
|
8317
|
-
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
8318
|
-
}
|
|
8319
|
-
function createWebhook(input, db) {
|
|
8320
|
-
const d = db || getDatabase();
|
|
8321
|
-
const id = uuid();
|
|
8322
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
8323
|
-
return getWebhook(id, d);
|
|
8324
|
-
}
|
|
8325
|
-
function getWebhook(id, db) {
|
|
8326
|
-
const d = db || getDatabase();
|
|
8327
|
-
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
8328
|
-
return row ? rowToWebhook(row) : null;
|
|
8329
|
-
}
|
|
8330
|
-
function listWebhooks(db) {
|
|
8331
|
-
const d = db || getDatabase();
|
|
8332
|
-
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
8333
|
-
}
|
|
8334
|
-
function deleteWebhook(id, db) {
|
|
8335
|
-
const d = db || getDatabase();
|
|
8336
|
-
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
8337
|
-
}
|
|
8338
|
-
async function dispatchWebhook(event, payload, db) {
|
|
8339
|
-
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
8340
|
-
for (const wh of webhooks) {
|
|
8341
|
-
try {
|
|
8342
|
-
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
8343
|
-
const headers = { "Content-Type": "application/json" };
|
|
8344
|
-
if (wh.secret) {
|
|
8345
|
-
const encoder = new TextEncoder;
|
|
8346
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
8347
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
8348
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8349
|
-
}
|
|
8350
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
8351
|
-
} catch {}
|
|
8352
|
-
}
|
|
8353
|
-
}
|
|
8354
|
-
var init_webhooks = __esm(() => {
|
|
8355
|
-
init_database();
|
|
8356
|
-
});
|
|
8357
|
-
|
|
8358
9002
|
// src/mcp/index.ts
|
|
8359
9003
|
var exports_mcp = {};
|
|
8360
9004
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8361
9005
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8362
9006
|
function formatError(error) {
|
|
8363
|
-
if (error instanceof VersionConflictError)
|
|
8364
|
-
return
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
if (error instanceof
|
|
8370
|
-
return
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
9007
|
+
if (error instanceof VersionConflictError) {
|
|
9008
|
+
return JSON.stringify({ code: VersionConflictError.code, message: error.message, suggestion: VersionConflictError.suggestion });
|
|
9009
|
+
}
|
|
9010
|
+
if (error instanceof TaskNotFoundError) {
|
|
9011
|
+
return JSON.stringify({ code: TaskNotFoundError.code, message: error.message, suggestion: TaskNotFoundError.suggestion });
|
|
9012
|
+
}
|
|
9013
|
+
if (error instanceof ProjectNotFoundError) {
|
|
9014
|
+
return JSON.stringify({ code: ProjectNotFoundError.code, message: error.message, suggestion: ProjectNotFoundError.suggestion });
|
|
9015
|
+
}
|
|
9016
|
+
if (error instanceof PlanNotFoundError) {
|
|
9017
|
+
return JSON.stringify({ code: PlanNotFoundError.code, message: error.message, suggestion: PlanNotFoundError.suggestion });
|
|
9018
|
+
}
|
|
9019
|
+
if (error instanceof TaskListNotFoundError) {
|
|
9020
|
+
return JSON.stringify({ code: TaskListNotFoundError.code, message: error.message, suggestion: TaskListNotFoundError.suggestion });
|
|
9021
|
+
}
|
|
9022
|
+
if (error instanceof LockError) {
|
|
9023
|
+
return JSON.stringify({ code: LockError.code, message: error.message, suggestion: LockError.suggestion });
|
|
9024
|
+
}
|
|
9025
|
+
if (error instanceof AgentNotFoundError) {
|
|
9026
|
+
return JSON.stringify({ code: AgentNotFoundError.code, message: error.message, suggestion: AgentNotFoundError.suggestion });
|
|
9027
|
+
}
|
|
9028
|
+
if (error instanceof DependencyCycleError) {
|
|
9029
|
+
return JSON.stringify({ code: DependencyCycleError.code, message: error.message, suggestion: DependencyCycleError.suggestion });
|
|
9030
|
+
}
|
|
8375
9031
|
if (error instanceof CompletionGuardError) {
|
|
8376
|
-
const retry = error.retryAfterSeconds ?
|
|
8377
|
-
return
|
|
9032
|
+
const retry = error.retryAfterSeconds ? { retryAfterSeconds: error.retryAfterSeconds } : {};
|
|
9033
|
+
return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
|
|
9034
|
+
}
|
|
9035
|
+
if (error instanceof Error) {
|
|
9036
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: error.message });
|
|
8378
9037
|
}
|
|
8379
|
-
|
|
8380
|
-
return error.message;
|
|
8381
|
-
return String(error);
|
|
9038
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
|
|
8382
9039
|
}
|
|
8383
9040
|
function resolveId(partialId, table = "tasks") {
|
|
8384
9041
|
const db = getDatabase();
|
|
@@ -8401,7 +9058,8 @@ function formatTask(task) {
|
|
|
8401
9058
|
const id = task.short_id || task.id.slice(0, 8);
|
|
8402
9059
|
const assigned = task.assigned_to ? ` -> ${task.assigned_to}` : "";
|
|
8403
9060
|
const lock = task.locked_by ? ` [locked:${task.locked_by}]` : "";
|
|
8404
|
-
|
|
9061
|
+
const recur = task.recurrence_rule ? ` [\u21BB]` : "";
|
|
9062
|
+
return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
|
|
8405
9063
|
}
|
|
8406
9064
|
function formatTaskDetail(task) {
|
|
8407
9065
|
const parts = [
|
|
@@ -8426,6 +9084,10 @@ function formatTaskDetail(task) {
|
|
|
8426
9084
|
parts.push(`Plan: ${task.plan_id}`);
|
|
8427
9085
|
if (task.tags.length > 0)
|
|
8428
9086
|
parts.push(`Tags: ${task.tags.join(", ")}`);
|
|
9087
|
+
if (task.recurrence_rule)
|
|
9088
|
+
parts.push(`Recurrence: ${task.recurrence_rule}`);
|
|
9089
|
+
if (task.recurrence_parent_id)
|
|
9090
|
+
parts.push(`Recurrence parent: ${task.recurrence_parent_id}`);
|
|
8429
9091
|
parts.push(`Version: ${task.version}`);
|
|
8430
9092
|
parts.push(`Created: ${task.created_at}`);
|
|
8431
9093
|
if (task.completed_at)
|
|
@@ -8453,7 +9115,7 @@ var init_mcp = __esm(() => {
|
|
|
8453
9115
|
init_types();
|
|
8454
9116
|
server = new McpServer({
|
|
8455
9117
|
name: "todos",
|
|
8456
|
-
version: "0.9.
|
|
9118
|
+
version: "0.9.35"
|
|
8457
9119
|
});
|
|
8458
9120
|
server.tool("create_task", "Create a new task", {
|
|
8459
9121
|
title: exports_external.string(),
|
|
@@ -8471,7 +9133,8 @@ var init_mcp = __esm(() => {
|
|
|
8471
9133
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
8472
9134
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
8473
9135
|
estimated_minutes: exports_external.number().optional(),
|
|
8474
|
-
requires_approval: exports_external.boolean().optional()
|
|
9136
|
+
requires_approval: exports_external.boolean().optional(),
|
|
9137
|
+
recurrence_rule: exports_external.string().optional()
|
|
8475
9138
|
}, async (params) => {
|
|
8476
9139
|
try {
|
|
8477
9140
|
const resolved = { ...params };
|
|
@@ -8489,7 +9152,7 @@ var init_mcp = __esm(() => {
|
|
|
8489
9152
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8490
9153
|
}
|
|
8491
9154
|
});
|
|
8492
|
-
server.tool("list_tasks", "List tasks with optional filters", {
|
|
9155
|
+
server.tool("list_tasks", "List tasks with optional filters and pagination.", {
|
|
8493
9156
|
project_id: exports_external.string().optional(),
|
|
8494
9157
|
status: exports_external.union([
|
|
8495
9158
|
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
@@ -8502,7 +9165,10 @@ var init_mcp = __esm(() => {
|
|
|
8502
9165
|
assigned_to: exports_external.string().optional(),
|
|
8503
9166
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
8504
9167
|
plan_id: exports_external.string().optional(),
|
|
8505
|
-
task_list_id: exports_external.string().optional()
|
|
9168
|
+
task_list_id: exports_external.string().optional(),
|
|
9169
|
+
has_recurrence: exports_external.boolean().optional(),
|
|
9170
|
+
limit: exports_external.number().optional(),
|
|
9171
|
+
offset: exports_external.number().optional()
|
|
8506
9172
|
}, async (params) => {
|
|
8507
9173
|
try {
|
|
8508
9174
|
const resolved = { ...params };
|
|
@@ -8513,8 +9179,10 @@ var init_mcp = __esm(() => {
|
|
|
8513
9179
|
if (resolved.task_list_id)
|
|
8514
9180
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
8515
9181
|
const tasks = listTasks(resolved);
|
|
9182
|
+
const { limit: _limit, offset: _offset, ...countFilter } = resolved;
|
|
9183
|
+
const total = countTasks(countFilter);
|
|
8516
9184
|
if (tasks.length === 0) {
|
|
8517
|
-
return { content: [{ type: "text", text: "No tasks found." }] };
|
|
9185
|
+
return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
|
|
8518
9186
|
}
|
|
8519
9187
|
const text = tasks.map((t) => {
|
|
8520
9188
|
const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
|
|
@@ -8522,13 +9190,15 @@ var init_mcp = __esm(() => {
|
|
|
8522
9190
|
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}`;
|
|
8523
9191
|
}).join(`
|
|
8524
9192
|
`);
|
|
9193
|
+
const pagination = resolved.limit ? `
|
|
9194
|
+
(showing ${tasks.length} of ${total}, offset: ${resolved.offset || 0})` : "";
|
|
8525
9195
|
return { content: [{ type: "text", text: `${tasks.length} task(s):
|
|
8526
|
-
${text}` }] };
|
|
9196
|
+
${text}${pagination}` }] };
|
|
8527
9197
|
} catch (e) {
|
|
8528
9198
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8529
9199
|
}
|
|
8530
9200
|
});
|
|
8531
|
-
server.tool("get_task", "Get full task details with
|
|
9201
|
+
server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
|
|
8532
9202
|
id: exports_external.string()
|
|
8533
9203
|
}, async ({ id }) => {
|
|
8534
9204
|
try {
|
|
@@ -8597,7 +9267,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8597
9267
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8598
9268
|
}
|
|
8599
9269
|
});
|
|
8600
|
-
server.tool("delete_task", "Delete a task permanently", {
|
|
9270
|
+
server.tool("delete_task", "Delete a task permanently. Subtasks cascade-deleted.", {
|
|
8601
9271
|
id: exports_external.string()
|
|
8602
9272
|
}, async ({ id }) => {
|
|
8603
9273
|
try {
|
|
@@ -8613,7 +9283,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8613
9283
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8614
9284
|
}
|
|
8615
9285
|
});
|
|
8616
|
-
server.tool("start_task", "Claim, lock, and set task
|
|
9286
|
+
server.tool("start_task", "Claim, lock, and set task to in_progress.", {
|
|
8617
9287
|
id: exports_external.string(),
|
|
8618
9288
|
agent_id: exports_external.string()
|
|
8619
9289
|
}, async ({ id, agent_id }) => {
|
|
@@ -8625,19 +9295,26 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8625
9295
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8626
9296
|
}
|
|
8627
9297
|
});
|
|
8628
|
-
server.tool("complete_task", "
|
|
9298
|
+
server.tool("complete_task", "Complete a task. For recurring tasks, auto-spawns next instance.", {
|
|
8629
9299
|
id: exports_external.string(),
|
|
8630
|
-
agent_id: exports_external.string().optional()
|
|
8631
|
-
|
|
9300
|
+
agent_id: exports_external.string().optional(),
|
|
9301
|
+
skip_recurrence: exports_external.boolean().optional()
|
|
9302
|
+
}, async ({ id, agent_id, skip_recurrence }) => {
|
|
8632
9303
|
try {
|
|
8633
9304
|
const resolvedId = resolveId(id);
|
|
8634
|
-
const task = completeTask(resolvedId, agent_id);
|
|
8635
|
-
|
|
9305
|
+
const task = completeTask(resolvedId, agent_id, undefined, { skip_recurrence });
|
|
9306
|
+
let text = `completed: ${formatTask(task)}`;
|
|
9307
|
+
if (task.metadata._next_recurrence) {
|
|
9308
|
+
const next = task.metadata._next_recurrence;
|
|
9309
|
+
text += `
|
|
9310
|
+
next: ${next.short_id || next.id.slice(0, 8)} due ${next.due_at}`;
|
|
9311
|
+
}
|
|
9312
|
+
return { content: [{ type: "text", text }] };
|
|
8636
9313
|
} catch (e) {
|
|
8637
9314
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8638
9315
|
}
|
|
8639
9316
|
});
|
|
8640
|
-
server.tool("lock_task", "Acquire exclusive lock
|
|
9317
|
+
server.tool("lock_task", "Acquire exclusive lock. Expires after 30 min. Idempotent per agent.", {
|
|
8641
9318
|
id: exports_external.string(),
|
|
8642
9319
|
agent_id: exports_external.string()
|
|
8643
9320
|
}, async ({ id, agent_id }) => {
|
|
@@ -8652,7 +9329,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8652
9329
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8653
9330
|
}
|
|
8654
9331
|
});
|
|
8655
|
-
server.tool("unlock_task", "Release exclusive lock on a task", {
|
|
9332
|
+
server.tool("unlock_task", "Release exclusive lock on a task.", {
|
|
8656
9333
|
id: exports_external.string(),
|
|
8657
9334
|
agent_id: exports_external.string().optional()
|
|
8658
9335
|
}, async ({ id, agent_id }) => {
|
|
@@ -8664,7 +9341,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8664
9341
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8665
9342
|
}
|
|
8666
9343
|
});
|
|
8667
|
-
server.tool("add_dependency", "Add a dependency
|
|
9344
|
+
server.tool("add_dependency", "Add a dependency. Prevents cycles via BFS detection.", {
|
|
8668
9345
|
task_id: exports_external.string(),
|
|
8669
9346
|
depends_on: exports_external.string()
|
|
8670
9347
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -8677,7 +9354,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8677
9354
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8678
9355
|
}
|
|
8679
9356
|
});
|
|
8680
|
-
server.tool("remove_dependency", "Remove a dependency between tasks", {
|
|
9357
|
+
server.tool("remove_dependency", "Remove a dependency link between two tasks.", {
|
|
8681
9358
|
task_id: exports_external.string(),
|
|
8682
9359
|
depends_on: exports_external.string()
|
|
8683
9360
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -8695,7 +9372,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
|
8695
9372
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8696
9373
|
}
|
|
8697
9374
|
});
|
|
8698
|
-
server.tool("add_comment", "Add a comment
|
|
9375
|
+
server.tool("add_comment", "Add a comment or note to a task. Comments are append-only.", {
|
|
8699
9376
|
task_id: exports_external.string(),
|
|
8700
9377
|
content: exports_external.string(),
|
|
8701
9378
|
agent_id: exports_external.string().optional(),
|
|
@@ -8726,7 +9403,7 @@ ${text}` }] };
|
|
|
8726
9403
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8727
9404
|
}
|
|
8728
9405
|
});
|
|
8729
|
-
server.tool("create_project", "Register a new project", {
|
|
9406
|
+
server.tool("create_project", "Register a new project with auto-generated task prefix.", {
|
|
8730
9407
|
name: exports_external.string(),
|
|
8731
9408
|
path: exports_external.string(),
|
|
8732
9409
|
description: exports_external.string().optional(),
|
|
@@ -8745,7 +9422,7 @@ ${text}` }] };
|
|
|
8745
9422
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8746
9423
|
}
|
|
8747
9424
|
});
|
|
8748
|
-
server.tool("create_plan", "Create a
|
|
9425
|
+
server.tool("create_plan", "Create a plan to group related tasks.", {
|
|
8749
9426
|
name: exports_external.string(),
|
|
8750
9427
|
project_id: exports_external.string().optional(),
|
|
8751
9428
|
description: exports_external.string().optional(),
|
|
@@ -8770,7 +9447,7 @@ ${text}` }] };
|
|
|
8770
9447
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8771
9448
|
}
|
|
8772
9449
|
});
|
|
8773
|
-
server.tool("list_plans", "List plans
|
|
9450
|
+
server.tool("list_plans", "List all plans, optionally filtered by project.", {
|
|
8774
9451
|
project_id: exports_external.string().optional()
|
|
8775
9452
|
}, async ({ project_id }) => {
|
|
8776
9453
|
try {
|
|
@@ -8790,7 +9467,7 @@ ${text}` }] };
|
|
|
8790
9467
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8791
9468
|
}
|
|
8792
9469
|
});
|
|
8793
|
-
server.tool("get_plan", "Get plan details", {
|
|
9470
|
+
server.tool("get_plan", "Get plan details including status and timestamps.", {
|
|
8794
9471
|
id: exports_external.string()
|
|
8795
9472
|
}, async ({ id }) => {
|
|
8796
9473
|
try {
|
|
@@ -8815,7 +9492,7 @@ ${text}` }] };
|
|
|
8815
9492
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8816
9493
|
}
|
|
8817
9494
|
});
|
|
8818
|
-
server.tool("update_plan", "Update
|
|
9495
|
+
server.tool("update_plan", "Update plan fields (name, description, status).", {
|
|
8819
9496
|
id: exports_external.string(),
|
|
8820
9497
|
name: exports_external.string().optional(),
|
|
8821
9498
|
description: exports_external.string().optional(),
|
|
@@ -8839,7 +9516,7 @@ ${text}` }] };
|
|
|
8839
9516
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8840
9517
|
}
|
|
8841
9518
|
});
|
|
8842
|
-
server.tool("delete_plan", "Delete a plan", {
|
|
9519
|
+
server.tool("delete_plan", "Delete a plan. Tasks in the plan are orphaned (not deleted).", {
|
|
8843
9520
|
id: exports_external.string()
|
|
8844
9521
|
}, async ({ id }) => {
|
|
8845
9522
|
try {
|
|
@@ -8855,15 +9532,34 @@ ${text}` }] };
|
|
|
8855
9532
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8856
9533
|
}
|
|
8857
9534
|
});
|
|
8858
|
-
server.tool("search_tasks", "Full-text search across
|
|
9535
|
+
server.tool("search_tasks", "Full-text search across tasks with filters.", {
|
|
8859
9536
|
query: exports_external.string(),
|
|
8860
9537
|
project_id: exports_external.string().optional(),
|
|
8861
|
-
task_list_id: exports_external.string().optional()
|
|
8862
|
-
|
|
9538
|
+
task_list_id: exports_external.string().optional(),
|
|
9539
|
+
status: exports_external.union([
|
|
9540
|
+
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
9541
|
+
exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))
|
|
9542
|
+
]).optional(),
|
|
9543
|
+
priority: exports_external.union([
|
|
9544
|
+
exports_external.enum(["low", "medium", "high", "critical"]),
|
|
9545
|
+
exports_external.array(exports_external.enum(["low", "medium", "high", "critical"]))
|
|
9546
|
+
]).optional(),
|
|
9547
|
+
assigned_to: exports_external.string().optional(),
|
|
9548
|
+
agent_id: exports_external.string().optional(),
|
|
9549
|
+
created_after: exports_external.string().optional(),
|
|
9550
|
+
updated_after: exports_external.string().optional(),
|
|
9551
|
+
has_dependencies: exports_external.boolean().optional(),
|
|
9552
|
+
is_blocked: exports_external.boolean().optional()
|
|
9553
|
+
}, async ({ query, project_id, task_list_id, ...filters }) => {
|
|
8863
9554
|
try {
|
|
8864
9555
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
8865
9556
|
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
8866
|
-
const tasks = searchTasks(
|
|
9557
|
+
const tasks = searchTasks({
|
|
9558
|
+
query,
|
|
9559
|
+
project_id: resolvedProjectId,
|
|
9560
|
+
task_list_id: resolvedTaskListId,
|
|
9561
|
+
...filters
|
|
9562
|
+
});
|
|
8867
9563
|
if (tasks.length === 0) {
|
|
8868
9564
|
return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
|
|
8869
9565
|
}
|
|
@@ -8875,7 +9571,7 @@ ${text}` }] };
|
|
|
8875
9571
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8876
9572
|
}
|
|
8877
9573
|
});
|
|
8878
|
-
server.tool("sync", "Sync tasks
|
|
9574
|
+
server.tool("sync", "Sync tasks between local DB and agent task list.", {
|
|
8879
9575
|
task_list_id: exports_external.string().optional(),
|
|
8880
9576
|
agent: exports_external.string().optional(),
|
|
8881
9577
|
all_agents: exports_external.boolean().optional(),
|
|
@@ -8923,7 +9619,7 @@ ${text}` }] };
|
|
|
8923
9619
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8924
9620
|
}
|
|
8925
9621
|
});
|
|
8926
|
-
server.tool("register_agent", "Register an agent (idempotent by name).", {
|
|
9622
|
+
server.tool("register_agent", "Register an agent (idempotent by name). Updates last_seen_at.", {
|
|
8927
9623
|
name: exports_external.string(),
|
|
8928
9624
|
description: exports_external.string().optional()
|
|
8929
9625
|
}, async ({ name, description }) => {
|
|
@@ -8960,7 +9656,7 @@ ${text}` }] };
|
|
|
8960
9656
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8961
9657
|
}
|
|
8962
9658
|
});
|
|
8963
|
-
server.tool("get_agent", "Get agent details by ID or name", {
|
|
9659
|
+
server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
|
|
8964
9660
|
id: exports_external.string().optional(),
|
|
8965
9661
|
name: exports_external.string().optional()
|
|
8966
9662
|
}, async ({ id, name }) => {
|
|
@@ -8989,9 +9685,9 @@ ${text}` }] };
|
|
|
8989
9685
|
}
|
|
8990
9686
|
});
|
|
8991
9687
|
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()
|
|
9688
|
+
id: exports_external.string().optional(),
|
|
9689
|
+
name: exports_external.string().optional(),
|
|
9690
|
+
new_name: exports_external.string()
|
|
8995
9691
|
}, async ({ id, name, new_name }) => {
|
|
8996
9692
|
try {
|
|
8997
9693
|
if (!id && !name) {
|
|
@@ -9014,8 +9710,8 @@ ID: ${updated.id}`
|
|
|
9014
9710
|
}
|
|
9015
9711
|
});
|
|
9016
9712
|
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()
|
|
9713
|
+
id: exports_external.string().optional(),
|
|
9714
|
+
name: exports_external.string().optional()
|
|
9019
9715
|
}, async ({ id, name }) => {
|
|
9020
9716
|
try {
|
|
9021
9717
|
if (!id && !name) {
|
|
@@ -9037,7 +9733,7 @@ ID: ${updated.id}`
|
|
|
9037
9733
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9038
9734
|
}
|
|
9039
9735
|
});
|
|
9040
|
-
server.tool("create_task_list", "Create a
|
|
9736
|
+
server.tool("create_task_list", "Create a task list container for organizing tasks.", {
|
|
9041
9737
|
name: exports_external.string(),
|
|
9042
9738
|
slug: exports_external.string().optional(),
|
|
9043
9739
|
project_id: exports_external.string().optional(),
|
|
@@ -9063,7 +9759,7 @@ Description: ${list.description}` : ""}`
|
|
|
9063
9759
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9064
9760
|
}
|
|
9065
9761
|
});
|
|
9066
|
-
server.tool("list_task_lists", "List task lists, optionally filtered by project", {
|
|
9762
|
+
server.tool("list_task_lists", "List all task lists, optionally filtered by project.", {
|
|
9067
9763
|
project_id: exports_external.string().optional()
|
|
9068
9764
|
}, async ({ project_id }) => {
|
|
9069
9765
|
try {
|
|
@@ -9083,7 +9779,7 @@ ${text}` }] };
|
|
|
9083
9779
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9084
9780
|
}
|
|
9085
9781
|
});
|
|
9086
|
-
server.tool("get_task_list", "Get task list details", {
|
|
9782
|
+
server.tool("get_task_list", "Get task list details including slug and metadata.", {
|
|
9087
9783
|
id: exports_external.string()
|
|
9088
9784
|
}, async ({ id }) => {
|
|
9089
9785
|
try {
|
|
@@ -9111,7 +9807,7 @@ ${text}` }] };
|
|
|
9111
9807
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9112
9808
|
}
|
|
9113
9809
|
});
|
|
9114
|
-
server.tool("update_task_list", "Update a task list", {
|
|
9810
|
+
server.tool("update_task_list", "Update a task list's name or description.", {
|
|
9115
9811
|
id: exports_external.string(),
|
|
9116
9812
|
name: exports_external.string().optional(),
|
|
9117
9813
|
description: exports_external.string().optional()
|
|
@@ -9132,7 +9828,7 @@ Slug: ${list.slug}`
|
|
|
9132
9828
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9133
9829
|
}
|
|
9134
9830
|
});
|
|
9135
|
-
server.tool("delete_task_list", "Delete a task list. Tasks
|
|
9831
|
+
server.tool("delete_task_list", "Delete a task list. Tasks are orphaned, not deleted.", {
|
|
9136
9832
|
id: exports_external.string()
|
|
9137
9833
|
}, async ({ id }) => {
|
|
9138
9834
|
try {
|
|
@@ -9148,7 +9844,7 @@ Slug: ${list.slug}`
|
|
|
9148
9844
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9149
9845
|
}
|
|
9150
9846
|
});
|
|
9151
|
-
server.tool("get_task_history", "Get audit log
|
|
9847
|
+
server.tool("get_task_history", "Get audit log \u2014 field changes with timestamps and actors.", {
|
|
9152
9848
|
task_id: exports_external.string()
|
|
9153
9849
|
}, async ({ task_id }) => {
|
|
9154
9850
|
try {
|
|
@@ -9165,7 +9861,7 @@ ${text}` }] };
|
|
|
9165
9861
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9166
9862
|
}
|
|
9167
9863
|
});
|
|
9168
|
-
server.tool("get_recent_activity", "Get recent task changes
|
|
9864
|
+
server.tool("get_recent_activity", "Get recent task changes \u2014 global activity feed.", {
|
|
9169
9865
|
limit: exports_external.number().optional()
|
|
9170
9866
|
}, async ({ limit }) => {
|
|
9171
9867
|
try {
|
|
@@ -9181,7 +9877,7 @@ ${text}` }] };
|
|
|
9181
9877
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9182
9878
|
}
|
|
9183
9879
|
});
|
|
9184
|
-
server.tool("create_webhook", "Register a webhook
|
|
9880
|
+
server.tool("create_webhook", "Register a webhook for task change events.", {
|
|
9185
9881
|
url: exports_external.string(),
|
|
9186
9882
|
events: exports_external.array(exports_external.string()).optional(),
|
|
9187
9883
|
secret: exports_external.string().optional()
|
|
@@ -9208,7 +9904,7 @@ ${text}` }] };
|
|
|
9208
9904
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9209
9905
|
}
|
|
9210
9906
|
});
|
|
9211
|
-
server.tool("delete_webhook", "Delete a webhook", {
|
|
9907
|
+
server.tool("delete_webhook", "Delete a webhook by ID.", {
|
|
9212
9908
|
id: exports_external.string()
|
|
9213
9909
|
}, async ({ id }) => {
|
|
9214
9910
|
try {
|
|
@@ -9274,7 +9970,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
9274
9970
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9275
9971
|
}
|
|
9276
9972
|
});
|
|
9277
|
-
server.tool("delete_template", "Delete a task template", { id: exports_external.string() }, async ({ id }) => {
|
|
9973
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
9278
9974
|
try {
|
|
9279
9975
|
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
9280
9976
|
const deleted = deleteTemplate2(id);
|
|
@@ -9283,7 +9979,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
9283
9979
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9284
9980
|
}
|
|
9285
9981
|
});
|
|
9286
|
-
server.tool("approve_task", "Approve a task
|
|
9982
|
+
server.tool("approve_task", "Approve a task with requires_approval=true.", {
|
|
9287
9983
|
id: exports_external.string(),
|
|
9288
9984
|
agent_id: exports_external.string().optional()
|
|
9289
9985
|
}, async ({ id, agent_id }) => {
|
|
@@ -9302,7 +9998,31 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
9302
9998
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9303
9999
|
}
|
|
9304
10000
|
});
|
|
9305
|
-
server.tool("
|
|
10001
|
+
server.tool("fail_task", "Mark a task as failed with structured reason and optional auto-retry.", {
|
|
10002
|
+
id: exports_external.string(),
|
|
10003
|
+
agent_id: exports_external.string().optional(),
|
|
10004
|
+
reason: exports_external.string().optional(),
|
|
10005
|
+
error_code: exports_external.string().optional(),
|
|
10006
|
+
retry: exports_external.boolean().optional(),
|
|
10007
|
+
retry_after: exports_external.string().optional()
|
|
10008
|
+
}, async ({ id, agent_id, reason, error_code, retry, retry_after }) => {
|
|
10009
|
+
try {
|
|
10010
|
+
const resolvedId = resolveId(id);
|
|
10011
|
+
const result = failTask(resolvedId, agent_id, reason, { retry, retry_after, error_code });
|
|
10012
|
+
let text = `failed: ${formatTask(result.task)}`;
|
|
10013
|
+
if (reason)
|
|
10014
|
+
text += `
|
|
10015
|
+
Reason: ${reason}`;
|
|
10016
|
+
if (result.retryTask) {
|
|
10017
|
+
text += `
|
|
10018
|
+
Retry task created: ${formatTask(result.retryTask)}`;
|
|
10019
|
+
}
|
|
10020
|
+
return { content: [{ type: "text", text }] };
|
|
10021
|
+
} catch (e) {
|
|
10022
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10023
|
+
}
|
|
10024
|
+
});
|
|
10025
|
+
server.tool("get_my_tasks", "Get tasks assigned to/created by an agent with stats.", {
|
|
9306
10026
|
agent_name: exports_external.string()
|
|
9307
10027
|
}, async ({ agent_name }) => {
|
|
9308
10028
|
try {
|
|
@@ -9335,7 +10055,7 @@ In Progress:`);
|
|
|
9335
10055
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9336
10056
|
}
|
|
9337
10057
|
});
|
|
9338
|
-
server.tool("get_org_chart", "Get agent org chart
|
|
10058
|
+
server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
|
|
9339
10059
|
try {
|
|
9340
10060
|
let render = function(nodes, indent = 0) {
|
|
9341
10061
|
return nodes.map((n) => {
|
|
@@ -9357,7 +10077,7 @@ In Progress:`);
|
|
|
9357
10077
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9358
10078
|
}
|
|
9359
10079
|
});
|
|
9360
|
-
server.tool("set_reports_to", "Set
|
|
10080
|
+
server.tool("set_reports_to", "Set agent reporting relationship in org chart.", {
|
|
9361
10081
|
agent_name: exports_external.string(),
|
|
9362
10082
|
manager_name: exports_external.string().optional()
|
|
9363
10083
|
}, async ({ agent_name, manager_name }) => {
|
|
@@ -9380,7 +10100,367 @@ In Progress:`);
|
|
|
9380
10100
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9381
10101
|
}
|
|
9382
10102
|
});
|
|
9383
|
-
server.tool("
|
|
10103
|
+
server.tool("bulk_update_tasks", "Update multiple tasks at once with the same changes.", {
|
|
10104
|
+
task_ids: exports_external.array(exports_external.string()),
|
|
10105
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
10106
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
10107
|
+
assigned_to: exports_external.string().optional(),
|
|
10108
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
10109
|
+
}, async ({ task_ids, ...updates }) => {
|
|
10110
|
+
try {
|
|
10111
|
+
const resolvedIds = task_ids.map((id) => resolveId(id));
|
|
10112
|
+
const result = bulkUpdateTasks(resolvedIds, updates);
|
|
10113
|
+
const parts = [`Updated ${result.updated} task(s).`];
|
|
10114
|
+
if (result.failed.length > 0) {
|
|
10115
|
+
parts.push(`Failed ${result.failed.length}:`);
|
|
10116
|
+
for (const f of result.failed)
|
|
10117
|
+
parts.push(` ${f.id.slice(0, 8)}: ${f.error}`);
|
|
10118
|
+
}
|
|
10119
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
10120
|
+
`) }] };
|
|
10121
|
+
} catch (e) {
|
|
10122
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10123
|
+
}
|
|
10124
|
+
});
|
|
10125
|
+
server.tool("clone_task", "Duplicate a task with optional field overrides.", {
|
|
10126
|
+
task_id: exports_external.string(),
|
|
10127
|
+
title: exports_external.string().optional(),
|
|
10128
|
+
description: exports_external.string().optional(),
|
|
10129
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
10130
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
10131
|
+
project_id: exports_external.string().optional(),
|
|
10132
|
+
plan_id: exports_external.string().optional(),
|
|
10133
|
+
task_list_id: exports_external.string().optional(),
|
|
10134
|
+
assigned_to: exports_external.string().optional(),
|
|
10135
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
10136
|
+
estimated_minutes: exports_external.number().optional()
|
|
10137
|
+
}, async ({ task_id, ...overrides }) => {
|
|
10138
|
+
try {
|
|
10139
|
+
const resolvedId = resolveId(task_id);
|
|
10140
|
+
const resolved = { ...overrides };
|
|
10141
|
+
if (resolved.project_id)
|
|
10142
|
+
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
10143
|
+
if (resolved.plan_id)
|
|
10144
|
+
resolved.plan_id = resolveId(resolved.plan_id, "plans");
|
|
10145
|
+
if (resolved.task_list_id)
|
|
10146
|
+
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
10147
|
+
const task = cloneTask(resolvedId, resolved);
|
|
10148
|
+
return { content: [{ type: "text", text: `cloned: ${formatTask(task)}` }] };
|
|
10149
|
+
} catch (e) {
|
|
10150
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10151
|
+
}
|
|
10152
|
+
});
|
|
10153
|
+
server.tool("get_task_stats", "Get task analytics: counts by status, priority, agent.", {
|
|
10154
|
+
project_id: exports_external.string().optional(),
|
|
10155
|
+
task_list_id: exports_external.string().optional(),
|
|
10156
|
+
agent_id: exports_external.string().optional()
|
|
10157
|
+
}, async ({ project_id, task_list_id, agent_id }) => {
|
|
10158
|
+
try {
|
|
10159
|
+
const filters = {};
|
|
10160
|
+
if (project_id)
|
|
10161
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10162
|
+
if (task_list_id)
|
|
10163
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10164
|
+
if (agent_id)
|
|
10165
|
+
filters.agent_id = agent_id;
|
|
10166
|
+
const stats = getTaskStats(Object.keys(filters).length > 0 ? filters : undefined);
|
|
10167
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
10168
|
+
} catch (e) {
|
|
10169
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10170
|
+
}
|
|
10171
|
+
});
|
|
10172
|
+
server.tool("get_task_graph", "Get full dependency tree for a task.", {
|
|
10173
|
+
id: exports_external.string(),
|
|
10174
|
+
direction: exports_external.enum(["up", "down", "both"]).optional()
|
|
10175
|
+
}, async ({ id, direction }) => {
|
|
10176
|
+
try {
|
|
10177
|
+
let formatNode = function(node, indent) {
|
|
10178
|
+
const prefix = " ".repeat(indent);
|
|
10179
|
+
const idLabel = node.task.short_id || node.task.id.slice(0, 8);
|
|
10180
|
+
const blocked = node.task.is_blocked ? " (blocked: yes)" : "";
|
|
10181
|
+
let out = `${prefix}[${node.task.status}] ${idLabel} | ${node.task.title}${blocked}
|
|
10182
|
+
`;
|
|
10183
|
+
if (node.depends_on.length > 0) {
|
|
10184
|
+
out += `${prefix} Depends on:
|
|
10185
|
+
`;
|
|
10186
|
+
for (const dep of node.depends_on) {
|
|
10187
|
+
out += formatNode(dep, indent + 2);
|
|
10188
|
+
}
|
|
10189
|
+
}
|
|
10190
|
+
if (node.blocks.length > 0) {
|
|
10191
|
+
out += `${prefix} Blocks:
|
|
10192
|
+
`;
|
|
10193
|
+
for (const dep of node.blocks) {
|
|
10194
|
+
out += formatNode(dep, indent + 2);
|
|
10195
|
+
}
|
|
10196
|
+
}
|
|
10197
|
+
return out;
|
|
10198
|
+
};
|
|
10199
|
+
const taskId = resolveId(id, "tasks");
|
|
10200
|
+
const graph = getTaskGraph(taskId, direction || "both");
|
|
10201
|
+
let text = `Task: ${formatNode(graph, 0)}`;
|
|
10202
|
+
if (graph.depends_on.length > 0) {
|
|
10203
|
+
text += `
|
|
10204
|
+
Depends on:
|
|
10205
|
+
`;
|
|
10206
|
+
for (const dep of graph.depends_on) {
|
|
10207
|
+
text += formatNode(dep, 1);
|
|
10208
|
+
}
|
|
10209
|
+
}
|
|
10210
|
+
if (graph.blocks.length > 0) {
|
|
10211
|
+
text += `
|
|
10212
|
+
Blocks:
|
|
10213
|
+
`;
|
|
10214
|
+
for (const dep of graph.blocks) {
|
|
10215
|
+
text += formatNode(dep, 1);
|
|
10216
|
+
}
|
|
10217
|
+
}
|
|
10218
|
+
return { content: [{ type: "text", text }] };
|
|
10219
|
+
} catch (e) {
|
|
10220
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10221
|
+
}
|
|
10222
|
+
});
|
|
10223
|
+
server.tool("bulk_create_tasks", "Create multiple tasks atomically with dependency support.", {
|
|
10224
|
+
tasks: exports_external.array(exports_external.object({
|
|
10225
|
+
temp_id: exports_external.string().optional(),
|
|
10226
|
+
title: exports_external.string(),
|
|
10227
|
+
description: exports_external.string().optional(),
|
|
10228
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
10229
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
10230
|
+
project_id: exports_external.string().optional(),
|
|
10231
|
+
plan_id: exports_external.string().optional(),
|
|
10232
|
+
task_list_id: exports_external.string().optional(),
|
|
10233
|
+
agent_id: exports_external.string().optional(),
|
|
10234
|
+
assigned_to: exports_external.string().optional(),
|
|
10235
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
10236
|
+
estimated_minutes: exports_external.number().optional(),
|
|
10237
|
+
depends_on_temp_ids: exports_external.array(exports_external.string()).optional()
|
|
10238
|
+
})),
|
|
10239
|
+
project_id: exports_external.string().optional(),
|
|
10240
|
+
plan_id: exports_external.string().optional(),
|
|
10241
|
+
task_list_id: exports_external.string().optional()
|
|
10242
|
+
}, async ({ tasks, project_id, plan_id, task_list_id }) => {
|
|
10243
|
+
try {
|
|
10244
|
+
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
10245
|
+
const resolvedPlanId = plan_id ? resolveId(plan_id, "plans") : undefined;
|
|
10246
|
+
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
10247
|
+
const enrichedTasks = tasks.map((t) => ({
|
|
10248
|
+
...t,
|
|
10249
|
+
project_id: t.project_id || resolvedProjectId,
|
|
10250
|
+
plan_id: t.plan_id || resolvedPlanId,
|
|
10251
|
+
task_list_id: t.task_list_id || resolvedTaskListId
|
|
10252
|
+
}));
|
|
10253
|
+
const result = bulkCreateTasks(enrichedTasks);
|
|
10254
|
+
const lines = result.created.map((t) => {
|
|
10255
|
+
const tid = t.temp_id ? `[${t.temp_id}] ` : "";
|
|
10256
|
+
const sid = t.short_id || t.id.slice(0, 8);
|
|
10257
|
+
return ` ${tid}${sid} | ${t.title}`;
|
|
10258
|
+
});
|
|
10259
|
+
return { content: [{ type: "text", text: `Created ${result.created.length} task(s):
|
|
10260
|
+
${lines.join(`
|
|
10261
|
+
`)}` }] };
|
|
10262
|
+
} catch (e) {
|
|
10263
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10264
|
+
}
|
|
10265
|
+
});
|
|
10266
|
+
server.tool("move_task", "Move a task to a different list, project, or plan.", {
|
|
10267
|
+
task_id: exports_external.string(),
|
|
10268
|
+
task_list_id: exports_external.string().nullable().optional(),
|
|
10269
|
+
project_id: exports_external.string().nullable().optional(),
|
|
10270
|
+
plan_id: exports_external.string().nullable().optional()
|
|
10271
|
+
}, async ({ task_id, ...target }) => {
|
|
10272
|
+
try {
|
|
10273
|
+
const resolvedId = resolveId(task_id);
|
|
10274
|
+
const resolvedTarget = {};
|
|
10275
|
+
if (target.task_list_id !== undefined)
|
|
10276
|
+
resolvedTarget.task_list_id = target.task_list_id ? resolveId(target.task_list_id, "task_lists") : null;
|
|
10277
|
+
if (target.project_id !== undefined)
|
|
10278
|
+
resolvedTarget.project_id = target.project_id ? resolveId(target.project_id, "projects") : null;
|
|
10279
|
+
if (target.plan_id !== undefined)
|
|
10280
|
+
resolvedTarget.plan_id = target.plan_id ? resolveId(target.plan_id, "plans") : null;
|
|
10281
|
+
const task = moveTask(resolvedId, resolvedTarget);
|
|
10282
|
+
return { content: [{ type: "text", text: `moved: ${formatTask(task)}` }] };
|
|
10283
|
+
} catch (e) {
|
|
10284
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10285
|
+
}
|
|
10286
|
+
});
|
|
10287
|
+
server.tool("get_next_task", "Get the best pending task to work on next.", {
|
|
10288
|
+
agent_id: exports_external.string().optional(),
|
|
10289
|
+
project_id: exports_external.string().optional(),
|
|
10290
|
+
task_list_id: exports_external.string().optional(),
|
|
10291
|
+
plan_id: exports_external.string().optional(),
|
|
10292
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
10293
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
10294
|
+
try {
|
|
10295
|
+
const filters = {};
|
|
10296
|
+
if (project_id)
|
|
10297
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10298
|
+
if (task_list_id)
|
|
10299
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10300
|
+
if (plan_id)
|
|
10301
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
10302
|
+
if (tags)
|
|
10303
|
+
filters.tags = tags;
|
|
10304
|
+
const task = getNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10305
|
+
if (!task) {
|
|
10306
|
+
return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
|
|
10307
|
+
}
|
|
10308
|
+
return { content: [{ type: "text", text: `next: ${formatTask(task)}
|
|
10309
|
+
${formatTaskDetail(task)}` }] };
|
|
10310
|
+
} catch (e) {
|
|
10311
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10312
|
+
}
|
|
10313
|
+
});
|
|
10314
|
+
server.tool("get_active_work", "See all in-progress tasks and who is working on them.", {
|
|
10315
|
+
project_id: exports_external.string().optional(),
|
|
10316
|
+
task_list_id: exports_external.string().optional()
|
|
10317
|
+
}, async ({ project_id, task_list_id }) => {
|
|
10318
|
+
try {
|
|
10319
|
+
const filters = {};
|
|
10320
|
+
if (project_id)
|
|
10321
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10322
|
+
if (task_list_id)
|
|
10323
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10324
|
+
const work = getActiveWork(Object.keys(filters).length > 0 ? filters : undefined);
|
|
10325
|
+
if (work.length === 0) {
|
|
10326
|
+
return { content: [{ type: "text", text: "No active work \u2014 no tasks are currently in progress." }] };
|
|
10327
|
+
}
|
|
10328
|
+
const text = work.map((w) => {
|
|
10329
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
10330
|
+
const agent = w.assigned_to || w.locked_by || "unassigned";
|
|
10331
|
+
const since = w.updated_at;
|
|
10332
|
+
return `${agent.padEnd(12)} | ${w.priority.padEnd(8)} | ${id} | ${w.title} (since ${since})`;
|
|
10333
|
+
}).join(`
|
|
10334
|
+
`);
|
|
10335
|
+
return { content: [{ type: "text", text: `${work.length} active task(s):
|
|
10336
|
+
${text}` }] };
|
|
10337
|
+
} catch (e) {
|
|
10338
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10339
|
+
}
|
|
10340
|
+
});
|
|
10341
|
+
server.tool("get_tasks_changed_since", "Get tasks modified after a timestamp for incremental sync.", {
|
|
10342
|
+
since: exports_external.string(),
|
|
10343
|
+
project_id: exports_external.string().optional(),
|
|
10344
|
+
task_list_id: exports_external.string().optional()
|
|
10345
|
+
}, async ({ since, project_id, task_list_id }) => {
|
|
10346
|
+
try {
|
|
10347
|
+
const filters = {};
|
|
10348
|
+
if (project_id)
|
|
10349
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10350
|
+
if (task_list_id)
|
|
10351
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10352
|
+
const tasks = getTasksChangedSince(since, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10353
|
+
if (tasks.length === 0) {
|
|
10354
|
+
return { content: [{ type: "text", text: `No tasks changed since ${since}.` }] };
|
|
10355
|
+
}
|
|
10356
|
+
const text = tasks.map((t) => {
|
|
10357
|
+
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
10358
|
+
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned} (updated: ${t.updated_at})`;
|
|
10359
|
+
}).join(`
|
|
10360
|
+
`);
|
|
10361
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) changed since ${since}:
|
|
10362
|
+
${text}` }] };
|
|
10363
|
+
} catch (e) {
|
|
10364
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10365
|
+
}
|
|
10366
|
+
});
|
|
10367
|
+
server.tool("claim_next_task", "Atomically claim, lock, and start the best pending task.", {
|
|
10368
|
+
agent_id: exports_external.string(),
|
|
10369
|
+
project_id: exports_external.string().optional(),
|
|
10370
|
+
task_list_id: exports_external.string().optional(),
|
|
10371
|
+
plan_id: exports_external.string().optional(),
|
|
10372
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
10373
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
10374
|
+
try {
|
|
10375
|
+
const filters = {};
|
|
10376
|
+
if (project_id)
|
|
10377
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10378
|
+
if (task_list_id)
|
|
10379
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10380
|
+
if (plan_id)
|
|
10381
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
10382
|
+
if (tags)
|
|
10383
|
+
filters.tags = tags;
|
|
10384
|
+
const task = claimNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10385
|
+
if (!task) {
|
|
10386
|
+
return { content: [{ type: "text", text: "No tasks available to claim." }] };
|
|
10387
|
+
}
|
|
10388
|
+
return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
|
|
10389
|
+
${formatTaskDetail(task)}` }] };
|
|
10390
|
+
} catch (e) {
|
|
10391
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10392
|
+
}
|
|
10393
|
+
});
|
|
10394
|
+
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
10395
|
+
stale_minutes: exports_external.number().optional(),
|
|
10396
|
+
project_id: exports_external.string().optional(),
|
|
10397
|
+
task_list_id: exports_external.string().optional()
|
|
10398
|
+
}, async ({ stale_minutes, project_id, task_list_id }) => {
|
|
10399
|
+
try {
|
|
10400
|
+
const filters = {};
|
|
10401
|
+
if (project_id)
|
|
10402
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10403
|
+
if (task_list_id)
|
|
10404
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10405
|
+
const tasks = getStaleTasks(stale_minutes || 30, Object.keys(filters).length > 0 ? filters : undefined);
|
|
10406
|
+
if (tasks.length === 0) {
|
|
10407
|
+
return { content: [{ type: "text", text: "No stale tasks found." }] };
|
|
10408
|
+
}
|
|
10409
|
+
const text = tasks.map((t) => {
|
|
10410
|
+
const id = t.short_id || t.id.slice(0, 8);
|
|
10411
|
+
const agent = t.locked_by || t.assigned_to || "unknown";
|
|
10412
|
+
const staleFor = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
10413
|
+
return `${id} | ${agent} | ${t.title} (stale ${staleFor}min)`;
|
|
10414
|
+
}).join(`
|
|
10415
|
+
`);
|
|
10416
|
+
return { content: [{ type: "text", text: `${tasks.length} stale task(s):
|
|
10417
|
+
${text}` }] };
|
|
10418
|
+
} catch (e) {
|
|
10419
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10420
|
+
}
|
|
10421
|
+
});
|
|
10422
|
+
server.tool("get_status", "Get a full project health snapshot \u2014 counts, active work, next task, stale/overdue summary.", {
|
|
10423
|
+
agent_id: exports_external.string().optional(),
|
|
10424
|
+
project_id: exports_external.string().optional(),
|
|
10425
|
+
task_list_id: exports_external.string().optional()
|
|
10426
|
+
}, async ({ agent_id, project_id, task_list_id }) => {
|
|
10427
|
+
try {
|
|
10428
|
+
const filters = {};
|
|
10429
|
+
if (project_id)
|
|
10430
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
10431
|
+
if (task_list_id)
|
|
10432
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
10433
|
+
const status = getStatus(Object.keys(filters).length > 0 ? filters : undefined, agent_id);
|
|
10434
|
+
const lines = [
|
|
10435
|
+
`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done | ${status.total} total`
|
|
10436
|
+
];
|
|
10437
|
+
if (status.stale_count > 0)
|
|
10438
|
+
lines.push(`\u26A0\uFE0F ${status.stale_count} stale (stuck in_progress)`);
|
|
10439
|
+
if (status.overdue_recurring > 0)
|
|
10440
|
+
lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
|
|
10441
|
+
if (status.active_work.length > 0) {
|
|
10442
|
+
lines.push(`
|
|
10443
|
+
Active (${status.active_work.length}):`);
|
|
10444
|
+
for (const w of status.active_work.slice(0, 5)) {
|
|
10445
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
10446
|
+
lines.push(` ${id} | ${w.assigned_to || w.locked_by || "?"} | ${w.title}`);
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
if (status.next_task) {
|
|
10450
|
+
lines.push(`
|
|
10451
|
+
Next up:`);
|
|
10452
|
+
lines.push(` ${formatTask(status.next_task)}`);
|
|
10453
|
+
} else {
|
|
10454
|
+
lines.push(`
|
|
10455
|
+
No pending tasks available.`);
|
|
10456
|
+
}
|
|
10457
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
10458
|
+
`) }] };
|
|
10459
|
+
} catch (e) {
|
|
10460
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10461
|
+
}
|
|
10462
|
+
});
|
|
10463
|
+
server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
|
|
9384
10464
|
const all = [
|
|
9385
10465
|
"create_task",
|
|
9386
10466
|
"list_tasks",
|
|
@@ -9389,6 +10469,7 @@ In Progress:`);
|
|
|
9389
10469
|
"delete_task",
|
|
9390
10470
|
"start_task",
|
|
9391
10471
|
"complete_task",
|
|
10472
|
+
"fail_task",
|
|
9392
10473
|
"lock_task",
|
|
9393
10474
|
"unlock_task",
|
|
9394
10475
|
"approve_task",
|
|
@@ -9405,7 +10486,11 @@ In Progress:`);
|
|
|
9405
10486
|
"register_agent",
|
|
9406
10487
|
"list_agents",
|
|
9407
10488
|
"get_agent",
|
|
10489
|
+
"rename_agent",
|
|
10490
|
+
"delete_agent",
|
|
9408
10491
|
"get_my_tasks",
|
|
10492
|
+
"get_org_chart",
|
|
10493
|
+
"set_reports_to",
|
|
9409
10494
|
"create_task_list",
|
|
9410
10495
|
"list_task_lists",
|
|
9411
10496
|
"get_task_list",
|
|
@@ -9413,6 +10498,10 @@ In Progress:`);
|
|
|
9413
10498
|
"delete_task_list",
|
|
9414
10499
|
"search_tasks",
|
|
9415
10500
|
"sync",
|
|
10501
|
+
"clone_task",
|
|
10502
|
+
"move_task",
|
|
10503
|
+
"get_next_task",
|
|
10504
|
+
"claim_next_task",
|
|
9416
10505
|
"get_task_history",
|
|
9417
10506
|
"get_recent_activity",
|
|
9418
10507
|
"create_webhook",
|
|
@@ -9422,6 +10511,14 @@ In Progress:`);
|
|
|
9422
10511
|
"list_templates",
|
|
9423
10512
|
"create_task_from_template",
|
|
9424
10513
|
"delete_template",
|
|
10514
|
+
"bulk_update_tasks",
|
|
10515
|
+
"bulk_create_tasks",
|
|
10516
|
+
"get_task_stats",
|
|
10517
|
+
"get_task_graph",
|
|
10518
|
+
"get_active_work",
|
|
10519
|
+
"get_tasks_changed_since",
|
|
10520
|
+
"get_stale_tasks",
|
|
10521
|
+
"get_status",
|
|
9425
10522
|
"search_tools",
|
|
9426
10523
|
"describe_tools"
|
|
9427
10524
|
];
|
|
@@ -9429,27 +10526,178 @@ In Progress:`);
|
|
|
9429
10526
|
const matches = q ? all.filter((n) => n.includes(q)) : all;
|
|
9430
10527
|
return { content: [{ type: "text", text: matches.join(", ") }] };
|
|
9431
10528
|
});
|
|
9432
|
-
server.tool("describe_tools", "Get
|
|
10529
|
+
server.tool("describe_tools", "Get detailed parameter info for specific tools by name.", { names: exports_external.array(exports_external.string()) }, async ({ names }) => {
|
|
9433
10530
|
const descriptions = {
|
|
9434
|
-
create_task:
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
10531
|
+
create_task: `Create a new task.
|
|
10532
|
+
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)
|
|
10533
|
+
Example: {title: 'Daily standup', recurrence_rule: 'every weekday', priority: 'medium'}`,
|
|
10534
|
+
list_tasks: `List tasks with optional filters. Supports pagination.
|
|
10535
|
+
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)
|
|
10536
|
+
Example: {status: ['pending', 'in_progress'], has_recurrence: true, limit: 20}`,
|
|
10537
|
+
get_task: `Get full task details with subtasks, deps, and comments.
|
|
10538
|
+
Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
|
|
10539
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10540
|
+
update_task: `Update task fields. Requires version for optimistic locking (get it from get_task first).
|
|
10541
|
+
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)
|
|
10542
|
+
Example: {id: 'a1b2c3d4', version: 3, status: 'completed'}`,
|
|
10543
|
+
delete_task: `Delete a task permanently. Subtasks cascade-delete. Dependencies removed.
|
|
10544
|
+
Params: id(string, req)
|
|
10545
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10546
|
+
start_task: `Claim, lock, and set task status to in_progress in one call.
|
|
10547
|
+
Params: id(string, req), agent_id(string, req \u2014 your 8-char agent ID)
|
|
10548
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10549
|
+
complete_task: `Mark task completed, release lock, set completed_at timestamp. For recurring tasks, auto-spawns next instance unless skip_recurrence is true.
|
|
10550
|
+
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)
|
|
10551
|
+
Example: {id: 'a1b2c3d4', skip_recurrence: false}`,
|
|
10552
|
+
lock_task: `Acquire exclusive lock on a task. Locks auto-expire after 30 min. Re-locking by same agent is idempotent.
|
|
10553
|
+
Params: id(string, req), agent_id(string, req)
|
|
10554
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10555
|
+
unlock_task: `Release exclusive lock on a task.
|
|
10556
|
+
Params: id(string, req), agent_id(string, optional \u2014 omit to force-unlock)
|
|
10557
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10558
|
+
approve_task: `Approve a task with requires_approval=true. Must be approved before completion.
|
|
10559
|
+
Params: id(string, req), agent_id(string, optional \u2014 defaults to 'system')
|
|
10560
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
10561
|
+
fail_task: `Mark a task as failed with structured reason and optional auto-retry. Stores failure info in metadata._failure, releases lock.
|
|
10562
|
+
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)
|
|
10563
|
+
Example: {id: 'a1b2c3d4', reason: 'Build timeout', error_code: 'TIMEOUT', retry: true}`,
|
|
10564
|
+
add_dependency: `Add a dependency: task_id depends on depends_on. Prevents cycles via BFS.
|
|
10565
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
10566
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
10567
|
+
remove_dependency: `Remove a dependency link between two tasks.
|
|
10568
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
10569
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
10570
|
+
add_comment: `Add a comment/note to a task. Comments are append-only.
|
|
10571
|
+
Params: task_id(string, req), content(string, req), agent_id(string), session_id(string)
|
|
10572
|
+
Example: {task_id: 'a1b2c3d4', content: 'Blocked by API rate limit'}`,
|
|
10573
|
+
create_project: `Register a new project. Auto-generates task prefix for short IDs (e.g. APP-00001).
|
|
10574
|
+
Params: name(string, req), path(string, req \u2014 unique absolute path), description(string), task_list_id(string)
|
|
10575
|
+
Example: {name: 'my-app', path: '/Users/dev/my-app'}`,
|
|
10576
|
+
list_projects: "List all registered projects. No params.",
|
|
10577
|
+
create_plan: `Create a plan to group related tasks.
|
|
10578
|
+
Params: name(string, req), project_id(string), description(string), status(active|completed|archived, default:active), task_list_id(string), agent_id(string)
|
|
10579
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
10580
|
+
list_plans: `List all plans, optionally filtered by project.
|
|
10581
|
+
Params: project_id(string)
|
|
10582
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10583
|
+
get_plan: `Get plan details (name, status, description, timestamps).
|
|
10584
|
+
Params: id(string, req)
|
|
10585
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10586
|
+
update_plan: `Update plan fields.
|
|
10587
|
+
Params: id(string, req), name(string), description(string), status(active|completed|archived), task_list_id(string), agent_id(string)
|
|
10588
|
+
Example: {id: 'a1b2c3d4', status: 'completed'}`,
|
|
10589
|
+
delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
|
|
10590
|
+
Params: id(string, req)
|
|
10591
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10592
|
+
register_agent: `Register an agent (idempotent by name). Returns existing agent if name matches.
|
|
10593
|
+
Params: name(string, req \u2014 e.g. 'maximus'), description(string)
|
|
10594
|
+
Example: {name: 'maximus', description: 'Backend developer'}`,
|
|
10595
|
+
list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
|
|
10596
|
+
get_agent: `Get agent details by ID or name. Provide one of id or name.
|
|
10597
|
+
Params: id(string), name(string)
|
|
10598
|
+
Example: {name: 'maximus'}`,
|
|
10599
|
+
rename_agent: `Rename an agent. Resolve by id or current name.
|
|
10600
|
+
Params: id(string), name(string \u2014 current name), new_name(string, req)
|
|
10601
|
+
Example: {name: 'old-name', new_name: 'new-name'}`,
|
|
10602
|
+
delete_agent: `Delete an agent permanently. Resolve by id or name.
|
|
10603
|
+
Params: id(string), name(string)
|
|
10604
|
+
Example: {name: 'maximus'}`,
|
|
10605
|
+
get_my_tasks: `Get all tasks assigned to/created by an agent, with stats (pending/active/done/rate).
|
|
10606
|
+
Params: agent_name(string, req)
|
|
10607
|
+
Example: {agent_name: 'maximus'}`,
|
|
10608
|
+
get_org_chart: "Get agent org chart showing reporting hierarchy. No params.",
|
|
10609
|
+
set_reports_to: `Set who an agent reports to in the org chart. Omit manager_name for top-level.
|
|
10610
|
+
Params: agent_name(string, req), manager_name(string, optional)
|
|
10611
|
+
Example: {agent_name: 'brutus', manager_name: 'maximus'}`,
|
|
10612
|
+
create_task_list: `Create a task list \u2014 a container/folder for organizing tasks.
|
|
10613
|
+
Params: name(string, req), slug(string \u2014 auto-generated if omitted), project_id(string), description(string)
|
|
10614
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
10615
|
+
list_task_lists: `List all task lists, optionally filtered by project.
|
|
10616
|
+
Params: project_id(string)
|
|
10617
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10618
|
+
get_task_list: `Get task list details (name, slug, project, metadata).
|
|
10619
|
+
Params: id(string, req)
|
|
10620
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10621
|
+
update_task_list: `Update a task list's name or description.
|
|
10622
|
+
Params: id(string, req), name(string), description(string)
|
|
10623
|
+
Example: {id: 'a1b2c3d4', name: 'Sprint 2'}`,
|
|
10624
|
+
delete_task_list: `Delete a task list. Tasks are orphaned (not deleted).
|
|
10625
|
+
Params: id(string, req)
|
|
10626
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10627
|
+
search_tasks: `Full-text search across task titles, descriptions, and tags. Supports filters.
|
|
10628
|
+
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)
|
|
10629
|
+
Example: {query: 'auth bug', status: 'pending'}`,
|
|
10630
|
+
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.
|
|
10631
|
+
Params: agent_id(string \u2014 prefers your tasks), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
10632
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
10633
|
+
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.
|
|
10634
|
+
Params: agent_id(string, req \u2014 used for lock and assignment), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
10635
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
10636
|
+
sync: `Sync tasks between local DB and agent task list (e.g. Claude Code).
|
|
10637
|
+
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)
|
|
10638
|
+
Example: {agent: 'claude', direction: 'push'}`,
|
|
10639
|
+
clone_task: `Duplicate a task with optional field overrides. Creates new independent copy.
|
|
10640
|
+
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)
|
|
10641
|
+
Example: {task_id: 'a1b2c3d4', title: 'Cloned task', assigned_to: 'brutus'}`,
|
|
10642
|
+
move_task: `Move a task to a different list, project, or plan.
|
|
10643
|
+
Params: task_id(string, req), task_list_id(string|null), project_id(string|null), plan_id(string|null)
|
|
10644
|
+
Example: {task_id: 'a1b2c3d4', task_list_id: 'e5f6g7h8'}`,
|
|
10645
|
+
bulk_update_tasks: `Update multiple tasks at once with the same changes.
|
|
10646
|
+
Params: task_ids(string[], req), status(pending|in_progress|completed|failed|cancelled), priority(low|medium|high|critical), assigned_to(string), tags(string[])
|
|
10647
|
+
Example: {task_ids: ['abc12345', 'def67890'], status: 'completed'}`,
|
|
10648
|
+
bulk_create_tasks: `Create multiple tasks atomically. Supports inter-task dependencies via temp_id references.
|
|
10649
|
+
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)
|
|
10650
|
+
Example: {tasks: [{temp_id: 'a', title: 'First'}, {temp_id: 'b', title: 'Second', depends_on_temp_ids: ['a']}]}`,
|
|
10651
|
+
get_task_stats: `Get task analytics: counts by status, priority, agent, and completion rate. All via SQL.
|
|
10652
|
+
Params: project_id(string), task_list_id(string), agent_id(string)
|
|
10653
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10654
|
+
get_task_graph: `Get full dependency tree for a task \u2014 upstream blockers and downstream dependents.
|
|
10655
|
+
Params: id(string, req), direction(up|down|both, default:both)
|
|
10656
|
+
Example: {id: 'a1b2c3d4', direction: 'up'}`,
|
|
10657
|
+
get_task_history: `Get audit log for a task \u2014 all field changes with timestamps and actors.
|
|
10658
|
+
Params: task_id(string, req)
|
|
10659
|
+
Example: {task_id: 'a1b2c3d4'}`,
|
|
10660
|
+
get_recent_activity: `Get recent task changes across all tasks \u2014 global activity feed.
|
|
10661
|
+
Params: limit(number, default:50)
|
|
10662
|
+
Example: {limit: 20}`,
|
|
10663
|
+
create_webhook: `Register a webhook for task change events.
|
|
10664
|
+
Params: url(string, req), events(string[] \u2014 empty=all), secret(string \u2014 HMAC signing)
|
|
10665
|
+
Example: {url: 'https://example.com/hook', events: ['task.created', 'task.completed']}`,
|
|
10666
|
+
list_webhooks: "List all registered webhooks. No params.",
|
|
10667
|
+
delete_webhook: `Delete a webhook by ID.
|
|
10668
|
+
Params: id(string, req)
|
|
10669
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10670
|
+
create_template: `Create a reusable task template.
|
|
10671
|
+
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)
|
|
10672
|
+
Example: {name: 'Bug Report', title_pattern: 'Bug: {description}', priority: 'high', tags: ['bug']}`,
|
|
10673
|
+
list_templates: "List all task templates. No params.",
|
|
10674
|
+
create_task_from_template: `Create a task from a template with optional overrides.
|
|
10675
|
+
Params: template_id(string, req), title(string), description(string), priority(low|medium|high|critical), assigned_to(string), project_id(string)
|
|
10676
|
+
Example: {template_id: 'a1b2c3d4', assigned_to: 'maximus'}`,
|
|
10677
|
+
delete_template: `Delete a task template.
|
|
10678
|
+
Params: id(string, req)
|
|
10679
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
10680
|
+
get_active_work: `See all in-progress tasks and who is working on them.
|
|
10681
|
+
Params: project_id(string, optional), task_list_id(string, optional)
|
|
10682
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
10683
|
+
get_tasks_changed_since: `Get tasks modified after a timestamp \u2014 incremental delta sync.
|
|
10684
|
+
Params: since(string, req \u2014 ISO date), project_id(string, optional), task_list_id(string, optional)
|
|
10685
|
+
Example: {since: '2026-03-14T10:00:00Z'}`,
|
|
10686
|
+
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
10687
|
+
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
10688
|
+
Example: {stale_minutes: 60, project_id: 'a1b2c3d4'}`,
|
|
10689
|
+
get_status: `Get a full project health snapshot \u2014 pending/in_progress/completed counts, active work, next recommended task, stale task count, overdue recurring tasks. Saves 4+ round trips at session start.
|
|
10690
|
+
Params: agent_id(string, optional \u2014 prefers tasks assigned to this agent for next_task), project_id(string, optional), task_list_id(string, optional)
|
|
10691
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
10692
|
+
search_tools: `List all tool names or filter by substring.
|
|
10693
|
+
Params: query(string, optional)
|
|
10694
|
+
Example: {query: 'task'}`,
|
|
10695
|
+
describe_tools: `Get detailed descriptions and parameter info for tools by name.
|
|
10696
|
+
Params: names(string[], req)
|
|
10697
|
+
Example: {names: ['create_task', 'update_task']}`
|
|
9451
10698
|
};
|
|
9452
|
-
const result = names.map((n) => `${n}: ${descriptions[n] || "
|
|
10699
|
+
const result = names.map((n) => `${n}: ${descriptions[n] || "Unknown tool. Use search_tools to list available tools."}`).join(`
|
|
10700
|
+
|
|
9453
10701
|
`);
|
|
9454
10702
|
return { content: [{ type: "text", text: result }] };
|
|
9455
10703
|
});
|