@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/mcp/index.js
CHANGED
|
@@ -192,6 +192,8 @@ function ensureSchema(db) {
|
|
|
192
192
|
ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
|
|
193
193
|
ensureColumn("tasks", "approved_by", "TEXT");
|
|
194
194
|
ensureColumn("tasks", "approved_at", "TEXT");
|
|
195
|
+
ensureColumn("tasks", "recurrence_rule", "TEXT");
|
|
196
|
+
ensureColumn("tasks", "recurrence_parent_id", "TEXT REFERENCES tasks(id) ON DELETE SET NULL");
|
|
195
197
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
196
198
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
197
199
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -216,6 +218,8 @@ function ensureSchema(db) {
|
|
|
216
218
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
|
|
217
219
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
|
|
218
220
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
|
|
221
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id)");
|
|
222
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL");
|
|
219
223
|
}
|
|
220
224
|
function backfillTaskTags(db) {
|
|
221
225
|
try {
|
|
@@ -515,6 +519,13 @@ var init_database = __esm(() => {
|
|
|
515
519
|
ALTER TABLE agents ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
|
|
516
520
|
ALTER TABLE projects ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
|
|
517
521
|
INSERT OR IGNORE INTO _migrations (id) VALUES (12);
|
|
522
|
+
`,
|
|
523
|
+
`
|
|
524
|
+
ALTER TABLE tasks ADD COLUMN recurrence_rule TEXT;
|
|
525
|
+
ALTER TABLE tasks ADD COLUMN recurrence_parent_id TEXT REFERENCES tasks(id) ON DELETE SET NULL;
|
|
526
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id);
|
|
527
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL;
|
|
528
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (13);
|
|
518
529
|
`
|
|
519
530
|
];
|
|
520
531
|
});
|
|
@@ -546,6 +557,57 @@ var init_audit = __esm(() => {
|
|
|
546
557
|
init_database();
|
|
547
558
|
});
|
|
548
559
|
|
|
560
|
+
// src/db/webhooks.ts
|
|
561
|
+
var exports_webhooks = {};
|
|
562
|
+
__export(exports_webhooks, {
|
|
563
|
+
listWebhooks: () => listWebhooks,
|
|
564
|
+
getWebhook: () => getWebhook,
|
|
565
|
+
dispatchWebhook: () => dispatchWebhook,
|
|
566
|
+
deleteWebhook: () => deleteWebhook,
|
|
567
|
+
createWebhook: () => createWebhook
|
|
568
|
+
});
|
|
569
|
+
function rowToWebhook(row) {
|
|
570
|
+
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
571
|
+
}
|
|
572
|
+
function createWebhook(input, db) {
|
|
573
|
+
const d = db || getDatabase();
|
|
574
|
+
const id = uuid();
|
|
575
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
576
|
+
return getWebhook(id, d);
|
|
577
|
+
}
|
|
578
|
+
function getWebhook(id, db) {
|
|
579
|
+
const d = db || getDatabase();
|
|
580
|
+
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
581
|
+
return row ? rowToWebhook(row) : null;
|
|
582
|
+
}
|
|
583
|
+
function listWebhooks(db) {
|
|
584
|
+
const d = db || getDatabase();
|
|
585
|
+
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
586
|
+
}
|
|
587
|
+
function deleteWebhook(id, db) {
|
|
588
|
+
const d = db || getDatabase();
|
|
589
|
+
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
590
|
+
}
|
|
591
|
+
async function dispatchWebhook(event, payload, db) {
|
|
592
|
+
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
593
|
+
for (const wh of webhooks) {
|
|
594
|
+
try {
|
|
595
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
596
|
+
const headers = { "Content-Type": "application/json" };
|
|
597
|
+
if (wh.secret) {
|
|
598
|
+
const encoder = new TextEncoder;
|
|
599
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
600
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
601
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
602
|
+
}
|
|
603
|
+
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
604
|
+
} catch {}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
var init_webhooks = __esm(() => {
|
|
608
|
+
init_database();
|
|
609
|
+
});
|
|
610
|
+
|
|
549
611
|
// src/db/agents.ts
|
|
550
612
|
var exports_agents = {};
|
|
551
613
|
__export(exports_agents, {
|
|
@@ -689,57 +751,6 @@ var init_agents = __esm(() => {
|
|
|
689
751
|
init_database();
|
|
690
752
|
});
|
|
691
753
|
|
|
692
|
-
// src/db/webhooks.ts
|
|
693
|
-
var exports_webhooks = {};
|
|
694
|
-
__export(exports_webhooks, {
|
|
695
|
-
listWebhooks: () => listWebhooks,
|
|
696
|
-
getWebhook: () => getWebhook,
|
|
697
|
-
dispatchWebhook: () => dispatchWebhook,
|
|
698
|
-
deleteWebhook: () => deleteWebhook,
|
|
699
|
-
createWebhook: () => createWebhook
|
|
700
|
-
});
|
|
701
|
-
function rowToWebhook(row) {
|
|
702
|
-
return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
|
|
703
|
-
}
|
|
704
|
-
function createWebhook(input, db) {
|
|
705
|
-
const d = db || getDatabase();
|
|
706
|
-
const id = uuid();
|
|
707
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
|
|
708
|
-
return getWebhook(id, d);
|
|
709
|
-
}
|
|
710
|
-
function getWebhook(id, db) {
|
|
711
|
-
const d = db || getDatabase();
|
|
712
|
-
const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
713
|
-
return row ? rowToWebhook(row) : null;
|
|
714
|
-
}
|
|
715
|
-
function listWebhooks(db) {
|
|
716
|
-
const d = db || getDatabase();
|
|
717
|
-
return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
|
|
718
|
-
}
|
|
719
|
-
function deleteWebhook(id, db) {
|
|
720
|
-
const d = db || getDatabase();
|
|
721
|
-
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
722
|
-
}
|
|
723
|
-
async function dispatchWebhook(event, payload, db) {
|
|
724
|
-
const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
725
|
-
for (const wh of webhooks) {
|
|
726
|
-
try {
|
|
727
|
-
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
728
|
-
const headers = { "Content-Type": "application/json" };
|
|
729
|
-
if (wh.secret) {
|
|
730
|
-
const encoder = new TextEncoder;
|
|
731
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
732
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
733
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
734
|
-
}
|
|
735
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
736
|
-
} catch {}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
var init_webhooks = __esm(() => {
|
|
740
|
-
init_database();
|
|
741
|
-
});
|
|
742
|
-
|
|
743
754
|
// src/db/templates.ts
|
|
744
755
|
var exports_templates = {};
|
|
745
756
|
__export(exports_templates, {
|
|
@@ -4789,6 +4800,8 @@ class VersionConflictError extends Error {
|
|
|
4789
4800
|
taskId;
|
|
4790
4801
|
expectedVersion;
|
|
4791
4802
|
actualVersion;
|
|
4803
|
+
static code = "VERSION_CONFLICT";
|
|
4804
|
+
static suggestion = "Fetch the task with get_task to get the current version before updating.";
|
|
4792
4805
|
constructor(taskId, expectedVersion, actualVersion) {
|
|
4793
4806
|
super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
|
|
4794
4807
|
this.taskId = taskId;
|
|
@@ -4800,14 +4813,30 @@ class VersionConflictError extends Error {
|
|
|
4800
4813
|
|
|
4801
4814
|
class TaskNotFoundError extends Error {
|
|
4802
4815
|
taskId;
|
|
4816
|
+
static code = "TASK_NOT_FOUND";
|
|
4817
|
+
static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
|
|
4803
4818
|
constructor(taskId) {
|
|
4804
4819
|
super(`Task not found: ${taskId}`);
|
|
4805
4820
|
this.taskId = taskId;
|
|
4806
4821
|
this.name = "TaskNotFoundError";
|
|
4807
4822
|
}
|
|
4808
4823
|
}
|
|
4824
|
+
|
|
4825
|
+
class ProjectNotFoundError extends Error {
|
|
4826
|
+
projectId;
|
|
4827
|
+
static code = "PROJECT_NOT_FOUND";
|
|
4828
|
+
static suggestion = "Use list_projects to see available projects.";
|
|
4829
|
+
constructor(projectId) {
|
|
4830
|
+
super(`Project not found: ${projectId}`);
|
|
4831
|
+
this.projectId = projectId;
|
|
4832
|
+
this.name = "ProjectNotFoundError";
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4809
4836
|
class PlanNotFoundError extends Error {
|
|
4810
4837
|
planId;
|
|
4838
|
+
static code = "PLAN_NOT_FOUND";
|
|
4839
|
+
static suggestion = "Use list_plans to see available plans.";
|
|
4811
4840
|
constructor(planId) {
|
|
4812
4841
|
super(`Plan not found: ${planId}`);
|
|
4813
4842
|
this.planId = planId;
|
|
@@ -4818,6 +4847,8 @@ class PlanNotFoundError extends Error {
|
|
|
4818
4847
|
class LockError extends Error {
|
|
4819
4848
|
taskId;
|
|
4820
4849
|
lockedBy;
|
|
4850
|
+
static code = "LOCK_ERROR";
|
|
4851
|
+
static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
|
|
4821
4852
|
constructor(taskId, lockedBy) {
|
|
4822
4853
|
super(`Task ${taskId} is locked by ${lockedBy}`);
|
|
4823
4854
|
this.taskId = taskId;
|
|
@@ -4825,8 +4856,22 @@ class LockError extends Error {
|
|
|
4825
4856
|
this.name = "LockError";
|
|
4826
4857
|
}
|
|
4827
4858
|
}
|
|
4859
|
+
|
|
4860
|
+
class AgentNotFoundError extends Error {
|
|
4861
|
+
agentId;
|
|
4862
|
+
static code = "AGENT_NOT_FOUND";
|
|
4863
|
+
static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
|
|
4864
|
+
constructor(agentId) {
|
|
4865
|
+
super(`Agent not found: ${agentId}`);
|
|
4866
|
+
this.agentId = agentId;
|
|
4867
|
+
this.name = "AgentNotFoundError";
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4828
4871
|
class TaskListNotFoundError extends Error {
|
|
4829
4872
|
taskListId;
|
|
4873
|
+
static code = "TASK_LIST_NOT_FOUND";
|
|
4874
|
+
static suggestion = "Use list_task_lists to see available lists.";
|
|
4830
4875
|
constructor(taskListId) {
|
|
4831
4876
|
super(`Task list not found: ${taskListId}`);
|
|
4832
4877
|
this.taskListId = taskListId;
|
|
@@ -4837,6 +4882,8 @@ class TaskListNotFoundError extends Error {
|
|
|
4837
4882
|
class DependencyCycleError extends Error {
|
|
4838
4883
|
taskId;
|
|
4839
4884
|
dependsOn;
|
|
4885
|
+
static code = "DEPENDENCY_CYCLE";
|
|
4886
|
+
static suggestion = "Check the dependency chain with get_task to avoid circular references.";
|
|
4840
4887
|
constructor(taskId, dependsOn) {
|
|
4841
4888
|
super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
|
|
4842
4889
|
this.taskId = taskId;
|
|
@@ -4848,6 +4895,8 @@ class DependencyCycleError extends Error {
|
|
|
4848
4895
|
class CompletionGuardError extends Error {
|
|
4849
4896
|
reason;
|
|
4850
4897
|
retryAfterSeconds;
|
|
4898
|
+
static code = "COMPLETION_BLOCKED";
|
|
4899
|
+
static suggestion = "Wait for the cooldown period, then retry.";
|
|
4851
4900
|
constructor(reason, retryAfterSeconds) {
|
|
4852
4901
|
super(reason);
|
|
4853
4902
|
this.reason = reason;
|
|
@@ -5074,6 +5123,96 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
5074
5123
|
|
|
5075
5124
|
// src/db/tasks.ts
|
|
5076
5125
|
init_audit();
|
|
5126
|
+
|
|
5127
|
+
// src/lib/recurrence.ts
|
|
5128
|
+
var DAY_NAMES = {
|
|
5129
|
+
sunday: 0,
|
|
5130
|
+
sun: 0,
|
|
5131
|
+
monday: 1,
|
|
5132
|
+
mon: 1,
|
|
5133
|
+
tuesday: 2,
|
|
5134
|
+
tue: 2,
|
|
5135
|
+
wednesday: 3,
|
|
5136
|
+
wed: 3,
|
|
5137
|
+
thursday: 4,
|
|
5138
|
+
thu: 4,
|
|
5139
|
+
friday: 5,
|
|
5140
|
+
fri: 5,
|
|
5141
|
+
saturday: 6,
|
|
5142
|
+
sat: 6
|
|
5143
|
+
};
|
|
5144
|
+
function parseRecurrenceRule(rule) {
|
|
5145
|
+
const normalized = rule.trim().toLowerCase();
|
|
5146
|
+
if (normalized === "every weekday" || normalized === "every weekdays") {
|
|
5147
|
+
return { type: "specific_days", days: [1, 2, 3, 4, 5] };
|
|
5148
|
+
}
|
|
5149
|
+
if (normalized === "every day" || normalized === "daily") {
|
|
5150
|
+
return { type: "interval", interval: 1, unit: "day" };
|
|
5151
|
+
}
|
|
5152
|
+
if (normalized === "every week" || normalized === "weekly") {
|
|
5153
|
+
return { type: "interval", interval: 1, unit: "week" };
|
|
5154
|
+
}
|
|
5155
|
+
if (normalized === "every month" || normalized === "monthly") {
|
|
5156
|
+
return { type: "interval", interval: 1, unit: "month" };
|
|
5157
|
+
}
|
|
5158
|
+
const intervalMatch = normalized.match(/^every\s+(\d+)\s+(day|week|month)s?$/);
|
|
5159
|
+
if (intervalMatch) {
|
|
5160
|
+
return {
|
|
5161
|
+
type: "interval",
|
|
5162
|
+
interval: parseInt(intervalMatch[1], 10),
|
|
5163
|
+
unit: intervalMatch[2]
|
|
5164
|
+
};
|
|
5165
|
+
}
|
|
5166
|
+
const daysMatch = normalized.match(/^every\s+(.+)$/);
|
|
5167
|
+
if (daysMatch) {
|
|
5168
|
+
const dayParts = daysMatch[1].split(/[,\s]+/).map((d) => d.trim()).filter(Boolean);
|
|
5169
|
+
const days = [];
|
|
5170
|
+
for (const part of dayParts) {
|
|
5171
|
+
const dayNum = DAY_NAMES[part];
|
|
5172
|
+
if (dayNum !== undefined) {
|
|
5173
|
+
days.push(dayNum);
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
5176
|
+
if (days.length > 0) {
|
|
5177
|
+
return { type: "specific_days", days: days.sort((a, b) => a - b) };
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
throw new Error(`Invalid recurrence rule: "${rule}". Supported formats: "every day", "every weekday", "every week", "every 2 weeks", "every month", "every N days/weeks/months", "every monday", "every mon,wed,fri"`);
|
|
5181
|
+
}
|
|
5182
|
+
function nextOccurrence(rule, from) {
|
|
5183
|
+
const parsed = parseRecurrenceRule(rule);
|
|
5184
|
+
const base = from || new Date;
|
|
5185
|
+
if (parsed.type === "interval") {
|
|
5186
|
+
const next = new Date(base);
|
|
5187
|
+
if (parsed.unit === "day") {
|
|
5188
|
+
next.setDate(next.getDate() + parsed.interval);
|
|
5189
|
+
} else if (parsed.unit === "week") {
|
|
5190
|
+
next.setDate(next.getDate() + parsed.interval * 7);
|
|
5191
|
+
} else if (parsed.unit === "month") {
|
|
5192
|
+
next.setMonth(next.getMonth() + parsed.interval);
|
|
5193
|
+
}
|
|
5194
|
+
return next.toISOString();
|
|
5195
|
+
}
|
|
5196
|
+
if (parsed.type === "specific_days") {
|
|
5197
|
+
const currentDay = base.getDay();
|
|
5198
|
+
const days = parsed.days;
|
|
5199
|
+
let daysToAdd = Infinity;
|
|
5200
|
+
for (const day of days) {
|
|
5201
|
+
let diff = day - currentDay;
|
|
5202
|
+
if (diff <= 0)
|
|
5203
|
+
diff += 7;
|
|
5204
|
+
if (diff < daysToAdd)
|
|
5205
|
+
daysToAdd = diff;
|
|
5206
|
+
}
|
|
5207
|
+
const next = new Date(base);
|
|
5208
|
+
next.setDate(next.getDate() + daysToAdd);
|
|
5209
|
+
return next.toISOString();
|
|
5210
|
+
}
|
|
5211
|
+
throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5214
|
+
// src/db/tasks.ts
|
|
5215
|
+
init_webhooks();
|
|
5077
5216
|
function rowToTask(row) {
|
|
5078
5217
|
return {
|
|
5079
5218
|
...row,
|
|
@@ -5104,8 +5243,8 @@ function createTask(input, db) {
|
|
|
5104
5243
|
const tags = input.tags || [];
|
|
5105
5244
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
5106
5245
|
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
5107
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at)
|
|
5108
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5246
|
+
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)
|
|
5247
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5109
5248
|
id,
|
|
5110
5249
|
shortId,
|
|
5111
5250
|
input.project_id || null,
|
|
@@ -5128,12 +5267,16 @@ function createTask(input, db) {
|
|
|
5128
5267
|
input.estimated_minutes || null,
|
|
5129
5268
|
input.requires_approval ? 1 : 0,
|
|
5130
5269
|
null,
|
|
5131
|
-
null
|
|
5270
|
+
null,
|
|
5271
|
+
input.recurrence_rule || null,
|
|
5272
|
+
input.recurrence_parent_id || null
|
|
5132
5273
|
]);
|
|
5133
5274
|
if (tags.length > 0) {
|
|
5134
5275
|
insertTaskTags(id, tags, d);
|
|
5135
5276
|
}
|
|
5136
|
-
|
|
5277
|
+
const task = getTask(id, d);
|
|
5278
|
+
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(() => {});
|
|
5279
|
+
return task;
|
|
5137
5280
|
}
|
|
5138
5281
|
function getTask(id, db) {
|
|
5139
5282
|
const d = db || getDatabase();
|
|
@@ -5228,6 +5371,11 @@ function listTasks(filter = {}, db) {
|
|
|
5228
5371
|
conditions.push("task_list_id = ?");
|
|
5229
5372
|
params.push(filter.task_list_id);
|
|
5230
5373
|
}
|
|
5374
|
+
if (filter.has_recurrence === true) {
|
|
5375
|
+
conditions.push("recurrence_rule IS NOT NULL");
|
|
5376
|
+
} else if (filter.has_recurrence === false) {
|
|
5377
|
+
conditions.push("recurrence_rule IS NULL");
|
|
5378
|
+
}
|
|
5231
5379
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5232
5380
|
let limitClause = "";
|
|
5233
5381
|
if (filter.limit) {
|
|
@@ -5243,6 +5391,69 @@ function listTasks(filter = {}, db) {
|
|
|
5243
5391
|
created_at DESC${limitClause}`).all(...params);
|
|
5244
5392
|
return rows.map(rowToTask);
|
|
5245
5393
|
}
|
|
5394
|
+
function countTasks(filter = {}, db) {
|
|
5395
|
+
const d = db || getDatabase();
|
|
5396
|
+
const conditions = [];
|
|
5397
|
+
const params = [];
|
|
5398
|
+
if (filter.project_id) {
|
|
5399
|
+
conditions.push("project_id = ?");
|
|
5400
|
+
params.push(filter.project_id);
|
|
5401
|
+
}
|
|
5402
|
+
if (filter.parent_id !== undefined) {
|
|
5403
|
+
if (filter.parent_id === null) {
|
|
5404
|
+
conditions.push("parent_id IS NULL");
|
|
5405
|
+
} else {
|
|
5406
|
+
conditions.push("parent_id = ?");
|
|
5407
|
+
params.push(filter.parent_id);
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
if (filter.status) {
|
|
5411
|
+
if (Array.isArray(filter.status)) {
|
|
5412
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
5413
|
+
params.push(...filter.status);
|
|
5414
|
+
} else {
|
|
5415
|
+
conditions.push("status = ?");
|
|
5416
|
+
params.push(filter.status);
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
if (filter.priority) {
|
|
5420
|
+
if (Array.isArray(filter.priority)) {
|
|
5421
|
+
conditions.push(`priority IN (${filter.priority.map(() => "?").join(",")})`);
|
|
5422
|
+
params.push(...filter.priority);
|
|
5423
|
+
} else {
|
|
5424
|
+
conditions.push("priority = ?");
|
|
5425
|
+
params.push(filter.priority);
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
if (filter.assigned_to) {
|
|
5429
|
+
conditions.push("assigned_to = ?");
|
|
5430
|
+
params.push(filter.assigned_to);
|
|
5431
|
+
}
|
|
5432
|
+
if (filter.agent_id) {
|
|
5433
|
+
conditions.push("agent_id = ?");
|
|
5434
|
+
params.push(filter.agent_id);
|
|
5435
|
+
}
|
|
5436
|
+
if (filter.session_id) {
|
|
5437
|
+
conditions.push("session_id = ?");
|
|
5438
|
+
params.push(filter.session_id);
|
|
5439
|
+
}
|
|
5440
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
5441
|
+
const placeholders = filter.tags.map(() => "?").join(",");
|
|
5442
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
5443
|
+
params.push(...filter.tags);
|
|
5444
|
+
}
|
|
5445
|
+
if (filter.plan_id) {
|
|
5446
|
+
conditions.push("plan_id = ?");
|
|
5447
|
+
params.push(filter.plan_id);
|
|
5448
|
+
}
|
|
5449
|
+
if (filter.task_list_id) {
|
|
5450
|
+
conditions.push("task_list_id = ?");
|
|
5451
|
+
params.push(filter.task_list_id);
|
|
5452
|
+
}
|
|
5453
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5454
|
+
const row = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
5455
|
+
return row.count;
|
|
5456
|
+
}
|
|
5246
5457
|
function updateTask(id, input, db) {
|
|
5247
5458
|
const d = db || getDatabase();
|
|
5248
5459
|
const task = getTask(id, d);
|
|
@@ -5314,6 +5525,10 @@ function updateTask(id, input, db) {
|
|
|
5314
5525
|
sets.push("approved_at = ?");
|
|
5315
5526
|
params.push(now());
|
|
5316
5527
|
}
|
|
5528
|
+
if (input.recurrence_rule !== undefined) {
|
|
5529
|
+
sets.push("recurrence_rule = ?");
|
|
5530
|
+
params.push(input.recurrence_rule);
|
|
5531
|
+
}
|
|
5317
5532
|
params.push(id, input.version);
|
|
5318
5533
|
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
5319
5534
|
if (result.changes === 0) {
|
|
@@ -5334,6 +5549,12 @@ function updateTask(id, input, db) {
|
|
|
5334
5549
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
5335
5550
|
if (input.approved_by !== undefined)
|
|
5336
5551
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
5552
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
5553
|
+
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
5554
|
+
}
|
|
5555
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
5556
|
+
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
5557
|
+
}
|
|
5337
5558
|
return {
|
|
5338
5559
|
...task,
|
|
5339
5560
|
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
@@ -5385,9 +5606,10 @@ function startTask(id, agentId, db) {
|
|
|
5385
5606
|
}
|
|
5386
5607
|
}
|
|
5387
5608
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
5609
|
+
dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
5388
5610
|
return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
|
|
5389
5611
|
}
|
|
5390
|
-
function completeTask(id, agentId, db,
|
|
5612
|
+
function completeTask(id, agentId, db, options) {
|
|
5391
5613
|
const d = db || getDatabase();
|
|
5392
5614
|
const task = getTask(id, d);
|
|
5393
5615
|
if (!task)
|
|
@@ -5396,7 +5618,9 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
5396
5618
|
throw new LockError(id, task.locked_by);
|
|
5397
5619
|
}
|
|
5398
5620
|
checkCompletionGuard(task, agentId || null, d);
|
|
5399
|
-
|
|
5621
|
+
const evidence = options ? { files_changed: options.files_changed, test_results: options.test_results, commit_hash: options.commit_hash, notes: options.notes } : undefined;
|
|
5622
|
+
const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes);
|
|
5623
|
+
if (hasEvidence) {
|
|
5400
5624
|
const meta2 = { ...task.metadata, _evidence: evidence };
|
|
5401
5625
|
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
|
|
5402
5626
|
}
|
|
@@ -5404,7 +5628,15 @@ function completeTask(id, agentId, db, evidence) {
|
|
|
5404
5628
|
d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
|
|
5405
5629
|
WHERE id = ?`, [timestamp, timestamp, id]);
|
|
5406
5630
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
5407
|
-
|
|
5631
|
+
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
5632
|
+
let spawnedTask = null;
|
|
5633
|
+
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
5634
|
+
spawnedTask = spawnNextRecurrence(task, d);
|
|
5635
|
+
}
|
|
5636
|
+
const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
|
|
5637
|
+
if (spawnedTask) {
|
|
5638
|
+
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
5639
|
+
}
|
|
5408
5640
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
5409
5641
|
}
|
|
5410
5642
|
function lockTask(id, agentId, db) {
|
|
@@ -5467,51 +5699,428 @@ function getTaskDependencies(taskId, db) {
|
|
|
5467
5699
|
const d = db || getDatabase();
|
|
5468
5700
|
return d.query("SELECT * FROM task_dependencies WHERE task_id = ?").all(taskId);
|
|
5469
5701
|
}
|
|
5470
|
-
function
|
|
5471
|
-
const
|
|
5472
|
-
const
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5702
|
+
function cloneTask(taskId, overrides, db) {
|
|
5703
|
+
const d = db || getDatabase();
|
|
5704
|
+
const source = getTask(taskId, d);
|
|
5705
|
+
if (!source)
|
|
5706
|
+
throw new TaskNotFoundError(taskId);
|
|
5707
|
+
const input = {
|
|
5708
|
+
title: overrides?.title ?? source.title,
|
|
5709
|
+
description: overrides?.description ?? source.description ?? undefined,
|
|
5710
|
+
priority: overrides?.priority ?? source.priority,
|
|
5711
|
+
project_id: overrides?.project_id ?? source.project_id ?? undefined,
|
|
5712
|
+
parent_id: overrides?.parent_id ?? source.parent_id ?? undefined,
|
|
5713
|
+
plan_id: overrides?.plan_id ?? source.plan_id ?? undefined,
|
|
5714
|
+
task_list_id: overrides?.task_list_id ?? source.task_list_id ?? undefined,
|
|
5715
|
+
status: overrides?.status ?? "pending",
|
|
5716
|
+
agent_id: overrides?.agent_id ?? source.agent_id ?? undefined,
|
|
5717
|
+
assigned_to: overrides?.assigned_to ?? source.assigned_to ?? undefined,
|
|
5718
|
+
tags: overrides?.tags ?? source.tags,
|
|
5719
|
+
metadata: overrides?.metadata ?? source.metadata,
|
|
5720
|
+
estimated_minutes: overrides?.estimated_minutes ?? source.estimated_minutes ?? undefined,
|
|
5721
|
+
recurrence_rule: overrides?.recurrence_rule ?? source.recurrence_rule ?? undefined
|
|
5722
|
+
};
|
|
5723
|
+
return createTask(input, d);
|
|
5486
5724
|
}
|
|
5487
|
-
|
|
5488
|
-
// src/db/comments.ts
|
|
5489
|
-
init_database();
|
|
5490
|
-
function addComment(input, db) {
|
|
5725
|
+
function getTaskGraph(taskId, direction = "both", db) {
|
|
5491
5726
|
const d = db || getDatabase();
|
|
5492
|
-
|
|
5493
|
-
|
|
5727
|
+
const task = getTask(taskId, d);
|
|
5728
|
+
if (!task)
|
|
5729
|
+
throw new TaskNotFoundError(taskId);
|
|
5730
|
+
function toNode(t) {
|
|
5731
|
+
const deps = getTaskDependencies(t.id, d);
|
|
5732
|
+
const hasUnfinishedDeps = deps.some((dep) => {
|
|
5733
|
+
const depTask = getTask(dep.depends_on, d);
|
|
5734
|
+
return depTask && depTask.status !== "completed";
|
|
5735
|
+
});
|
|
5736
|
+
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
5737
|
+
}
|
|
5738
|
+
function buildUp(id, visited) {
|
|
5739
|
+
if (visited.has(id))
|
|
5740
|
+
return [];
|
|
5741
|
+
visited.add(id);
|
|
5742
|
+
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
5743
|
+
return deps.map((dep) => {
|
|
5744
|
+
const depTask = getTask(dep.depends_on, d);
|
|
5745
|
+
if (!depTask)
|
|
5746
|
+
return null;
|
|
5747
|
+
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
5748
|
+
}).filter(Boolean);
|
|
5749
|
+
}
|
|
5750
|
+
function buildDown(id, visited) {
|
|
5751
|
+
if (visited.has(id))
|
|
5752
|
+
return [];
|
|
5753
|
+
visited.add(id);
|
|
5754
|
+
const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
|
|
5755
|
+
return dependents.map((dep) => {
|
|
5756
|
+
const depTask = getTask(dep.task_id, d);
|
|
5757
|
+
if (!depTask)
|
|
5758
|
+
return null;
|
|
5759
|
+
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
5760
|
+
}).filter(Boolean);
|
|
5494
5761
|
}
|
|
5495
|
-
const
|
|
5496
|
-
const
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
id,
|
|
5500
|
-
input.task_id,
|
|
5501
|
-
input.agent_id || null,
|
|
5502
|
-
input.session_id || null,
|
|
5503
|
-
input.content,
|
|
5504
|
-
timestamp
|
|
5505
|
-
]);
|
|
5506
|
-
return getComment(id, d);
|
|
5762
|
+
const rootNode = toNode(task);
|
|
5763
|
+
const depends_on = direction === "up" || direction === "both" ? buildUp(taskId, new Set) : [];
|
|
5764
|
+
const blocks = direction === "down" || direction === "both" ? buildDown(taskId, new Set) : [];
|
|
5765
|
+
return { task: rootNode, depends_on, blocks };
|
|
5507
5766
|
}
|
|
5508
|
-
function
|
|
5767
|
+
function moveTask(taskId, target, db) {
|
|
5509
5768
|
const d = db || getDatabase();
|
|
5510
|
-
|
|
5769
|
+
const task = getTask(taskId, d);
|
|
5770
|
+
if (!task)
|
|
5771
|
+
throw new TaskNotFoundError(taskId);
|
|
5772
|
+
const sets = ["updated_at = ?", "version = version + 1"];
|
|
5773
|
+
const params = [now()];
|
|
5774
|
+
if (target.task_list_id !== undefined) {
|
|
5775
|
+
sets.push("task_list_id = ?");
|
|
5776
|
+
params.push(target.task_list_id);
|
|
5777
|
+
}
|
|
5778
|
+
if (target.project_id !== undefined) {
|
|
5779
|
+
sets.push("project_id = ?");
|
|
5780
|
+
params.push(target.project_id);
|
|
5781
|
+
}
|
|
5782
|
+
if (target.plan_id !== undefined) {
|
|
5783
|
+
sets.push("plan_id = ?");
|
|
5784
|
+
params.push(target.plan_id);
|
|
5785
|
+
}
|
|
5786
|
+
params.push(taskId);
|
|
5787
|
+
d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5788
|
+
return getTask(taskId, d);
|
|
5511
5789
|
}
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5790
|
+
function spawnNextRecurrence(completedTask, db) {
|
|
5791
|
+
const dueAt = nextOccurrence(completedTask.recurrence_rule, new Date);
|
|
5792
|
+
let title = completedTask.title;
|
|
5793
|
+
if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
|
|
5794
|
+
title = title.slice(completedTask.short_id.length + 2);
|
|
5795
|
+
}
|
|
5796
|
+
const recurrenceParentId = completedTask.recurrence_parent_id || completedTask.id;
|
|
5797
|
+
return createTask({
|
|
5798
|
+
title,
|
|
5799
|
+
description: completedTask.description ?? undefined,
|
|
5800
|
+
priority: completedTask.priority,
|
|
5801
|
+
project_id: completedTask.project_id ?? undefined,
|
|
5802
|
+
task_list_id: completedTask.task_list_id ?? undefined,
|
|
5803
|
+
plan_id: completedTask.plan_id ?? undefined,
|
|
5804
|
+
assigned_to: completedTask.assigned_to ?? undefined,
|
|
5805
|
+
tags: completedTask.tags,
|
|
5806
|
+
metadata: completedTask.metadata,
|
|
5807
|
+
estimated_minutes: completedTask.estimated_minutes ?? undefined,
|
|
5808
|
+
recurrence_rule: completedTask.recurrence_rule,
|
|
5809
|
+
recurrence_parent_id: recurrenceParentId,
|
|
5810
|
+
due_at: dueAt
|
|
5811
|
+
}, db);
|
|
5812
|
+
}
|
|
5813
|
+
function claimNextTask(agentId, filters, db) {
|
|
5814
|
+
const d = db || getDatabase();
|
|
5815
|
+
const tx = d.transaction(() => {
|
|
5816
|
+
const task = getNextTask(agentId, filters, d);
|
|
5817
|
+
if (!task)
|
|
5818
|
+
return null;
|
|
5819
|
+
return startTask(task.id, agentId, d);
|
|
5820
|
+
});
|
|
5821
|
+
return tx();
|
|
5822
|
+
}
|
|
5823
|
+
function getNextTask(agentId, filters, db) {
|
|
5824
|
+
const d = db || getDatabase();
|
|
5825
|
+
clearExpiredLocks(d);
|
|
5826
|
+
const conditions = ["status = 'pending'", "(locked_by IS NULL OR locked_at < ?)"];
|
|
5827
|
+
const params = [lockExpiryCutoff()];
|
|
5828
|
+
if (filters?.project_id) {
|
|
5829
|
+
conditions.push("project_id = ?");
|
|
5830
|
+
params.push(filters.project_id);
|
|
5831
|
+
}
|
|
5832
|
+
if (filters?.task_list_id) {
|
|
5833
|
+
conditions.push("task_list_id = ?");
|
|
5834
|
+
params.push(filters.task_list_id);
|
|
5835
|
+
}
|
|
5836
|
+
if (filters?.plan_id) {
|
|
5837
|
+
conditions.push("plan_id = ?");
|
|
5838
|
+
params.push(filters.plan_id);
|
|
5839
|
+
}
|
|
5840
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
5841
|
+
const placeholders = filters.tags.map(() => "?").join(",");
|
|
5842
|
+
conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
|
|
5843
|
+
params.push(...filters.tags);
|
|
5844
|
+
}
|
|
5845
|
+
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')");
|
|
5846
|
+
const where = conditions.join(" AND ");
|
|
5847
|
+
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
5848
|
+
if (agentId) {
|
|
5849
|
+
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
5850
|
+
params.push(agentId);
|
|
5851
|
+
}
|
|
5852
|
+
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`;
|
|
5853
|
+
const row = d.query(sql).get(...params);
|
|
5854
|
+
return row ? rowToTask(row) : null;
|
|
5855
|
+
}
|
|
5856
|
+
function getActiveWork(filters, db) {
|
|
5857
|
+
const d = db || getDatabase();
|
|
5858
|
+
clearExpiredLocks(d);
|
|
5859
|
+
const conditions = ["status = 'in_progress'"];
|
|
5860
|
+
const params = [];
|
|
5861
|
+
if (filters?.project_id) {
|
|
5862
|
+
conditions.push("project_id = ?");
|
|
5863
|
+
params.push(filters.project_id);
|
|
5864
|
+
}
|
|
5865
|
+
if (filters?.task_list_id) {
|
|
5866
|
+
conditions.push("task_list_id = ?");
|
|
5867
|
+
params.push(filters.task_list_id);
|
|
5868
|
+
}
|
|
5869
|
+
const where = conditions.join(" AND ");
|
|
5870
|
+
const rows = d.query(`SELECT id, short_id, title, priority, assigned_to, locked_by, locked_at, updated_at FROM tasks WHERE ${where} ORDER BY
|
|
5871
|
+
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
5872
|
+
updated_at DESC`).all(...params);
|
|
5873
|
+
return rows;
|
|
5874
|
+
}
|
|
5875
|
+
function getTasksChangedSince(since, filters, db) {
|
|
5876
|
+
const d = db || getDatabase();
|
|
5877
|
+
const conditions = ["updated_at > ?"];
|
|
5878
|
+
const params = [since];
|
|
5879
|
+
if (filters?.project_id) {
|
|
5880
|
+
conditions.push("project_id = ?");
|
|
5881
|
+
params.push(filters.project_id);
|
|
5882
|
+
}
|
|
5883
|
+
if (filters?.task_list_id) {
|
|
5884
|
+
conditions.push("task_list_id = ?");
|
|
5885
|
+
params.push(filters.task_list_id);
|
|
5886
|
+
}
|
|
5887
|
+
const where = conditions.join(" AND ");
|
|
5888
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at DESC`).all(...params);
|
|
5889
|
+
return rows.map(rowToTask);
|
|
5890
|
+
}
|
|
5891
|
+
function failTask(id, agentId, reason, options, db) {
|
|
5892
|
+
const d = db || getDatabase();
|
|
5893
|
+
const task = getTask(id, d);
|
|
5894
|
+
if (!task)
|
|
5895
|
+
throw new TaskNotFoundError(id);
|
|
5896
|
+
const meta = {
|
|
5897
|
+
...task.metadata,
|
|
5898
|
+
_failure: {
|
|
5899
|
+
reason: reason || "Unknown failure",
|
|
5900
|
+
error_code: options?.error_code || null,
|
|
5901
|
+
failed_by: agentId || null,
|
|
5902
|
+
failed_at: now(),
|
|
5903
|
+
retry_requested: options?.retry || false
|
|
5904
|
+
}
|
|
5905
|
+
};
|
|
5906
|
+
const timestamp = now();
|
|
5907
|
+
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
5908
|
+
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
5909
|
+
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
5910
|
+
dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
5911
|
+
const failedTask = {
|
|
5912
|
+
...task,
|
|
5913
|
+
status: "failed",
|
|
5914
|
+
locked_by: null,
|
|
5915
|
+
locked_at: null,
|
|
5916
|
+
metadata: meta,
|
|
5917
|
+
version: task.version + 1,
|
|
5918
|
+
updated_at: timestamp
|
|
5919
|
+
};
|
|
5920
|
+
let retryTask;
|
|
5921
|
+
if (options?.retry) {
|
|
5922
|
+
let title = task.title;
|
|
5923
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
5924
|
+
title = title.slice(task.short_id.length + 2);
|
|
5925
|
+
}
|
|
5926
|
+
retryTask = createTask({
|
|
5927
|
+
title,
|
|
5928
|
+
description: task.description ?? undefined,
|
|
5929
|
+
priority: task.priority,
|
|
5930
|
+
project_id: task.project_id ?? undefined,
|
|
5931
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
5932
|
+
plan_id: task.plan_id ?? undefined,
|
|
5933
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
5934
|
+
tags: task.tags,
|
|
5935
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
|
|
5936
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
5937
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
5938
|
+
due_at: options.retry_after || task.due_at || undefined
|
|
5939
|
+
}, d);
|
|
5940
|
+
}
|
|
5941
|
+
return { task: failedTask, retryTask };
|
|
5942
|
+
}
|
|
5943
|
+
function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
5944
|
+
const d = db || getDatabase();
|
|
5945
|
+
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
5946
|
+
const conditions = [
|
|
5947
|
+
"status = 'in_progress'",
|
|
5948
|
+
"(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
|
|
5949
|
+
];
|
|
5950
|
+
const params = [cutoff, cutoff];
|
|
5951
|
+
if (filters?.project_id) {
|
|
5952
|
+
conditions.push("project_id = ?");
|
|
5953
|
+
params.push(filters.project_id);
|
|
5954
|
+
}
|
|
5955
|
+
if (filters?.task_list_id) {
|
|
5956
|
+
conditions.push("task_list_id = ?");
|
|
5957
|
+
params.push(filters.task_list_id);
|
|
5958
|
+
}
|
|
5959
|
+
const where = conditions.join(" AND ");
|
|
5960
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
5961
|
+
return rows.map(rowToTask);
|
|
5962
|
+
}
|
|
5963
|
+
function getStatus(filters, agentId, db) {
|
|
5964
|
+
const d = db || getDatabase();
|
|
5965
|
+
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
5966
|
+
const in_progress = countTasks({ ...filters, status: "in_progress" }, d);
|
|
5967
|
+
const completed = countTasks({ ...filters, status: "completed" }, d);
|
|
5968
|
+
const total = countTasks(filters || {}, d);
|
|
5969
|
+
const active_work = getActiveWork(filters, d);
|
|
5970
|
+
const next_task = getNextTask(agentId, filters, d);
|
|
5971
|
+
const stale = getStaleTasks(30, filters, d);
|
|
5972
|
+
const conditions = ["recurrence_rule IS NOT NULL", "status = 'pending'", "due_at < ?"];
|
|
5973
|
+
const params = [now()];
|
|
5974
|
+
if (filters?.project_id) {
|
|
5975
|
+
conditions.push("project_id = ?");
|
|
5976
|
+
params.push(filters.project_id);
|
|
5977
|
+
}
|
|
5978
|
+
if (filters?.task_list_id) {
|
|
5979
|
+
conditions.push("task_list_id = ?");
|
|
5980
|
+
params.push(filters.task_list_id);
|
|
5981
|
+
}
|
|
5982
|
+
const overdueRow = d.query(`SELECT COUNT(*) as count FROM tasks WHERE ${conditions.join(" AND ")}`).get(...params);
|
|
5983
|
+
return {
|
|
5984
|
+
pending,
|
|
5985
|
+
in_progress,
|
|
5986
|
+
completed,
|
|
5987
|
+
total,
|
|
5988
|
+
active_work,
|
|
5989
|
+
next_task,
|
|
5990
|
+
stale_count: stale.length,
|
|
5991
|
+
overdue_recurring: overdueRow.count
|
|
5992
|
+
};
|
|
5993
|
+
}
|
|
5994
|
+
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
5995
|
+
const visited = new Set;
|
|
5996
|
+
const queue = [dependsOn];
|
|
5997
|
+
while (queue.length > 0) {
|
|
5998
|
+
const current = queue.shift();
|
|
5999
|
+
if (current === taskId)
|
|
6000
|
+
return true;
|
|
6001
|
+
if (visited.has(current))
|
|
6002
|
+
continue;
|
|
6003
|
+
visited.add(current);
|
|
6004
|
+
const deps = db.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(current);
|
|
6005
|
+
for (const dep of deps) {
|
|
6006
|
+
queue.push(dep.depends_on);
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
6009
|
+
return false;
|
|
6010
|
+
}
|
|
6011
|
+
function getTaskStats(filters, db) {
|
|
6012
|
+
const d = db || getDatabase();
|
|
6013
|
+
const conditions = [];
|
|
6014
|
+
const params = [];
|
|
6015
|
+
if (filters?.project_id) {
|
|
6016
|
+
conditions.push("project_id = ?");
|
|
6017
|
+
params.push(filters.project_id);
|
|
6018
|
+
}
|
|
6019
|
+
if (filters?.task_list_id) {
|
|
6020
|
+
conditions.push("task_list_id = ?");
|
|
6021
|
+
params.push(filters.task_list_id);
|
|
6022
|
+
}
|
|
6023
|
+
if (filters?.agent_id) {
|
|
6024
|
+
conditions.push("(agent_id = ? OR assigned_to = ?)");
|
|
6025
|
+
params.push(filters.agent_id, filters.agent_id);
|
|
6026
|
+
}
|
|
6027
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
6028
|
+
const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
6029
|
+
const statusRows = d.query(`SELECT status, COUNT(*) as count FROM tasks ${where} GROUP BY status`).all(...params);
|
|
6030
|
+
const by_status = {};
|
|
6031
|
+
for (const r of statusRows)
|
|
6032
|
+
by_status[r.status] = r.count;
|
|
6033
|
+
const priorityRows = d.query(`SELECT priority, COUNT(*) as count FROM tasks ${where} GROUP BY priority`).all(...params);
|
|
6034
|
+
const by_priority = {};
|
|
6035
|
+
for (const r of priorityRows)
|
|
6036
|
+
by_priority[r.priority] = r.count;
|
|
6037
|
+
const agentRows = d.query(`SELECT COALESCE(assigned_to, agent_id, 'unassigned') as agent, COUNT(*) as count FROM tasks ${where} GROUP BY agent`).all(...params);
|
|
6038
|
+
const by_agent = {};
|
|
6039
|
+
for (const r of agentRows)
|
|
6040
|
+
by_agent[r.agent] = r.count;
|
|
6041
|
+
const completed = by_status["completed"] || 0;
|
|
6042
|
+
const completion_rate = totalRow.count > 0 ? Math.round(completed / totalRow.count * 100) : 0;
|
|
6043
|
+
return { total: totalRow.count, by_status, by_priority, completion_rate, by_agent };
|
|
6044
|
+
}
|
|
6045
|
+
function bulkCreateTasks(inputs, db) {
|
|
6046
|
+
const d = db || getDatabase();
|
|
6047
|
+
const tempIdToRealId = new Map;
|
|
6048
|
+
const created = [];
|
|
6049
|
+
const tx = d.transaction(() => {
|
|
6050
|
+
for (const input of inputs) {
|
|
6051
|
+
const { temp_id, depends_on_temp_ids: _deps, ...createInput } = input;
|
|
6052
|
+
const task = createTask(createInput, d);
|
|
6053
|
+
if (temp_id)
|
|
6054
|
+
tempIdToRealId.set(temp_id, task.id);
|
|
6055
|
+
created.push({ temp_id: temp_id || null, id: task.id, short_id: task.short_id, title: task.title });
|
|
6056
|
+
}
|
|
6057
|
+
for (const input of inputs) {
|
|
6058
|
+
if (input.depends_on_temp_ids && input.depends_on_temp_ids.length > 0) {
|
|
6059
|
+
const taskId = input.temp_id ? tempIdToRealId.get(input.temp_id) : null;
|
|
6060
|
+
if (!taskId)
|
|
6061
|
+
continue;
|
|
6062
|
+
for (const depTempId of input.depends_on_temp_ids) {
|
|
6063
|
+
const depRealId = tempIdToRealId.get(depTempId);
|
|
6064
|
+
if (depRealId) {
|
|
6065
|
+
addDependency(taskId, depRealId, d);
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
});
|
|
6071
|
+
tx();
|
|
6072
|
+
return { created };
|
|
6073
|
+
}
|
|
6074
|
+
function bulkUpdateTasks(taskIds, updates, db) {
|
|
6075
|
+
const d = db || getDatabase();
|
|
6076
|
+
let updated = 0;
|
|
6077
|
+
const failed = [];
|
|
6078
|
+
const tx = d.transaction(() => {
|
|
6079
|
+
for (const id of taskIds) {
|
|
6080
|
+
try {
|
|
6081
|
+
const task = getTask(id, d);
|
|
6082
|
+
if (!task) {
|
|
6083
|
+
failed.push({ id, error: "Task not found" });
|
|
6084
|
+
continue;
|
|
6085
|
+
}
|
|
6086
|
+
updateTask(id, { ...updates, version: task.version }, d);
|
|
6087
|
+
updated++;
|
|
6088
|
+
} catch (e) {
|
|
6089
|
+
failed.push({ id, error: e instanceof Error ? e.message : String(e) });
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
6092
|
+
});
|
|
6093
|
+
tx();
|
|
6094
|
+
return { updated, failed };
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6097
|
+
// src/db/comments.ts
|
|
6098
|
+
init_database();
|
|
6099
|
+
function addComment(input, db) {
|
|
6100
|
+
const d = db || getDatabase();
|
|
6101
|
+
if (!getTask(input.task_id, d)) {
|
|
6102
|
+
throw new TaskNotFoundError(input.task_id);
|
|
6103
|
+
}
|
|
6104
|
+
const id = uuid();
|
|
6105
|
+
const timestamp = now();
|
|
6106
|
+
d.run(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, created_at)
|
|
6107
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
6108
|
+
id,
|
|
6109
|
+
input.task_id,
|
|
6110
|
+
input.agent_id || null,
|
|
6111
|
+
input.session_id || null,
|
|
6112
|
+
input.content,
|
|
6113
|
+
timestamp
|
|
6114
|
+
]);
|
|
6115
|
+
return getComment(id, d);
|
|
6116
|
+
}
|
|
6117
|
+
function getComment(id, db) {
|
|
6118
|
+
const d = db || getDatabase();
|
|
6119
|
+
return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
// src/db/plans.ts
|
|
6123
|
+
init_database();
|
|
5515
6124
|
function createPlan(input, db) {
|
|
5516
6125
|
const d = db || getDatabase();
|
|
5517
6126
|
const id = uuid();
|
|
@@ -5657,19 +6266,64 @@ function rowToTask2(row) {
|
|
|
5657
6266
|
requires_approval: Boolean(row.requires_approval)
|
|
5658
6267
|
};
|
|
5659
6268
|
}
|
|
5660
|
-
function searchTasks(
|
|
6269
|
+
function searchTasks(options, projectId, taskListId, db) {
|
|
6270
|
+
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
5661
6271
|
const d = db || getDatabase();
|
|
5662
6272
|
clearExpiredLocks(d);
|
|
5663
|
-
const pattern = `%${query}%`;
|
|
6273
|
+
const pattern = `%${opts.query}%`;
|
|
5664
6274
|
let sql = `SELECT * FROM tasks WHERE (title LIKE ? OR description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = tasks.id AND tag LIKE ?))`;
|
|
5665
6275
|
const params = [pattern, pattern, pattern];
|
|
5666
|
-
if (
|
|
6276
|
+
if (opts.project_id) {
|
|
5667
6277
|
sql += " AND project_id = ?";
|
|
5668
|
-
params.push(
|
|
6278
|
+
params.push(opts.project_id);
|
|
5669
6279
|
}
|
|
5670
|
-
if (
|
|
6280
|
+
if (opts.task_list_id) {
|
|
5671
6281
|
sql += " AND task_list_id = ?";
|
|
5672
|
-
params.push(
|
|
6282
|
+
params.push(opts.task_list_id);
|
|
6283
|
+
}
|
|
6284
|
+
if (opts.status) {
|
|
6285
|
+
if (Array.isArray(opts.status)) {
|
|
6286
|
+
sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
|
|
6287
|
+
params.push(...opts.status);
|
|
6288
|
+
} else {
|
|
6289
|
+
sql += " AND status = ?";
|
|
6290
|
+
params.push(opts.status);
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
6293
|
+
if (opts.priority) {
|
|
6294
|
+
if (Array.isArray(opts.priority)) {
|
|
6295
|
+
sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
6296
|
+
params.push(...opts.priority);
|
|
6297
|
+
} else {
|
|
6298
|
+
sql += " AND priority = ?";
|
|
6299
|
+
params.push(opts.priority);
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
if (opts.assigned_to) {
|
|
6303
|
+
sql += " AND assigned_to = ?";
|
|
6304
|
+
params.push(opts.assigned_to);
|
|
6305
|
+
}
|
|
6306
|
+
if (opts.agent_id) {
|
|
6307
|
+
sql += " AND agent_id = ?";
|
|
6308
|
+
params.push(opts.agent_id);
|
|
6309
|
+
}
|
|
6310
|
+
if (opts.created_after) {
|
|
6311
|
+
sql += " AND created_at > ?";
|
|
6312
|
+
params.push(opts.created_after);
|
|
6313
|
+
}
|
|
6314
|
+
if (opts.updated_after) {
|
|
6315
|
+
sql += " AND updated_at > ?";
|
|
6316
|
+
params.push(opts.updated_after);
|
|
6317
|
+
}
|
|
6318
|
+
if (opts.has_dependencies === true) {
|
|
6319
|
+
sql += " AND id IN (SELECT task_id FROM task_dependencies)";
|
|
6320
|
+
} else if (opts.has_dependencies === false) {
|
|
6321
|
+
sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
6322
|
+
}
|
|
6323
|
+
if (opts.is_blocked === true) {
|
|
6324
|
+
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')";
|
|
6325
|
+
} else if (opts.is_blocked === false) {
|
|
6326
|
+
sql += " AND id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
5673
6327
|
}
|
|
5674
6328
|
sql += ` ORDER BY
|
|
5675
6329
|
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
@@ -6199,28 +6853,41 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
6199
6853
|
init_database();
|
|
6200
6854
|
var server = new McpServer({
|
|
6201
6855
|
name: "todos",
|
|
6202
|
-
version: "0.9.
|
|
6856
|
+
version: "0.9.35"
|
|
6203
6857
|
});
|
|
6204
6858
|
function formatError(error) {
|
|
6205
|
-
if (error instanceof VersionConflictError)
|
|
6206
|
-
return
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
if (error instanceof
|
|
6212
|
-
return
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6859
|
+
if (error instanceof VersionConflictError) {
|
|
6860
|
+
return JSON.stringify({ code: VersionConflictError.code, message: error.message, suggestion: VersionConflictError.suggestion });
|
|
6861
|
+
}
|
|
6862
|
+
if (error instanceof TaskNotFoundError) {
|
|
6863
|
+
return JSON.stringify({ code: TaskNotFoundError.code, message: error.message, suggestion: TaskNotFoundError.suggestion });
|
|
6864
|
+
}
|
|
6865
|
+
if (error instanceof ProjectNotFoundError) {
|
|
6866
|
+
return JSON.stringify({ code: ProjectNotFoundError.code, message: error.message, suggestion: ProjectNotFoundError.suggestion });
|
|
6867
|
+
}
|
|
6868
|
+
if (error instanceof PlanNotFoundError) {
|
|
6869
|
+
return JSON.stringify({ code: PlanNotFoundError.code, message: error.message, suggestion: PlanNotFoundError.suggestion });
|
|
6870
|
+
}
|
|
6871
|
+
if (error instanceof TaskListNotFoundError) {
|
|
6872
|
+
return JSON.stringify({ code: TaskListNotFoundError.code, message: error.message, suggestion: TaskListNotFoundError.suggestion });
|
|
6873
|
+
}
|
|
6874
|
+
if (error instanceof LockError) {
|
|
6875
|
+
return JSON.stringify({ code: LockError.code, message: error.message, suggestion: LockError.suggestion });
|
|
6876
|
+
}
|
|
6877
|
+
if (error instanceof AgentNotFoundError) {
|
|
6878
|
+
return JSON.stringify({ code: AgentNotFoundError.code, message: error.message, suggestion: AgentNotFoundError.suggestion });
|
|
6879
|
+
}
|
|
6880
|
+
if (error instanceof DependencyCycleError) {
|
|
6881
|
+
return JSON.stringify({ code: DependencyCycleError.code, message: error.message, suggestion: DependencyCycleError.suggestion });
|
|
6882
|
+
}
|
|
6217
6883
|
if (error instanceof CompletionGuardError) {
|
|
6218
|
-
const retry = error.retryAfterSeconds ?
|
|
6219
|
-
return
|
|
6884
|
+
const retry = error.retryAfterSeconds ? { retryAfterSeconds: error.retryAfterSeconds } : {};
|
|
6885
|
+
return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
|
|
6220
6886
|
}
|
|
6221
|
-
if (error instanceof Error)
|
|
6222
|
-
return error.message;
|
|
6223
|
-
|
|
6887
|
+
if (error instanceof Error) {
|
|
6888
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: error.message });
|
|
6889
|
+
}
|
|
6890
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
|
|
6224
6891
|
}
|
|
6225
6892
|
function resolveId(partialId, table = "tasks") {
|
|
6226
6893
|
const db = getDatabase();
|
|
@@ -6243,7 +6910,8 @@ function formatTask(task) {
|
|
|
6243
6910
|
const id = task.short_id || task.id.slice(0, 8);
|
|
6244
6911
|
const assigned = task.assigned_to ? ` -> ${task.assigned_to}` : "";
|
|
6245
6912
|
const lock = task.locked_by ? ` [locked:${task.locked_by}]` : "";
|
|
6246
|
-
|
|
6913
|
+
const recur = task.recurrence_rule ? ` [\u21BB]` : "";
|
|
6914
|
+
return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
|
|
6247
6915
|
}
|
|
6248
6916
|
function formatTaskDetail(task) {
|
|
6249
6917
|
const parts = [
|
|
@@ -6268,6 +6936,10 @@ function formatTaskDetail(task) {
|
|
|
6268
6936
|
parts.push(`Plan: ${task.plan_id}`);
|
|
6269
6937
|
if (task.tags.length > 0)
|
|
6270
6938
|
parts.push(`Tags: ${task.tags.join(", ")}`);
|
|
6939
|
+
if (task.recurrence_rule)
|
|
6940
|
+
parts.push(`Recurrence: ${task.recurrence_rule}`);
|
|
6941
|
+
if (task.recurrence_parent_id)
|
|
6942
|
+
parts.push(`Recurrence parent: ${task.recurrence_parent_id}`);
|
|
6271
6943
|
parts.push(`Version: ${task.version}`);
|
|
6272
6944
|
parts.push(`Created: ${task.created_at}`);
|
|
6273
6945
|
if (task.completed_at)
|
|
@@ -6291,7 +6963,8 @@ server.tool("create_task", "Create a new task", {
|
|
|
6291
6963
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
6292
6964
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
6293
6965
|
estimated_minutes: exports_external.number().optional(),
|
|
6294
|
-
requires_approval: exports_external.boolean().optional()
|
|
6966
|
+
requires_approval: exports_external.boolean().optional(),
|
|
6967
|
+
recurrence_rule: exports_external.string().optional()
|
|
6295
6968
|
}, async (params) => {
|
|
6296
6969
|
try {
|
|
6297
6970
|
const resolved = { ...params };
|
|
@@ -6309,7 +6982,7 @@ server.tool("create_task", "Create a new task", {
|
|
|
6309
6982
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6310
6983
|
}
|
|
6311
6984
|
});
|
|
6312
|
-
server.tool("list_tasks", "List tasks with optional filters", {
|
|
6985
|
+
server.tool("list_tasks", "List tasks with optional filters and pagination.", {
|
|
6313
6986
|
project_id: exports_external.string().optional(),
|
|
6314
6987
|
status: exports_external.union([
|
|
6315
6988
|
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
@@ -6322,7 +6995,10 @@ server.tool("list_tasks", "List tasks with optional filters", {
|
|
|
6322
6995
|
assigned_to: exports_external.string().optional(),
|
|
6323
6996
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
6324
6997
|
plan_id: exports_external.string().optional(),
|
|
6325
|
-
task_list_id: exports_external.string().optional()
|
|
6998
|
+
task_list_id: exports_external.string().optional(),
|
|
6999
|
+
has_recurrence: exports_external.boolean().optional(),
|
|
7000
|
+
limit: exports_external.number().optional(),
|
|
7001
|
+
offset: exports_external.number().optional()
|
|
6326
7002
|
}, async (params) => {
|
|
6327
7003
|
try {
|
|
6328
7004
|
const resolved = { ...params };
|
|
@@ -6333,8 +7009,10 @@ server.tool("list_tasks", "List tasks with optional filters", {
|
|
|
6333
7009
|
if (resolved.task_list_id)
|
|
6334
7010
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
6335
7011
|
const tasks = listTasks(resolved);
|
|
7012
|
+
const { limit: _limit, offset: _offset, ...countFilter } = resolved;
|
|
7013
|
+
const total = countTasks(countFilter);
|
|
6336
7014
|
if (tasks.length === 0) {
|
|
6337
|
-
return { content: [{ type: "text", text: "No tasks found." }] };
|
|
7015
|
+
return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
|
|
6338
7016
|
}
|
|
6339
7017
|
const text = tasks.map((t) => {
|
|
6340
7018
|
const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
|
|
@@ -6342,13 +7020,15 @@ server.tool("list_tasks", "List tasks with optional filters", {
|
|
|
6342
7020
|
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}`;
|
|
6343
7021
|
}).join(`
|
|
6344
7022
|
`);
|
|
7023
|
+
const pagination = resolved.limit ? `
|
|
7024
|
+
(showing ${tasks.length} of ${total}, offset: ${resolved.offset || 0})` : "";
|
|
6345
7025
|
return { content: [{ type: "text", text: `${tasks.length} task(s):
|
|
6346
|
-
${text}` }] };
|
|
7026
|
+
${text}${pagination}` }] };
|
|
6347
7027
|
} catch (e) {
|
|
6348
7028
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6349
7029
|
}
|
|
6350
7030
|
});
|
|
6351
|
-
server.tool("get_task", "Get full task details with
|
|
7031
|
+
server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
|
|
6352
7032
|
id: exports_external.string()
|
|
6353
7033
|
}, async ({ id }) => {
|
|
6354
7034
|
try {
|
|
@@ -6417,7 +7097,7 @@ server.tool("update_task", "Update task fields. Version required for optimistic
|
|
|
6417
7097
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6418
7098
|
}
|
|
6419
7099
|
});
|
|
6420
|
-
server.tool("delete_task", "Delete a task permanently", {
|
|
7100
|
+
server.tool("delete_task", "Delete a task permanently. Subtasks cascade-deleted.", {
|
|
6421
7101
|
id: exports_external.string()
|
|
6422
7102
|
}, async ({ id }) => {
|
|
6423
7103
|
try {
|
|
@@ -6433,7 +7113,7 @@ server.tool("delete_task", "Delete a task permanently", {
|
|
|
6433
7113
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6434
7114
|
}
|
|
6435
7115
|
});
|
|
6436
|
-
server.tool("start_task", "Claim, lock, and set task
|
|
7116
|
+
server.tool("start_task", "Claim, lock, and set task to in_progress.", {
|
|
6437
7117
|
id: exports_external.string(),
|
|
6438
7118
|
agent_id: exports_external.string()
|
|
6439
7119
|
}, async ({ id, agent_id }) => {
|
|
@@ -6445,19 +7125,26 @@ server.tool("start_task", "Claim, lock, and set task status to in_progress.", {
|
|
|
6445
7125
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6446
7126
|
}
|
|
6447
7127
|
});
|
|
6448
|
-
server.tool("complete_task", "
|
|
7128
|
+
server.tool("complete_task", "Complete a task. For recurring tasks, auto-spawns next instance.", {
|
|
6449
7129
|
id: exports_external.string(),
|
|
6450
|
-
agent_id: exports_external.string().optional()
|
|
6451
|
-
|
|
7130
|
+
agent_id: exports_external.string().optional(),
|
|
7131
|
+
skip_recurrence: exports_external.boolean().optional()
|
|
7132
|
+
}, async ({ id, agent_id, skip_recurrence }) => {
|
|
6452
7133
|
try {
|
|
6453
7134
|
const resolvedId = resolveId(id);
|
|
6454
|
-
const task = completeTask(resolvedId, agent_id);
|
|
6455
|
-
|
|
7135
|
+
const task = completeTask(resolvedId, agent_id, undefined, { skip_recurrence });
|
|
7136
|
+
let text = `completed: ${formatTask(task)}`;
|
|
7137
|
+
if (task.metadata._next_recurrence) {
|
|
7138
|
+
const next = task.metadata._next_recurrence;
|
|
7139
|
+
text += `
|
|
7140
|
+
next: ${next.short_id || next.id.slice(0, 8)} due ${next.due_at}`;
|
|
7141
|
+
}
|
|
7142
|
+
return { content: [{ type: "text", text }] };
|
|
6456
7143
|
} catch (e) {
|
|
6457
7144
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6458
7145
|
}
|
|
6459
7146
|
});
|
|
6460
|
-
server.tool("lock_task", "Acquire exclusive lock
|
|
7147
|
+
server.tool("lock_task", "Acquire exclusive lock. Expires after 30 min. Idempotent per agent.", {
|
|
6461
7148
|
id: exports_external.string(),
|
|
6462
7149
|
agent_id: exports_external.string()
|
|
6463
7150
|
}, async ({ id, agent_id }) => {
|
|
@@ -6472,7 +7159,7 @@ server.tool("lock_task", "Acquire exclusive lock on a task", {
|
|
|
6472
7159
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6473
7160
|
}
|
|
6474
7161
|
});
|
|
6475
|
-
server.tool("unlock_task", "Release exclusive lock on a task", {
|
|
7162
|
+
server.tool("unlock_task", "Release exclusive lock on a task.", {
|
|
6476
7163
|
id: exports_external.string(),
|
|
6477
7164
|
agent_id: exports_external.string().optional()
|
|
6478
7165
|
}, async ({ id, agent_id }) => {
|
|
@@ -6484,7 +7171,7 @@ server.tool("unlock_task", "Release exclusive lock on a task", {
|
|
|
6484
7171
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6485
7172
|
}
|
|
6486
7173
|
});
|
|
6487
|
-
server.tool("add_dependency", "Add a dependency
|
|
7174
|
+
server.tool("add_dependency", "Add a dependency. Prevents cycles via BFS detection.", {
|
|
6488
7175
|
task_id: exports_external.string(),
|
|
6489
7176
|
depends_on: exports_external.string()
|
|
6490
7177
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -6497,7 +7184,7 @@ server.tool("add_dependency", "Add a dependency: task_id depends on depends_on."
|
|
|
6497
7184
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6498
7185
|
}
|
|
6499
7186
|
});
|
|
6500
|
-
server.tool("remove_dependency", "Remove a dependency between tasks", {
|
|
7187
|
+
server.tool("remove_dependency", "Remove a dependency link between two tasks.", {
|
|
6501
7188
|
task_id: exports_external.string(),
|
|
6502
7189
|
depends_on: exports_external.string()
|
|
6503
7190
|
}, async ({ task_id, depends_on }) => {
|
|
@@ -6515,7 +7202,7 @@ server.tool("remove_dependency", "Remove a dependency between tasks", {
|
|
|
6515
7202
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6516
7203
|
}
|
|
6517
7204
|
});
|
|
6518
|
-
server.tool("add_comment", "Add a comment
|
|
7205
|
+
server.tool("add_comment", "Add a comment or note to a task. Comments are append-only.", {
|
|
6519
7206
|
task_id: exports_external.string(),
|
|
6520
7207
|
content: exports_external.string(),
|
|
6521
7208
|
agent_id: exports_external.string().optional(),
|
|
@@ -6546,7 +7233,7 @@ ${text}` }] };
|
|
|
6546
7233
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6547
7234
|
}
|
|
6548
7235
|
});
|
|
6549
|
-
server.tool("create_project", "Register a new project", {
|
|
7236
|
+
server.tool("create_project", "Register a new project with auto-generated task prefix.", {
|
|
6550
7237
|
name: exports_external.string(),
|
|
6551
7238
|
path: exports_external.string(),
|
|
6552
7239
|
description: exports_external.string().optional(),
|
|
@@ -6565,7 +7252,7 @@ server.tool("create_project", "Register a new project", {
|
|
|
6565
7252
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6566
7253
|
}
|
|
6567
7254
|
});
|
|
6568
|
-
server.tool("create_plan", "Create a
|
|
7255
|
+
server.tool("create_plan", "Create a plan to group related tasks.", {
|
|
6569
7256
|
name: exports_external.string(),
|
|
6570
7257
|
project_id: exports_external.string().optional(),
|
|
6571
7258
|
description: exports_external.string().optional(),
|
|
@@ -6590,7 +7277,7 @@ server.tool("create_plan", "Create a new plan", {
|
|
|
6590
7277
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6591
7278
|
}
|
|
6592
7279
|
});
|
|
6593
|
-
server.tool("list_plans", "List plans
|
|
7280
|
+
server.tool("list_plans", "List all plans, optionally filtered by project.", {
|
|
6594
7281
|
project_id: exports_external.string().optional()
|
|
6595
7282
|
}, async ({ project_id }) => {
|
|
6596
7283
|
try {
|
|
@@ -6610,7 +7297,7 @@ ${text}` }] };
|
|
|
6610
7297
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6611
7298
|
}
|
|
6612
7299
|
});
|
|
6613
|
-
server.tool("get_plan", "Get plan details", {
|
|
7300
|
+
server.tool("get_plan", "Get plan details including status and timestamps.", {
|
|
6614
7301
|
id: exports_external.string()
|
|
6615
7302
|
}, async ({ id }) => {
|
|
6616
7303
|
try {
|
|
@@ -6635,7 +7322,7 @@ server.tool("get_plan", "Get plan details", {
|
|
|
6635
7322
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6636
7323
|
}
|
|
6637
7324
|
});
|
|
6638
|
-
server.tool("update_plan", "Update
|
|
7325
|
+
server.tool("update_plan", "Update plan fields (name, description, status).", {
|
|
6639
7326
|
id: exports_external.string(),
|
|
6640
7327
|
name: exports_external.string().optional(),
|
|
6641
7328
|
description: exports_external.string().optional(),
|
|
@@ -6659,7 +7346,7 @@ server.tool("update_plan", "Update a plan", {
|
|
|
6659
7346
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6660
7347
|
}
|
|
6661
7348
|
});
|
|
6662
|
-
server.tool("delete_plan", "Delete a plan", {
|
|
7349
|
+
server.tool("delete_plan", "Delete a plan. Tasks in the plan are orphaned (not deleted).", {
|
|
6663
7350
|
id: exports_external.string()
|
|
6664
7351
|
}, async ({ id }) => {
|
|
6665
7352
|
try {
|
|
@@ -6675,15 +7362,34 @@ server.tool("delete_plan", "Delete a plan", {
|
|
|
6675
7362
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6676
7363
|
}
|
|
6677
7364
|
});
|
|
6678
|
-
server.tool("search_tasks", "Full-text search across
|
|
7365
|
+
server.tool("search_tasks", "Full-text search across tasks with filters.", {
|
|
6679
7366
|
query: exports_external.string(),
|
|
6680
7367
|
project_id: exports_external.string().optional(),
|
|
6681
|
-
task_list_id: exports_external.string().optional()
|
|
6682
|
-
|
|
7368
|
+
task_list_id: exports_external.string().optional(),
|
|
7369
|
+
status: exports_external.union([
|
|
7370
|
+
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
7371
|
+
exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))
|
|
7372
|
+
]).optional(),
|
|
7373
|
+
priority: exports_external.union([
|
|
7374
|
+
exports_external.enum(["low", "medium", "high", "critical"]),
|
|
7375
|
+
exports_external.array(exports_external.enum(["low", "medium", "high", "critical"]))
|
|
7376
|
+
]).optional(),
|
|
7377
|
+
assigned_to: exports_external.string().optional(),
|
|
7378
|
+
agent_id: exports_external.string().optional(),
|
|
7379
|
+
created_after: exports_external.string().optional(),
|
|
7380
|
+
updated_after: exports_external.string().optional(),
|
|
7381
|
+
has_dependencies: exports_external.boolean().optional(),
|
|
7382
|
+
is_blocked: exports_external.boolean().optional()
|
|
7383
|
+
}, async ({ query, project_id, task_list_id, ...filters }) => {
|
|
6683
7384
|
try {
|
|
6684
7385
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
6685
7386
|
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
6686
|
-
const tasks = searchTasks(
|
|
7387
|
+
const tasks = searchTasks({
|
|
7388
|
+
query,
|
|
7389
|
+
project_id: resolvedProjectId,
|
|
7390
|
+
task_list_id: resolvedTaskListId,
|
|
7391
|
+
...filters
|
|
7392
|
+
});
|
|
6687
7393
|
if (tasks.length === 0) {
|
|
6688
7394
|
return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
|
|
6689
7395
|
}
|
|
@@ -6695,7 +7401,7 @@ ${text}` }] };
|
|
|
6695
7401
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6696
7402
|
}
|
|
6697
7403
|
});
|
|
6698
|
-
server.tool("sync", "Sync tasks
|
|
7404
|
+
server.tool("sync", "Sync tasks between local DB and agent task list.", {
|
|
6699
7405
|
task_list_id: exports_external.string().optional(),
|
|
6700
7406
|
agent: exports_external.string().optional(),
|
|
6701
7407
|
all_agents: exports_external.boolean().optional(),
|
|
@@ -6743,7 +7449,7 @@ server.tool("sync", "Sync tasks with an agent task list.", {
|
|
|
6743
7449
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6744
7450
|
}
|
|
6745
7451
|
});
|
|
6746
|
-
server.tool("register_agent", "Register an agent (idempotent by name).", {
|
|
7452
|
+
server.tool("register_agent", "Register an agent (idempotent by name). Updates last_seen_at.", {
|
|
6747
7453
|
name: exports_external.string(),
|
|
6748
7454
|
description: exports_external.string().optional()
|
|
6749
7455
|
}, async ({ name, description }) => {
|
|
@@ -6780,7 +7486,7 @@ ${text}` }] };
|
|
|
6780
7486
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6781
7487
|
}
|
|
6782
7488
|
});
|
|
6783
|
-
server.tool("get_agent", "Get agent details by ID or name", {
|
|
7489
|
+
server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
|
|
6784
7490
|
id: exports_external.string().optional(),
|
|
6785
7491
|
name: exports_external.string().optional()
|
|
6786
7492
|
}, async ({ id, name }) => {
|
|
@@ -6809,9 +7515,9 @@ server.tool("get_agent", "Get agent details by ID or name", {
|
|
|
6809
7515
|
}
|
|
6810
7516
|
});
|
|
6811
7517
|
server.tool("rename_agent", "Rename an agent. Resolve by id or current name.", {
|
|
6812
|
-
id: exports_external.string().optional()
|
|
6813
|
-
name: exports_external.string().optional()
|
|
6814
|
-
new_name: exports_external.string()
|
|
7518
|
+
id: exports_external.string().optional(),
|
|
7519
|
+
name: exports_external.string().optional(),
|
|
7520
|
+
new_name: exports_external.string()
|
|
6815
7521
|
}, async ({ id, name, new_name }) => {
|
|
6816
7522
|
try {
|
|
6817
7523
|
if (!id && !name) {
|
|
@@ -6834,8 +7540,8 @@ ID: ${updated.id}`
|
|
|
6834
7540
|
}
|
|
6835
7541
|
});
|
|
6836
7542
|
server.tool("delete_agent", "Delete an agent permanently. Resolve by id or name.", {
|
|
6837
|
-
id: exports_external.string().optional()
|
|
6838
|
-
name: exports_external.string().optional()
|
|
7543
|
+
id: exports_external.string().optional(),
|
|
7544
|
+
name: exports_external.string().optional()
|
|
6839
7545
|
}, async ({ id, name }) => {
|
|
6840
7546
|
try {
|
|
6841
7547
|
if (!id && !name) {
|
|
@@ -6857,7 +7563,7 @@ server.tool("delete_agent", "Delete an agent permanently. Resolve by id or name.
|
|
|
6857
7563
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6858
7564
|
}
|
|
6859
7565
|
});
|
|
6860
|
-
server.tool("create_task_list", "Create a
|
|
7566
|
+
server.tool("create_task_list", "Create a task list container for organizing tasks.", {
|
|
6861
7567
|
name: exports_external.string(),
|
|
6862
7568
|
slug: exports_external.string().optional(),
|
|
6863
7569
|
project_id: exports_external.string().optional(),
|
|
@@ -6883,7 +7589,7 @@ Description: ${list.description}` : ""}`
|
|
|
6883
7589
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6884
7590
|
}
|
|
6885
7591
|
});
|
|
6886
|
-
server.tool("list_task_lists", "List task lists, optionally filtered by project", {
|
|
7592
|
+
server.tool("list_task_lists", "List all task lists, optionally filtered by project.", {
|
|
6887
7593
|
project_id: exports_external.string().optional()
|
|
6888
7594
|
}, async ({ project_id }) => {
|
|
6889
7595
|
try {
|
|
@@ -6903,7 +7609,7 @@ ${text}` }] };
|
|
|
6903
7609
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6904
7610
|
}
|
|
6905
7611
|
});
|
|
6906
|
-
server.tool("get_task_list", "Get task list details", {
|
|
7612
|
+
server.tool("get_task_list", "Get task list details including slug and metadata.", {
|
|
6907
7613
|
id: exports_external.string()
|
|
6908
7614
|
}, async ({ id }) => {
|
|
6909
7615
|
try {
|
|
@@ -6931,7 +7637,7 @@ server.tool("get_task_list", "Get task list details", {
|
|
|
6931
7637
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6932
7638
|
}
|
|
6933
7639
|
});
|
|
6934
|
-
server.tool("update_task_list", "Update a task list", {
|
|
7640
|
+
server.tool("update_task_list", "Update a task list's name or description.", {
|
|
6935
7641
|
id: exports_external.string(),
|
|
6936
7642
|
name: exports_external.string().optional(),
|
|
6937
7643
|
description: exports_external.string().optional()
|
|
@@ -6952,7 +7658,7 @@ Slug: ${list.slug}`
|
|
|
6952
7658
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6953
7659
|
}
|
|
6954
7660
|
});
|
|
6955
|
-
server.tool("delete_task_list", "Delete a task list. Tasks
|
|
7661
|
+
server.tool("delete_task_list", "Delete a task list. Tasks are orphaned, not deleted.", {
|
|
6956
7662
|
id: exports_external.string()
|
|
6957
7663
|
}, async ({ id }) => {
|
|
6958
7664
|
try {
|
|
@@ -6968,7 +7674,7 @@ server.tool("delete_task_list", "Delete a task list. Tasks lose association but
|
|
|
6968
7674
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6969
7675
|
}
|
|
6970
7676
|
});
|
|
6971
|
-
server.tool("get_task_history", "Get audit log
|
|
7677
|
+
server.tool("get_task_history", "Get audit log \u2014 field changes with timestamps and actors.", {
|
|
6972
7678
|
task_id: exports_external.string()
|
|
6973
7679
|
}, async ({ task_id }) => {
|
|
6974
7680
|
try {
|
|
@@ -6985,7 +7691,7 @@ ${text}` }] };
|
|
|
6985
7691
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6986
7692
|
}
|
|
6987
7693
|
});
|
|
6988
|
-
server.tool("get_recent_activity", "Get recent task changes
|
|
7694
|
+
server.tool("get_recent_activity", "Get recent task changes \u2014 global activity feed.", {
|
|
6989
7695
|
limit: exports_external.number().optional()
|
|
6990
7696
|
}, async ({ limit }) => {
|
|
6991
7697
|
try {
|
|
@@ -7001,7 +7707,7 @@ ${text}` }] };
|
|
|
7001
7707
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7002
7708
|
}
|
|
7003
7709
|
});
|
|
7004
|
-
server.tool("create_webhook", "Register a webhook
|
|
7710
|
+
server.tool("create_webhook", "Register a webhook for task change events.", {
|
|
7005
7711
|
url: exports_external.string(),
|
|
7006
7712
|
events: exports_external.array(exports_external.string()).optional(),
|
|
7007
7713
|
secret: exports_external.string().optional()
|
|
@@ -7028,7 +7734,7 @@ ${text}` }] };
|
|
|
7028
7734
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7029
7735
|
}
|
|
7030
7736
|
});
|
|
7031
|
-
server.tool("delete_webhook", "Delete a webhook", {
|
|
7737
|
+
server.tool("delete_webhook", "Delete a webhook by ID.", {
|
|
7032
7738
|
id: exports_external.string()
|
|
7033
7739
|
}, async ({ id }) => {
|
|
7034
7740
|
try {
|
|
@@ -7094,7 +7800,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
7094
7800
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7095
7801
|
}
|
|
7096
7802
|
});
|
|
7097
|
-
server.tool("delete_template", "Delete a task template", { id: exports_external.string() }, async ({ id }) => {
|
|
7803
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
7098
7804
|
try {
|
|
7099
7805
|
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
7100
7806
|
const deleted = deleteTemplate2(id);
|
|
@@ -7103,7 +7809,7 @@ server.tool("delete_template", "Delete a task template", { id: exports_external.
|
|
|
7103
7809
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7104
7810
|
}
|
|
7105
7811
|
});
|
|
7106
|
-
server.tool("approve_task", "Approve a task
|
|
7812
|
+
server.tool("approve_task", "Approve a task with requires_approval=true.", {
|
|
7107
7813
|
id: exports_external.string(),
|
|
7108
7814
|
agent_id: exports_external.string().optional()
|
|
7109
7815
|
}, async ({ id, agent_id }) => {
|
|
@@ -7122,7 +7828,31 @@ server.tool("approve_task", "Approve a task that requires approval.", {
|
|
|
7122
7828
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7123
7829
|
}
|
|
7124
7830
|
});
|
|
7125
|
-
server.tool("
|
|
7831
|
+
server.tool("fail_task", "Mark a task as failed with structured reason and optional auto-retry.", {
|
|
7832
|
+
id: exports_external.string(),
|
|
7833
|
+
agent_id: exports_external.string().optional(),
|
|
7834
|
+
reason: exports_external.string().optional(),
|
|
7835
|
+
error_code: exports_external.string().optional(),
|
|
7836
|
+
retry: exports_external.boolean().optional(),
|
|
7837
|
+
retry_after: exports_external.string().optional()
|
|
7838
|
+
}, async ({ id, agent_id, reason, error_code, retry, retry_after }) => {
|
|
7839
|
+
try {
|
|
7840
|
+
const resolvedId = resolveId(id);
|
|
7841
|
+
const result = failTask(resolvedId, agent_id, reason, { retry, retry_after, error_code });
|
|
7842
|
+
let text = `failed: ${formatTask(result.task)}`;
|
|
7843
|
+
if (reason)
|
|
7844
|
+
text += `
|
|
7845
|
+
Reason: ${reason}`;
|
|
7846
|
+
if (result.retryTask) {
|
|
7847
|
+
text += `
|
|
7848
|
+
Retry task created: ${formatTask(result.retryTask)}`;
|
|
7849
|
+
}
|
|
7850
|
+
return { content: [{ type: "text", text }] };
|
|
7851
|
+
} catch (e) {
|
|
7852
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7853
|
+
}
|
|
7854
|
+
});
|
|
7855
|
+
server.tool("get_my_tasks", "Get tasks assigned to/created by an agent with stats.", {
|
|
7126
7856
|
agent_name: exports_external.string()
|
|
7127
7857
|
}, async ({ agent_name }) => {
|
|
7128
7858
|
try {
|
|
@@ -7155,7 +7885,7 @@ In Progress:`);
|
|
|
7155
7885
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7156
7886
|
}
|
|
7157
7887
|
});
|
|
7158
|
-
server.tool("get_org_chart", "Get agent org chart
|
|
7888
|
+
server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
|
|
7159
7889
|
try {
|
|
7160
7890
|
let render = function(nodes, indent = 0) {
|
|
7161
7891
|
return nodes.map((n) => {
|
|
@@ -7177,7 +7907,7 @@ server.tool("get_org_chart", "Get agent org chart \u2014 who reports to who.", {
|
|
|
7177
7907
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7178
7908
|
}
|
|
7179
7909
|
});
|
|
7180
|
-
server.tool("set_reports_to", "Set
|
|
7910
|
+
server.tool("set_reports_to", "Set agent reporting relationship in org chart.", {
|
|
7181
7911
|
agent_name: exports_external.string(),
|
|
7182
7912
|
manager_name: exports_external.string().optional()
|
|
7183
7913
|
}, async ({ agent_name, manager_name }) => {
|
|
@@ -7200,7 +7930,367 @@ server.tool("set_reports_to", "Set who an agent reports to in the org chart.", {
|
|
|
7200
7930
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7201
7931
|
}
|
|
7202
7932
|
});
|
|
7203
|
-
server.tool("
|
|
7933
|
+
server.tool("bulk_update_tasks", "Update multiple tasks at once with the same changes.", {
|
|
7934
|
+
task_ids: exports_external.array(exports_external.string()),
|
|
7935
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
7936
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
7937
|
+
assigned_to: exports_external.string().optional(),
|
|
7938
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
7939
|
+
}, async ({ task_ids, ...updates }) => {
|
|
7940
|
+
try {
|
|
7941
|
+
const resolvedIds = task_ids.map((id) => resolveId(id));
|
|
7942
|
+
const result = bulkUpdateTasks(resolvedIds, updates);
|
|
7943
|
+
const parts = [`Updated ${result.updated} task(s).`];
|
|
7944
|
+
if (result.failed.length > 0) {
|
|
7945
|
+
parts.push(`Failed ${result.failed.length}:`);
|
|
7946
|
+
for (const f of result.failed)
|
|
7947
|
+
parts.push(` ${f.id.slice(0, 8)}: ${f.error}`);
|
|
7948
|
+
}
|
|
7949
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
7950
|
+
`) }] };
|
|
7951
|
+
} catch (e) {
|
|
7952
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7953
|
+
}
|
|
7954
|
+
});
|
|
7955
|
+
server.tool("clone_task", "Duplicate a task with optional field overrides.", {
|
|
7956
|
+
task_id: exports_external.string(),
|
|
7957
|
+
title: exports_external.string().optional(),
|
|
7958
|
+
description: exports_external.string().optional(),
|
|
7959
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
7960
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
7961
|
+
project_id: exports_external.string().optional(),
|
|
7962
|
+
plan_id: exports_external.string().optional(),
|
|
7963
|
+
task_list_id: exports_external.string().optional(),
|
|
7964
|
+
assigned_to: exports_external.string().optional(),
|
|
7965
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
7966
|
+
estimated_minutes: exports_external.number().optional()
|
|
7967
|
+
}, async ({ task_id, ...overrides }) => {
|
|
7968
|
+
try {
|
|
7969
|
+
const resolvedId = resolveId(task_id);
|
|
7970
|
+
const resolved = { ...overrides };
|
|
7971
|
+
if (resolved.project_id)
|
|
7972
|
+
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
7973
|
+
if (resolved.plan_id)
|
|
7974
|
+
resolved.plan_id = resolveId(resolved.plan_id, "plans");
|
|
7975
|
+
if (resolved.task_list_id)
|
|
7976
|
+
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
7977
|
+
const task = cloneTask(resolvedId, resolved);
|
|
7978
|
+
return { content: [{ type: "text", text: `cloned: ${formatTask(task)}` }] };
|
|
7979
|
+
} catch (e) {
|
|
7980
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7981
|
+
}
|
|
7982
|
+
});
|
|
7983
|
+
server.tool("get_task_stats", "Get task analytics: counts by status, priority, agent.", {
|
|
7984
|
+
project_id: exports_external.string().optional(),
|
|
7985
|
+
task_list_id: exports_external.string().optional(),
|
|
7986
|
+
agent_id: exports_external.string().optional()
|
|
7987
|
+
}, async ({ project_id, task_list_id, agent_id }) => {
|
|
7988
|
+
try {
|
|
7989
|
+
const filters = {};
|
|
7990
|
+
if (project_id)
|
|
7991
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
7992
|
+
if (task_list_id)
|
|
7993
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
7994
|
+
if (agent_id)
|
|
7995
|
+
filters.agent_id = agent_id;
|
|
7996
|
+
const stats = getTaskStats(Object.keys(filters).length > 0 ? filters : undefined);
|
|
7997
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
7998
|
+
} catch (e) {
|
|
7999
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8000
|
+
}
|
|
8001
|
+
});
|
|
8002
|
+
server.tool("get_task_graph", "Get full dependency tree for a task.", {
|
|
8003
|
+
id: exports_external.string(),
|
|
8004
|
+
direction: exports_external.enum(["up", "down", "both"]).optional()
|
|
8005
|
+
}, async ({ id, direction }) => {
|
|
8006
|
+
try {
|
|
8007
|
+
let formatNode = function(node, indent) {
|
|
8008
|
+
const prefix = " ".repeat(indent);
|
|
8009
|
+
const idLabel = node.task.short_id || node.task.id.slice(0, 8);
|
|
8010
|
+
const blocked = node.task.is_blocked ? " (blocked: yes)" : "";
|
|
8011
|
+
let out = `${prefix}[${node.task.status}] ${idLabel} | ${node.task.title}${blocked}
|
|
8012
|
+
`;
|
|
8013
|
+
if (node.depends_on.length > 0) {
|
|
8014
|
+
out += `${prefix} Depends on:
|
|
8015
|
+
`;
|
|
8016
|
+
for (const dep of node.depends_on) {
|
|
8017
|
+
out += formatNode(dep, indent + 2);
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
if (node.blocks.length > 0) {
|
|
8021
|
+
out += `${prefix} Blocks:
|
|
8022
|
+
`;
|
|
8023
|
+
for (const dep of node.blocks) {
|
|
8024
|
+
out += formatNode(dep, indent + 2);
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
return out;
|
|
8028
|
+
};
|
|
8029
|
+
const taskId = resolveId(id, "tasks");
|
|
8030
|
+
const graph = getTaskGraph(taskId, direction || "both");
|
|
8031
|
+
let text = `Task: ${formatNode(graph, 0)}`;
|
|
8032
|
+
if (graph.depends_on.length > 0) {
|
|
8033
|
+
text += `
|
|
8034
|
+
Depends on:
|
|
8035
|
+
`;
|
|
8036
|
+
for (const dep of graph.depends_on) {
|
|
8037
|
+
text += formatNode(dep, 1);
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
8040
|
+
if (graph.blocks.length > 0) {
|
|
8041
|
+
text += `
|
|
8042
|
+
Blocks:
|
|
8043
|
+
`;
|
|
8044
|
+
for (const dep of graph.blocks) {
|
|
8045
|
+
text += formatNode(dep, 1);
|
|
8046
|
+
}
|
|
8047
|
+
}
|
|
8048
|
+
return { content: [{ type: "text", text }] };
|
|
8049
|
+
} catch (e) {
|
|
8050
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8051
|
+
}
|
|
8052
|
+
});
|
|
8053
|
+
server.tool("bulk_create_tasks", "Create multiple tasks atomically with dependency support.", {
|
|
8054
|
+
tasks: exports_external.array(exports_external.object({
|
|
8055
|
+
temp_id: exports_external.string().optional(),
|
|
8056
|
+
title: exports_external.string(),
|
|
8057
|
+
description: exports_external.string().optional(),
|
|
8058
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
8059
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
8060
|
+
project_id: exports_external.string().optional(),
|
|
8061
|
+
plan_id: exports_external.string().optional(),
|
|
8062
|
+
task_list_id: exports_external.string().optional(),
|
|
8063
|
+
agent_id: exports_external.string().optional(),
|
|
8064
|
+
assigned_to: exports_external.string().optional(),
|
|
8065
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
8066
|
+
estimated_minutes: exports_external.number().optional(),
|
|
8067
|
+
depends_on_temp_ids: exports_external.array(exports_external.string()).optional()
|
|
8068
|
+
})),
|
|
8069
|
+
project_id: exports_external.string().optional(),
|
|
8070
|
+
plan_id: exports_external.string().optional(),
|
|
8071
|
+
task_list_id: exports_external.string().optional()
|
|
8072
|
+
}, async ({ tasks, project_id, plan_id, task_list_id }) => {
|
|
8073
|
+
try {
|
|
8074
|
+
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
8075
|
+
const resolvedPlanId = plan_id ? resolveId(plan_id, "plans") : undefined;
|
|
8076
|
+
const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
|
|
8077
|
+
const enrichedTasks = tasks.map((t) => ({
|
|
8078
|
+
...t,
|
|
8079
|
+
project_id: t.project_id || resolvedProjectId,
|
|
8080
|
+
plan_id: t.plan_id || resolvedPlanId,
|
|
8081
|
+
task_list_id: t.task_list_id || resolvedTaskListId
|
|
8082
|
+
}));
|
|
8083
|
+
const result = bulkCreateTasks(enrichedTasks);
|
|
8084
|
+
const lines = result.created.map((t) => {
|
|
8085
|
+
const tid = t.temp_id ? `[${t.temp_id}] ` : "";
|
|
8086
|
+
const sid = t.short_id || t.id.slice(0, 8);
|
|
8087
|
+
return ` ${tid}${sid} | ${t.title}`;
|
|
8088
|
+
});
|
|
8089
|
+
return { content: [{ type: "text", text: `Created ${result.created.length} task(s):
|
|
8090
|
+
${lines.join(`
|
|
8091
|
+
`)}` }] };
|
|
8092
|
+
} catch (e) {
|
|
8093
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8094
|
+
}
|
|
8095
|
+
});
|
|
8096
|
+
server.tool("move_task", "Move a task to a different list, project, or plan.", {
|
|
8097
|
+
task_id: exports_external.string(),
|
|
8098
|
+
task_list_id: exports_external.string().nullable().optional(),
|
|
8099
|
+
project_id: exports_external.string().nullable().optional(),
|
|
8100
|
+
plan_id: exports_external.string().nullable().optional()
|
|
8101
|
+
}, async ({ task_id, ...target }) => {
|
|
8102
|
+
try {
|
|
8103
|
+
const resolvedId = resolveId(task_id);
|
|
8104
|
+
const resolvedTarget = {};
|
|
8105
|
+
if (target.task_list_id !== undefined)
|
|
8106
|
+
resolvedTarget.task_list_id = target.task_list_id ? resolveId(target.task_list_id, "task_lists") : null;
|
|
8107
|
+
if (target.project_id !== undefined)
|
|
8108
|
+
resolvedTarget.project_id = target.project_id ? resolveId(target.project_id, "projects") : null;
|
|
8109
|
+
if (target.plan_id !== undefined)
|
|
8110
|
+
resolvedTarget.plan_id = target.plan_id ? resolveId(target.plan_id, "plans") : null;
|
|
8111
|
+
const task = moveTask(resolvedId, resolvedTarget);
|
|
8112
|
+
return { content: [{ type: "text", text: `moved: ${formatTask(task)}` }] };
|
|
8113
|
+
} catch (e) {
|
|
8114
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8115
|
+
}
|
|
8116
|
+
});
|
|
8117
|
+
server.tool("get_next_task", "Get the best pending task to work on next.", {
|
|
8118
|
+
agent_id: exports_external.string().optional(),
|
|
8119
|
+
project_id: exports_external.string().optional(),
|
|
8120
|
+
task_list_id: exports_external.string().optional(),
|
|
8121
|
+
plan_id: exports_external.string().optional(),
|
|
8122
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
8123
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
8124
|
+
try {
|
|
8125
|
+
const filters = {};
|
|
8126
|
+
if (project_id)
|
|
8127
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8128
|
+
if (task_list_id)
|
|
8129
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8130
|
+
if (plan_id)
|
|
8131
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
8132
|
+
if (tags)
|
|
8133
|
+
filters.tags = tags;
|
|
8134
|
+
const task = getNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8135
|
+
if (!task) {
|
|
8136
|
+
return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
|
|
8137
|
+
}
|
|
8138
|
+
return { content: [{ type: "text", text: `next: ${formatTask(task)}
|
|
8139
|
+
${formatTaskDetail(task)}` }] };
|
|
8140
|
+
} catch (e) {
|
|
8141
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8142
|
+
}
|
|
8143
|
+
});
|
|
8144
|
+
server.tool("get_active_work", "See all in-progress tasks and who is working on them.", {
|
|
8145
|
+
project_id: exports_external.string().optional(),
|
|
8146
|
+
task_list_id: exports_external.string().optional()
|
|
8147
|
+
}, async ({ project_id, task_list_id }) => {
|
|
8148
|
+
try {
|
|
8149
|
+
const filters = {};
|
|
8150
|
+
if (project_id)
|
|
8151
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8152
|
+
if (task_list_id)
|
|
8153
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8154
|
+
const work = getActiveWork(Object.keys(filters).length > 0 ? filters : undefined);
|
|
8155
|
+
if (work.length === 0) {
|
|
8156
|
+
return { content: [{ type: "text", text: "No active work \u2014 no tasks are currently in progress." }] };
|
|
8157
|
+
}
|
|
8158
|
+
const text = work.map((w) => {
|
|
8159
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
8160
|
+
const agent = w.assigned_to || w.locked_by || "unassigned";
|
|
8161
|
+
const since = w.updated_at;
|
|
8162
|
+
return `${agent.padEnd(12)} | ${w.priority.padEnd(8)} | ${id} | ${w.title} (since ${since})`;
|
|
8163
|
+
}).join(`
|
|
8164
|
+
`);
|
|
8165
|
+
return { content: [{ type: "text", text: `${work.length} active task(s):
|
|
8166
|
+
${text}` }] };
|
|
8167
|
+
} catch (e) {
|
|
8168
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8169
|
+
}
|
|
8170
|
+
});
|
|
8171
|
+
server.tool("get_tasks_changed_since", "Get tasks modified after a timestamp for incremental sync.", {
|
|
8172
|
+
since: exports_external.string(),
|
|
8173
|
+
project_id: exports_external.string().optional(),
|
|
8174
|
+
task_list_id: exports_external.string().optional()
|
|
8175
|
+
}, async ({ since, project_id, task_list_id }) => {
|
|
8176
|
+
try {
|
|
8177
|
+
const filters = {};
|
|
8178
|
+
if (project_id)
|
|
8179
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8180
|
+
if (task_list_id)
|
|
8181
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8182
|
+
const tasks = getTasksChangedSince(since, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8183
|
+
if (tasks.length === 0) {
|
|
8184
|
+
return { content: [{ type: "text", text: `No tasks changed since ${since}.` }] };
|
|
8185
|
+
}
|
|
8186
|
+
const text = tasks.map((t) => {
|
|
8187
|
+
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
8188
|
+
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned} (updated: ${t.updated_at})`;
|
|
8189
|
+
}).join(`
|
|
8190
|
+
`);
|
|
8191
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) changed since ${since}:
|
|
8192
|
+
${text}` }] };
|
|
8193
|
+
} catch (e) {
|
|
8194
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8195
|
+
}
|
|
8196
|
+
});
|
|
8197
|
+
server.tool("claim_next_task", "Atomically claim, lock, and start the best pending task.", {
|
|
8198
|
+
agent_id: exports_external.string(),
|
|
8199
|
+
project_id: exports_external.string().optional(),
|
|
8200
|
+
task_list_id: exports_external.string().optional(),
|
|
8201
|
+
plan_id: exports_external.string().optional(),
|
|
8202
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
8203
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
8204
|
+
try {
|
|
8205
|
+
const filters = {};
|
|
8206
|
+
if (project_id)
|
|
8207
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8208
|
+
if (task_list_id)
|
|
8209
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8210
|
+
if (plan_id)
|
|
8211
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
8212
|
+
if (tags)
|
|
8213
|
+
filters.tags = tags;
|
|
8214
|
+
const task = claimNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8215
|
+
if (!task) {
|
|
8216
|
+
return { content: [{ type: "text", text: "No tasks available to claim." }] };
|
|
8217
|
+
}
|
|
8218
|
+
return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
|
|
8219
|
+
${formatTaskDetail(task)}` }] };
|
|
8220
|
+
} catch (e) {
|
|
8221
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8222
|
+
}
|
|
8223
|
+
});
|
|
8224
|
+
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
8225
|
+
stale_minutes: exports_external.number().optional(),
|
|
8226
|
+
project_id: exports_external.string().optional(),
|
|
8227
|
+
task_list_id: exports_external.string().optional()
|
|
8228
|
+
}, async ({ stale_minutes, project_id, task_list_id }) => {
|
|
8229
|
+
try {
|
|
8230
|
+
const filters = {};
|
|
8231
|
+
if (project_id)
|
|
8232
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8233
|
+
if (task_list_id)
|
|
8234
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8235
|
+
const tasks = getStaleTasks(stale_minutes || 30, Object.keys(filters).length > 0 ? filters : undefined);
|
|
8236
|
+
if (tasks.length === 0) {
|
|
8237
|
+
return { content: [{ type: "text", text: "No stale tasks found." }] };
|
|
8238
|
+
}
|
|
8239
|
+
const text = tasks.map((t) => {
|
|
8240
|
+
const id = t.short_id || t.id.slice(0, 8);
|
|
8241
|
+
const agent = t.locked_by || t.assigned_to || "unknown";
|
|
8242
|
+
const staleFor = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
8243
|
+
return `${id} | ${agent} | ${t.title} (stale ${staleFor}min)`;
|
|
8244
|
+
}).join(`
|
|
8245
|
+
`);
|
|
8246
|
+
return { content: [{ type: "text", text: `${tasks.length} stale task(s):
|
|
8247
|
+
${text}` }] };
|
|
8248
|
+
} catch (e) {
|
|
8249
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8250
|
+
}
|
|
8251
|
+
});
|
|
8252
|
+
server.tool("get_status", "Get a full project health snapshot \u2014 counts, active work, next task, stale/overdue summary.", {
|
|
8253
|
+
agent_id: exports_external.string().optional(),
|
|
8254
|
+
project_id: exports_external.string().optional(),
|
|
8255
|
+
task_list_id: exports_external.string().optional()
|
|
8256
|
+
}, async ({ agent_id, project_id, task_list_id }) => {
|
|
8257
|
+
try {
|
|
8258
|
+
const filters = {};
|
|
8259
|
+
if (project_id)
|
|
8260
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8261
|
+
if (task_list_id)
|
|
8262
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
8263
|
+
const status = getStatus(Object.keys(filters).length > 0 ? filters : undefined, agent_id);
|
|
8264
|
+
const lines = [
|
|
8265
|
+
`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done | ${status.total} total`
|
|
8266
|
+
];
|
|
8267
|
+
if (status.stale_count > 0)
|
|
8268
|
+
lines.push(`\u26A0\uFE0F ${status.stale_count} stale (stuck in_progress)`);
|
|
8269
|
+
if (status.overdue_recurring > 0)
|
|
8270
|
+
lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
|
|
8271
|
+
if (status.active_work.length > 0) {
|
|
8272
|
+
lines.push(`
|
|
8273
|
+
Active (${status.active_work.length}):`);
|
|
8274
|
+
for (const w of status.active_work.slice(0, 5)) {
|
|
8275
|
+
const id = w.short_id || w.id.slice(0, 8);
|
|
8276
|
+
lines.push(` ${id} | ${w.assigned_to || w.locked_by || "?"} | ${w.title}`);
|
|
8277
|
+
}
|
|
8278
|
+
}
|
|
8279
|
+
if (status.next_task) {
|
|
8280
|
+
lines.push(`
|
|
8281
|
+
Next up:`);
|
|
8282
|
+
lines.push(` ${formatTask(status.next_task)}`);
|
|
8283
|
+
} else {
|
|
8284
|
+
lines.push(`
|
|
8285
|
+
No pending tasks available.`);
|
|
8286
|
+
}
|
|
8287
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
8288
|
+
`) }] };
|
|
8289
|
+
} catch (e) {
|
|
8290
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8291
|
+
}
|
|
8292
|
+
});
|
|
8293
|
+
server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
|
|
7204
8294
|
const all = [
|
|
7205
8295
|
"create_task",
|
|
7206
8296
|
"list_tasks",
|
|
@@ -7209,6 +8299,7 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7209
8299
|
"delete_task",
|
|
7210
8300
|
"start_task",
|
|
7211
8301
|
"complete_task",
|
|
8302
|
+
"fail_task",
|
|
7212
8303
|
"lock_task",
|
|
7213
8304
|
"unlock_task",
|
|
7214
8305
|
"approve_task",
|
|
@@ -7225,7 +8316,11 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7225
8316
|
"register_agent",
|
|
7226
8317
|
"list_agents",
|
|
7227
8318
|
"get_agent",
|
|
8319
|
+
"rename_agent",
|
|
8320
|
+
"delete_agent",
|
|
7228
8321
|
"get_my_tasks",
|
|
8322
|
+
"get_org_chart",
|
|
8323
|
+
"set_reports_to",
|
|
7229
8324
|
"create_task_list",
|
|
7230
8325
|
"list_task_lists",
|
|
7231
8326
|
"get_task_list",
|
|
@@ -7233,6 +8328,10 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7233
8328
|
"delete_task_list",
|
|
7234
8329
|
"search_tasks",
|
|
7235
8330
|
"sync",
|
|
8331
|
+
"clone_task",
|
|
8332
|
+
"move_task",
|
|
8333
|
+
"get_next_task",
|
|
8334
|
+
"claim_next_task",
|
|
7236
8335
|
"get_task_history",
|
|
7237
8336
|
"get_recent_activity",
|
|
7238
8337
|
"create_webhook",
|
|
@@ -7242,6 +8341,14 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7242
8341
|
"list_templates",
|
|
7243
8342
|
"create_task_from_template",
|
|
7244
8343
|
"delete_template",
|
|
8344
|
+
"bulk_update_tasks",
|
|
8345
|
+
"bulk_create_tasks",
|
|
8346
|
+
"get_task_stats",
|
|
8347
|
+
"get_task_graph",
|
|
8348
|
+
"get_active_work",
|
|
8349
|
+
"get_tasks_changed_since",
|
|
8350
|
+
"get_stale_tasks",
|
|
8351
|
+
"get_status",
|
|
7245
8352
|
"search_tools",
|
|
7246
8353
|
"describe_tools"
|
|
7247
8354
|
];
|
|
@@ -7249,27 +8356,178 @@ server.tool("search_tools", "List tool names matching a query.", { query: export
|
|
|
7249
8356
|
const matches = q ? all.filter((n) => n.includes(q)) : all;
|
|
7250
8357
|
return { content: [{ type: "text", text: matches.join(", ") }] };
|
|
7251
8358
|
});
|
|
7252
|
-
server.tool("describe_tools", "Get
|
|
8359
|
+
server.tool("describe_tools", "Get detailed parameter info for specific tools by name.", { names: exports_external.array(exports_external.string()) }, async ({ names }) => {
|
|
7253
8360
|
const descriptions = {
|
|
7254
|
-
create_task:
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
8361
|
+
create_task: `Create a new task.
|
|
8362
|
+
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)
|
|
8363
|
+
Example: {title: 'Daily standup', recurrence_rule: 'every weekday', priority: 'medium'}`,
|
|
8364
|
+
list_tasks: `List tasks with optional filters. Supports pagination.
|
|
8365
|
+
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)
|
|
8366
|
+
Example: {status: ['pending', 'in_progress'], has_recurrence: true, limit: 20}`,
|
|
8367
|
+
get_task: `Get full task details with subtasks, deps, and comments.
|
|
8368
|
+
Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
|
|
8369
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8370
|
+
update_task: `Update task fields. Requires version for optimistic locking (get it from get_task first).
|
|
8371
|
+
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)
|
|
8372
|
+
Example: {id: 'a1b2c3d4', version: 3, status: 'completed'}`,
|
|
8373
|
+
delete_task: `Delete a task permanently. Subtasks cascade-delete. Dependencies removed.
|
|
8374
|
+
Params: id(string, req)
|
|
8375
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8376
|
+
start_task: `Claim, lock, and set task status to in_progress in one call.
|
|
8377
|
+
Params: id(string, req), agent_id(string, req \u2014 your 8-char agent ID)
|
|
8378
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8379
|
+
complete_task: `Mark task completed, release lock, set completed_at timestamp. For recurring tasks, auto-spawns next instance unless skip_recurrence is true.
|
|
8380
|
+
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)
|
|
8381
|
+
Example: {id: 'a1b2c3d4', skip_recurrence: false}`,
|
|
8382
|
+
lock_task: `Acquire exclusive lock on a task. Locks auto-expire after 30 min. Re-locking by same agent is idempotent.
|
|
8383
|
+
Params: id(string, req), agent_id(string, req)
|
|
8384
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8385
|
+
unlock_task: `Release exclusive lock on a task.
|
|
8386
|
+
Params: id(string, req), agent_id(string, optional \u2014 omit to force-unlock)
|
|
8387
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8388
|
+
approve_task: `Approve a task with requires_approval=true. Must be approved before completion.
|
|
8389
|
+
Params: id(string, req), agent_id(string, optional \u2014 defaults to 'system')
|
|
8390
|
+
Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
|
|
8391
|
+
fail_task: `Mark a task as failed with structured reason and optional auto-retry. Stores failure info in metadata._failure, releases lock.
|
|
8392
|
+
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)
|
|
8393
|
+
Example: {id: 'a1b2c3d4', reason: 'Build timeout', error_code: 'TIMEOUT', retry: true}`,
|
|
8394
|
+
add_dependency: `Add a dependency: task_id depends on depends_on. Prevents cycles via BFS.
|
|
8395
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
8396
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
8397
|
+
remove_dependency: `Remove a dependency link between two tasks.
|
|
8398
|
+
Params: task_id(string, req), depends_on(string, req)
|
|
8399
|
+
Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
|
|
8400
|
+
add_comment: `Add a comment/note to a task. Comments are append-only.
|
|
8401
|
+
Params: task_id(string, req), content(string, req), agent_id(string), session_id(string)
|
|
8402
|
+
Example: {task_id: 'a1b2c3d4', content: 'Blocked by API rate limit'}`,
|
|
8403
|
+
create_project: `Register a new project. Auto-generates task prefix for short IDs (e.g. APP-00001).
|
|
8404
|
+
Params: name(string, req), path(string, req \u2014 unique absolute path), description(string), task_list_id(string)
|
|
8405
|
+
Example: {name: 'my-app', path: '/Users/dev/my-app'}`,
|
|
8406
|
+
list_projects: "List all registered projects. No params.",
|
|
8407
|
+
create_plan: `Create a plan to group related tasks.
|
|
8408
|
+
Params: name(string, req), project_id(string), description(string), status(active|completed|archived, default:active), task_list_id(string), agent_id(string)
|
|
8409
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
8410
|
+
list_plans: `List all plans, optionally filtered by project.
|
|
8411
|
+
Params: project_id(string)
|
|
8412
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8413
|
+
get_plan: `Get plan details (name, status, description, timestamps).
|
|
8414
|
+
Params: id(string, req)
|
|
8415
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8416
|
+
update_plan: `Update plan fields.
|
|
8417
|
+
Params: id(string, req), name(string), description(string), status(active|completed|archived), task_list_id(string), agent_id(string)
|
|
8418
|
+
Example: {id: 'a1b2c3d4', status: 'completed'}`,
|
|
8419
|
+
delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
|
|
8420
|
+
Params: id(string, req)
|
|
8421
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8422
|
+
register_agent: `Register an agent (idempotent by name). Returns existing agent if name matches.
|
|
8423
|
+
Params: name(string, req \u2014 e.g. 'maximus'), description(string)
|
|
8424
|
+
Example: {name: 'maximus', description: 'Backend developer'}`,
|
|
8425
|
+
list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
|
|
8426
|
+
get_agent: `Get agent details by ID or name. Provide one of id or name.
|
|
8427
|
+
Params: id(string), name(string)
|
|
8428
|
+
Example: {name: 'maximus'}`,
|
|
8429
|
+
rename_agent: `Rename an agent. Resolve by id or current name.
|
|
8430
|
+
Params: id(string), name(string \u2014 current name), new_name(string, req)
|
|
8431
|
+
Example: {name: 'old-name', new_name: 'new-name'}`,
|
|
8432
|
+
delete_agent: `Delete an agent permanently. Resolve by id or name.
|
|
8433
|
+
Params: id(string), name(string)
|
|
8434
|
+
Example: {name: 'maximus'}`,
|
|
8435
|
+
get_my_tasks: `Get all tasks assigned to/created by an agent, with stats (pending/active/done/rate).
|
|
8436
|
+
Params: agent_name(string, req)
|
|
8437
|
+
Example: {agent_name: 'maximus'}`,
|
|
8438
|
+
get_org_chart: "Get agent org chart showing reporting hierarchy. No params.",
|
|
8439
|
+
set_reports_to: `Set who an agent reports to in the org chart. Omit manager_name for top-level.
|
|
8440
|
+
Params: agent_name(string, req), manager_name(string, optional)
|
|
8441
|
+
Example: {agent_name: 'brutus', manager_name: 'maximus'}`,
|
|
8442
|
+
create_task_list: `Create a task list \u2014 a container/folder for organizing tasks.
|
|
8443
|
+
Params: name(string, req), slug(string \u2014 auto-generated if omitted), project_id(string), description(string)
|
|
8444
|
+
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
8445
|
+
list_task_lists: `List all task lists, optionally filtered by project.
|
|
8446
|
+
Params: project_id(string)
|
|
8447
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8448
|
+
get_task_list: `Get task list details (name, slug, project, metadata).
|
|
8449
|
+
Params: id(string, req)
|
|
8450
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8451
|
+
update_task_list: `Update a task list's name or description.
|
|
8452
|
+
Params: id(string, req), name(string), description(string)
|
|
8453
|
+
Example: {id: 'a1b2c3d4', name: 'Sprint 2'}`,
|
|
8454
|
+
delete_task_list: `Delete a task list. Tasks are orphaned (not deleted).
|
|
8455
|
+
Params: id(string, req)
|
|
8456
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8457
|
+
search_tasks: `Full-text search across task titles, descriptions, and tags. Supports filters.
|
|
8458
|
+
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)
|
|
8459
|
+
Example: {query: 'auth bug', status: 'pending'}`,
|
|
8460
|
+
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.
|
|
8461
|
+
Params: agent_id(string \u2014 prefers your tasks), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
8462
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
8463
|
+
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.
|
|
8464
|
+
Params: agent_id(string, req \u2014 used for lock and assignment), project_id(string), task_list_id(string), plan_id(string), tags(string[])
|
|
8465
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
8466
|
+
sync: `Sync tasks between local DB and agent task list (e.g. Claude Code).
|
|
8467
|
+
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)
|
|
8468
|
+
Example: {agent: 'claude', direction: 'push'}`,
|
|
8469
|
+
clone_task: `Duplicate a task with optional field overrides. Creates new independent copy.
|
|
8470
|
+
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)
|
|
8471
|
+
Example: {task_id: 'a1b2c3d4', title: 'Cloned task', assigned_to: 'brutus'}`,
|
|
8472
|
+
move_task: `Move a task to a different list, project, or plan.
|
|
8473
|
+
Params: task_id(string, req), task_list_id(string|null), project_id(string|null), plan_id(string|null)
|
|
8474
|
+
Example: {task_id: 'a1b2c3d4', task_list_id: 'e5f6g7h8'}`,
|
|
8475
|
+
bulk_update_tasks: `Update multiple tasks at once with the same changes.
|
|
8476
|
+
Params: task_ids(string[], req), status(pending|in_progress|completed|failed|cancelled), priority(low|medium|high|critical), assigned_to(string), tags(string[])
|
|
8477
|
+
Example: {task_ids: ['abc12345', 'def67890'], status: 'completed'}`,
|
|
8478
|
+
bulk_create_tasks: `Create multiple tasks atomically. Supports inter-task dependencies via temp_id references.
|
|
8479
|
+
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)
|
|
8480
|
+
Example: {tasks: [{temp_id: 'a', title: 'First'}, {temp_id: 'b', title: 'Second', depends_on_temp_ids: ['a']}]}`,
|
|
8481
|
+
get_task_stats: `Get task analytics: counts by status, priority, agent, and completion rate. All via SQL.
|
|
8482
|
+
Params: project_id(string), task_list_id(string), agent_id(string)
|
|
8483
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8484
|
+
get_task_graph: `Get full dependency tree for a task \u2014 upstream blockers and downstream dependents.
|
|
8485
|
+
Params: id(string, req), direction(up|down|both, default:both)
|
|
8486
|
+
Example: {id: 'a1b2c3d4', direction: 'up'}`,
|
|
8487
|
+
get_task_history: `Get audit log for a task \u2014 all field changes with timestamps and actors.
|
|
8488
|
+
Params: task_id(string, req)
|
|
8489
|
+
Example: {task_id: 'a1b2c3d4'}`,
|
|
8490
|
+
get_recent_activity: `Get recent task changes across all tasks \u2014 global activity feed.
|
|
8491
|
+
Params: limit(number, default:50)
|
|
8492
|
+
Example: {limit: 20}`,
|
|
8493
|
+
create_webhook: `Register a webhook for task change events.
|
|
8494
|
+
Params: url(string, req), events(string[] \u2014 empty=all), secret(string \u2014 HMAC signing)
|
|
8495
|
+
Example: {url: 'https://example.com/hook', events: ['task.created', 'task.completed']}`,
|
|
8496
|
+
list_webhooks: "List all registered webhooks. No params.",
|
|
8497
|
+
delete_webhook: `Delete a webhook by ID.
|
|
8498
|
+
Params: id(string, req)
|
|
8499
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8500
|
+
create_template: `Create a reusable task template.
|
|
8501
|
+
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)
|
|
8502
|
+
Example: {name: 'Bug Report', title_pattern: 'Bug: {description}', priority: 'high', tags: ['bug']}`,
|
|
8503
|
+
list_templates: "List all task templates. No params.",
|
|
8504
|
+
create_task_from_template: `Create a task from a template with optional overrides.
|
|
8505
|
+
Params: template_id(string, req), title(string), description(string), priority(low|medium|high|critical), assigned_to(string), project_id(string)
|
|
8506
|
+
Example: {template_id: 'a1b2c3d4', assigned_to: 'maximus'}`,
|
|
8507
|
+
delete_template: `Delete a task template.
|
|
8508
|
+
Params: id(string, req)
|
|
8509
|
+
Example: {id: 'a1b2c3d4'}`,
|
|
8510
|
+
get_active_work: `See all in-progress tasks and who is working on them.
|
|
8511
|
+
Params: project_id(string, optional), task_list_id(string, optional)
|
|
8512
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
8513
|
+
get_tasks_changed_since: `Get tasks modified after a timestamp \u2014 incremental delta sync.
|
|
8514
|
+
Params: since(string, req \u2014 ISO date), project_id(string, optional), task_list_id(string, optional)
|
|
8515
|
+
Example: {since: '2026-03-14T10:00:00Z'}`,
|
|
8516
|
+
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
8517
|
+
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
8518
|
+
Example: {stale_minutes: 60, project_id: 'a1b2c3d4'}`,
|
|
8519
|
+
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.
|
|
8520
|
+
Params: agent_id(string, optional \u2014 prefers tasks assigned to this agent for next_task), project_id(string, optional), task_list_id(string, optional)
|
|
8521
|
+
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
8522
|
+
search_tools: `List all tool names or filter by substring.
|
|
8523
|
+
Params: query(string, optional)
|
|
8524
|
+
Example: {query: 'task'}`,
|
|
8525
|
+
describe_tools: `Get detailed descriptions and parameter info for tools by name.
|
|
8526
|
+
Params: names(string[], req)
|
|
8527
|
+
Example: {names: ['create_task', 'update_task']}`
|
|
7271
8528
|
};
|
|
7272
|
-
const result = names.map((n) => `${n}: ${descriptions[n] || "
|
|
8529
|
+
const result = names.map((n) => `${n}: ${descriptions[n] || "Unknown tool. Use search_tools to list available tools."}`).join(`
|
|
8530
|
+
|
|
7273
8531
|
`);
|
|
7274
8532
|
return { content: [{ type: "text", text: result }] };
|
|
7275
8533
|
});
|