@hasna/todos 0.11.19 → 0.11.21
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 +399 -21
- package/dist/db/projects.d.ts +39 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +16 -0
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +168 -9
- package/dist/lib/search.d.ts +1 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +264 -18
- package/dist/mcp/tools/agents.d.ts.map +1 -1
- package/dist/server/index.js +166 -4
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -200,6 +200,18 @@ function ensureSchema(db) {
|
|
|
200
200
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
201
201
|
UNIQUE(source_id, source_type, target_id, target_type, relation_type)
|
|
202
202
|
)`);
|
|
203
|
+
ensureTable("project_machine_paths", `
|
|
204
|
+
CREATE TABLE project_machine_paths (
|
|
205
|
+
id TEXT PRIMARY KEY,
|
|
206
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
207
|
+
machine_id TEXT NOT NULL,
|
|
208
|
+
path TEXT NOT NULL,
|
|
209
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
210
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
211
|
+
UNIQUE(project_id, machine_id)
|
|
212
|
+
)`);
|
|
213
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
|
|
214
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
|
|
203
215
|
ensureTable("machines", `
|
|
204
216
|
CREATE TABLE machines (
|
|
205
217
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
|
|
@@ -235,6 +247,7 @@ function ensureSchema(db) {
|
|
|
235
247
|
ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
|
|
236
248
|
ensureColumn("tasks", "retry_after", "TEXT");
|
|
237
249
|
ensureColumn("tasks", "sla_minutes", "INTEGER");
|
|
250
|
+
ensureColumn("tasks", "archived_at", "TEXT");
|
|
238
251
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
239
252
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
240
253
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -1073,6 +1086,25 @@ var init_schema = __esm(() => {
|
|
|
1073
1086
|
CREATE INDEX IF NOT EXISTS idx_agents_machine ON agents(machine_id);
|
|
1074
1087
|
|
|
1075
1088
|
INSERT OR IGNORE INTO _migrations (id) VALUES (41);
|
|
1089
|
+
`,
|
|
1090
|
+
`
|
|
1091
|
+
CREATE TABLE IF NOT EXISTS project_machine_paths (
|
|
1092
|
+
id TEXT PRIMARY KEY,
|
|
1093
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
1094
|
+
machine_id TEXT NOT NULL,
|
|
1095
|
+
path TEXT NOT NULL,
|
|
1096
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1097
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1098
|
+
UNIQUE(project_id, machine_id)
|
|
1099
|
+
);
|
|
1100
|
+
CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id);
|
|
1101
|
+
CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id);
|
|
1102
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (42);
|
|
1103
|
+
`,
|
|
1104
|
+
`
|
|
1105
|
+
ALTER TABLE tasks ADD COLUMN archived_at TEXT;
|
|
1106
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_archived ON tasks(archived_at) WHERE archived_at IS NOT NULL;
|
|
1107
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (43);
|
|
1076
1108
|
`
|
|
1077
1109
|
];
|
|
1078
1110
|
});
|
|
@@ -12544,6 +12576,59 @@ function listProjects(db) {
|
|
|
12544
12576
|
const d = db || getDatabase();
|
|
12545
12577
|
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
12546
12578
|
}
|
|
12579
|
+
function updateProject(id, input, db) {
|
|
12580
|
+
const d = db || getDatabase();
|
|
12581
|
+
const project = getProject(id, d);
|
|
12582
|
+
if (!project)
|
|
12583
|
+
throw new ProjectNotFoundError(id);
|
|
12584
|
+
const sets = ["updated_at = ?"];
|
|
12585
|
+
const params = [now()];
|
|
12586
|
+
if (input.name !== undefined) {
|
|
12587
|
+
sets.push("name = ?");
|
|
12588
|
+
params.push(input.name);
|
|
12589
|
+
}
|
|
12590
|
+
if (input.description !== undefined) {
|
|
12591
|
+
sets.push("description = ?");
|
|
12592
|
+
params.push(input.description);
|
|
12593
|
+
}
|
|
12594
|
+
if (input.task_list_id !== undefined) {
|
|
12595
|
+
sets.push("task_list_id = ?");
|
|
12596
|
+
params.push(input.task_list_id);
|
|
12597
|
+
}
|
|
12598
|
+
if (input.path !== undefined) {
|
|
12599
|
+
sets.push("path = ?");
|
|
12600
|
+
params.push(input.path);
|
|
12601
|
+
}
|
|
12602
|
+
params.push(id);
|
|
12603
|
+
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
12604
|
+
return getProject(id, d);
|
|
12605
|
+
}
|
|
12606
|
+
function renameProject(id, input, db) {
|
|
12607
|
+
const d = db || getDatabase();
|
|
12608
|
+
const project = getProject(id, d);
|
|
12609
|
+
if (!project)
|
|
12610
|
+
throw new ProjectNotFoundError(id);
|
|
12611
|
+
let taskListsUpdated = 0;
|
|
12612
|
+
const ts = now();
|
|
12613
|
+
if (input.new_slug !== undefined) {
|
|
12614
|
+
const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
|
|
12615
|
+
if (!normalised)
|
|
12616
|
+
throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
|
|
12617
|
+
const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
|
|
12618
|
+
if (conflict)
|
|
12619
|
+
throw new Error(`Slug "${normalised}" is already used by another project`);
|
|
12620
|
+
const oldSlug = project.task_list_id;
|
|
12621
|
+
d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
|
|
12622
|
+
if (oldSlug) {
|
|
12623
|
+
const result = d.run("UPDATE task_lists SET slug = ?, name = COALESCE(?, name), updated_at = ? WHERE project_id = ? AND slug = ?", [normalised, input.name ?? null, ts, id, oldSlug]);
|
|
12624
|
+
taskListsUpdated = result.changes;
|
|
12625
|
+
}
|
|
12626
|
+
}
|
|
12627
|
+
if (input.name !== undefined) {
|
|
12628
|
+
d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
|
|
12629
|
+
}
|
|
12630
|
+
return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
|
|
12631
|
+
}
|
|
12547
12632
|
function rowToSource(row) {
|
|
12548
12633
|
return {
|
|
12549
12634
|
...row,
|
|
@@ -12583,6 +12668,13 @@ function nextTaskShortId(projectId, db) {
|
|
|
12583
12668
|
const project = getProject(projectId, d);
|
|
12584
12669
|
if (!project || !project.task_prefix)
|
|
12585
12670
|
return null;
|
|
12671
|
+
const prefix = project.task_prefix;
|
|
12672
|
+
const prefixLen = prefix.length + 2;
|
|
12673
|
+
const maxRow = d.query(`SELECT MAX(CAST(SUBSTR(short_id, ?) AS INTEGER)) as max_counter FROM tasks WHERE short_id LIKE ?`).get(prefixLen, `${prefix}-%`);
|
|
12674
|
+
const syncedMax = maxRow?.max_counter ?? 0;
|
|
12675
|
+
if (syncedMax >= (project.task_counter ?? 0)) {
|
|
12676
|
+
d.run("UPDATE projects SET task_counter = ?, updated_at = ? WHERE id = ?", [syncedMax, now(), projectId]);
|
|
12677
|
+
}
|
|
12586
12678
|
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
|
|
12587
12679
|
const updated = getProject(projectId, d);
|
|
12588
12680
|
const padded = String(updated.task_counter).padStart(5, "0");
|
|
@@ -12591,6 +12683,7 @@ function nextTaskShortId(projectId, db) {
|
|
|
12591
12683
|
var init_projects = __esm(() => {
|
|
12592
12684
|
init_types2();
|
|
12593
12685
|
init_database();
|
|
12686
|
+
init_machines();
|
|
12594
12687
|
});
|
|
12595
12688
|
|
|
12596
12689
|
// src/lib/sync-utils.ts
|
|
@@ -13553,6 +13646,7 @@ var exports_tasks = {};
|
|
|
13553
13646
|
__export(exports_tasks, {
|
|
13554
13647
|
updateTask: () => updateTask,
|
|
13555
13648
|
unlockTask: () => unlockTask,
|
|
13649
|
+
unarchiveTask: () => unarchiveTask,
|
|
13556
13650
|
stealTask: () => stealTask,
|
|
13557
13651
|
startTask: () => startTask,
|
|
13558
13652
|
setTaskStatus: () => setTaskStatus,
|
|
@@ -13587,6 +13681,7 @@ __export(exports_tasks, {
|
|
|
13587
13681
|
claimNextTask: () => claimNextTask,
|
|
13588
13682
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
13589
13683
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
13684
|
+
archiveTasks: () => archiveTasks,
|
|
13590
13685
|
addDependency: () => addDependency
|
|
13591
13686
|
});
|
|
13592
13687
|
function rowToTask(row) {
|
|
@@ -13779,6 +13874,9 @@ function listTasks(filter = {}, db) {
|
|
|
13779
13874
|
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
13780
13875
|
} catch {}
|
|
13781
13876
|
}
|
|
13877
|
+
if (!filter.include_archived) {
|
|
13878
|
+
conditions.push("archived_at IS NULL");
|
|
13879
|
+
}
|
|
13782
13880
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
13783
13881
|
let limitClause = "";
|
|
13784
13882
|
if (filter.limit) {
|
|
@@ -14692,6 +14790,35 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
14692
14790
|
tx();
|
|
14693
14791
|
return { updated, failed };
|
|
14694
14792
|
}
|
|
14793
|
+
function archiveTasks(options, db) {
|
|
14794
|
+
const d = db || getDatabase();
|
|
14795
|
+
const conditions = ["archived_at IS NULL"];
|
|
14796
|
+
const params = [];
|
|
14797
|
+
const statuses = options.status ?? ["completed", "failed", "cancelled"];
|
|
14798
|
+
conditions.push(`status IN (${statuses.map(() => "?").join(",")})`);
|
|
14799
|
+
params.push(...statuses);
|
|
14800
|
+
if (options.project_id) {
|
|
14801
|
+
conditions.push("project_id = ?");
|
|
14802
|
+
params.push(options.project_id);
|
|
14803
|
+
}
|
|
14804
|
+
if (options.task_list_id) {
|
|
14805
|
+
conditions.push("task_list_id = ?");
|
|
14806
|
+
params.push(options.task_list_id);
|
|
14807
|
+
}
|
|
14808
|
+
if (options.older_than_days !== undefined) {
|
|
14809
|
+
const cutoff = new Date(Date.now() - options.older_than_days * 86400000).toISOString();
|
|
14810
|
+
conditions.push("updated_at < ?");
|
|
14811
|
+
params.push(cutoff);
|
|
14812
|
+
}
|
|
14813
|
+
const ts = now();
|
|
14814
|
+
const result = d.run(`UPDATE tasks SET archived_at = ? WHERE ${conditions.join(" AND ")}`, [ts, ...params]);
|
|
14815
|
+
return { archived: result.changes };
|
|
14816
|
+
}
|
|
14817
|
+
function unarchiveTask(id, db) {
|
|
14818
|
+
const d = db || getDatabase();
|
|
14819
|
+
d.run("UPDATE tasks SET archived_at = NULL WHERE id = ?", [id]);
|
|
14820
|
+
return getTask(id, d);
|
|
14821
|
+
}
|
|
14695
14822
|
function getOverdueTasks(projectId, db) {
|
|
14696
14823
|
const d = db || getDatabase();
|
|
14697
14824
|
const nowStr = new Date().toISOString();
|
|
@@ -22240,21 +22367,25 @@ function escapeFtsQuery(q) {
|
|
|
22240
22367
|
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
22241
22368
|
}
|
|
22242
22369
|
function searchTasks(options, projectId, taskListId, db) {
|
|
22243
|
-
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
22370
|
+
const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
|
|
22244
22371
|
const d = db || getDatabase();
|
|
22245
22372
|
clearExpiredLocks(d);
|
|
22246
22373
|
const params = [];
|
|
22247
22374
|
let sql;
|
|
22248
|
-
|
|
22249
|
-
|
|
22375
|
+
const raw = opts.query?.trim() ?? "";
|
|
22376
|
+
const q = raw === "*" ? "" : raw;
|
|
22377
|
+
if (hasFts(d) && q) {
|
|
22378
|
+
const ftsQuery = escapeFtsQuery(q);
|
|
22250
22379
|
sql = `SELECT t.* FROM tasks t
|
|
22251
22380
|
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
22252
22381
|
WHERE tasks_fts MATCH ?`;
|
|
22253
22382
|
params.push(ftsQuery);
|
|
22254
|
-
} else {
|
|
22255
|
-
const pattern = `%${
|
|
22383
|
+
} else if (q) {
|
|
22384
|
+
const pattern = `%${q}%`;
|
|
22256
22385
|
sql = `SELECT * FROM tasks t WHERE (t.title LIKE ? OR t.description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?))`;
|
|
22257
22386
|
params.push(pattern, pattern, pattern);
|
|
22387
|
+
} else {
|
|
22388
|
+
sql = `SELECT * FROM tasks t WHERE 1=1`;
|
|
22258
22389
|
}
|
|
22259
22390
|
if (opts.project_id) {
|
|
22260
22391
|
sql += " AND t.project_id = ?";
|
|
@@ -22308,7 +22439,7 @@ function searchTasks(options, projectId, taskListId, db) {
|
|
|
22308
22439
|
} else if (opts.is_blocked === false) {
|
|
22309
22440
|
sql += " AND t.id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
22310
22441
|
}
|
|
22311
|
-
if (hasFts(d) &&
|
|
22442
|
+
if (hasFts(d) && q) {
|
|
22312
22443
|
sql += ` ORDER BY bm25(tasks_fts),
|
|
22313
22444
|
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
22314
22445
|
t.created_at DESC`;
|
|
@@ -23497,12 +23628,17 @@ ${text}` }] };
|
|
|
23497
23628
|
if (!agent) {
|
|
23498
23629
|
return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
|
|
23499
23630
|
}
|
|
23631
|
+
const oldName = agent.name;
|
|
23500
23632
|
const updated = updateAgent(agent.id, { name: new_name });
|
|
23633
|
+
const db = getDatabase();
|
|
23634
|
+
const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
|
|
23635
|
+
const taskNote = tasksResult.changes > 0 ? `
|
|
23636
|
+
Updated assigned_to on ${tasksResult.changes} task(s).` : "";
|
|
23501
23637
|
return {
|
|
23502
23638
|
content: [{
|
|
23503
23639
|
type: "text",
|
|
23504
|
-
text: `Agent renamed: ${
|
|
23505
|
-
ID: ${updated.id}`
|
|
23640
|
+
text: `Agent renamed: ${oldName} -> ${updated.name}
|
|
23641
|
+
ID: ${updated.id}${taskNote}`
|
|
23506
23642
|
}]
|
|
23507
23643
|
};
|
|
23508
23644
|
} catch (e) {
|
|
@@ -23819,6 +23955,8 @@ function formatTaskDetail(task, maxDescriptionChars) {
|
|
|
23819
23955
|
parts.push(`Project: ${task.project_id}`);
|
|
23820
23956
|
if (task.plan_id)
|
|
23821
23957
|
parts.push(`Plan: ${task.plan_id}`);
|
|
23958
|
+
if (task.due_at)
|
|
23959
|
+
parts.push(`Due: ${task.due_at.slice(0, 10)}`);
|
|
23822
23960
|
if (task.tags.length > 0)
|
|
23823
23961
|
parts.push(`Tags: ${task.tags.join(", ")}`);
|
|
23824
23962
|
if (task.recurrence_rule)
|
|
@@ -23855,7 +23993,8 @@ if (shouldRegisterTool("create_task")) {
|
|
|
23855
23993
|
reason: exports_external.string().optional().describe("Why this task exists \u2014 context for agents picking it up"),
|
|
23856
23994
|
spawned_from_session: exports_external.string().optional().describe("Session ID that created this task (for tracing task lineage)"),
|
|
23857
23995
|
assigned_from_project: exports_external.string().optional().describe("Override: project ID the assigning agent is working from. Auto-detected from agent focus if omitted."),
|
|
23858
|
-
task_type: exports_external.string().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or any custom string")
|
|
23996
|
+
task_type: exports_external.string().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or any custom string"),
|
|
23997
|
+
due_date: exports_external.string().optional().describe("Due date as YYYY-MM-DD (e.g. '2026-04-15'). Stored as end-of-day ISO timestamp.")
|
|
23859
23998
|
}, async (params) => {
|
|
23860
23999
|
try {
|
|
23861
24000
|
if (!params.agent_id) {
|
|
@@ -23870,6 +24009,10 @@ if (shouldRegisterTool("create_task")) {
|
|
|
23870
24009
|
resolved["plan_id"] = resolveId(resolved["plan_id"], "plans");
|
|
23871
24010
|
if (resolved["task_list_id"])
|
|
23872
24011
|
resolved["task_list_id"] = resolveId(resolved["task_list_id"], "task_lists");
|
|
24012
|
+
if (resolved["due_date"]) {
|
|
24013
|
+
resolved["due_at"] = `${resolved["due_date"]}T23:59:59.000Z`;
|
|
24014
|
+
delete resolved["due_date"];
|
|
24015
|
+
}
|
|
23873
24016
|
if (!resolved["assigned_from_project"]) {
|
|
23874
24017
|
const focus = getAgentFocus(params.agent_id);
|
|
23875
24018
|
if (focus?.project_id) {
|
|
@@ -23905,7 +24048,8 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
23905
24048
|
limit: exports_external.number().optional(),
|
|
23906
24049
|
offset: exports_external.number().optional(),
|
|
23907
24050
|
summary_only: exports_external.boolean().optional().describe("When true, return only id, short_id, title, status, priority \u2014 minimal tokens for navigation"),
|
|
23908
|
-
cursor: exports_external.string().optional().describe("Opaque cursor from a prior response for stable pagination. Use next_cursor from the previous page. Mutually exclusive with offset.")
|
|
24051
|
+
cursor: exports_external.string().optional().describe("Opaque cursor from a prior response for stable pagination. Use next_cursor from the previous page. Mutually exclusive with offset."),
|
|
24052
|
+
include_archived: exports_external.boolean().optional().describe("When true, include archived tasks. Default: false.")
|
|
23909
24053
|
}, async (params) => {
|
|
23910
24054
|
try {
|
|
23911
24055
|
const { due_today, overdue, summary_only, ...rest } = params;
|
|
@@ -23940,7 +24084,8 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
23940
24084
|
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
23941
24085
|
const due = t.due_at ? ` due:${t.due_at.slice(0, 10)}` : "";
|
|
23942
24086
|
const recur = t.recurrence_rule ? " [\u21BB]" : "";
|
|
23943
|
-
|
|
24087
|
+
const list = t.task_list_id ? ` list:${t.task_list_id.slice(0, 8)}` : "";
|
|
24088
|
+
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${list}${lock}${due}${recur}`;
|
|
23944
24089
|
}).join(`
|
|
23945
24090
|
`);
|
|
23946
24091
|
const currentOffset = resolved.offset || 0;
|
|
@@ -24027,7 +24172,7 @@ Checklist (${done}/${task.checklist.length}):`);
|
|
|
24027
24172
|
if (shouldRegisterTool("update_task")) {
|
|
24028
24173
|
server.tool("update_task", "Update task fields. Version required for optimistic locking.", {
|
|
24029
24174
|
id: exports_external.string(),
|
|
24030
|
-
version: exports_external.number(),
|
|
24175
|
+
version: exports_external.union([exports_external.number(), exports_external.string()]).transform((v) => Number(v)),
|
|
24031
24176
|
title: exports_external.string().optional(),
|
|
24032
24177
|
description: exports_external.string().optional(),
|
|
24033
24178
|
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
@@ -24037,7 +24182,8 @@ if (shouldRegisterTool("update_task")) {
|
|
|
24037
24182
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
24038
24183
|
plan_id: exports_external.string().optional(),
|
|
24039
24184
|
task_list_id: exports_external.string().optional(),
|
|
24040
|
-
task_type: exports_external.string().nullable().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or custom. null to clear.")
|
|
24185
|
+
task_type: exports_external.string().nullable().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or custom. null to clear."),
|
|
24186
|
+
due_date: exports_external.string().nullable().optional().describe("Due date as YYYY-MM-DD. null to clear. Stored as end-of-day ISO timestamp.")
|
|
24041
24187
|
}, async ({ id, ...rest }) => {
|
|
24042
24188
|
try {
|
|
24043
24189
|
const resolvedId = resolveId(id);
|
|
@@ -24046,6 +24192,10 @@ if (shouldRegisterTool("update_task")) {
|
|
|
24046
24192
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
24047
24193
|
if (resolved.plan_id)
|
|
24048
24194
|
resolved.plan_id = resolveId(resolved.plan_id, "plans");
|
|
24195
|
+
if ("due_date" in resolved) {
|
|
24196
|
+
resolved["due_at"] = resolved["due_date"] ? `${resolved["due_date"]}T23:59:59.000Z` : null;
|
|
24197
|
+
delete resolved["due_date"];
|
|
24198
|
+
}
|
|
24049
24199
|
const task = updateTask(resolvedId, resolved);
|
|
24050
24200
|
return { content: [{ type: "text", text: `updated: ${formatTask(task)}` }] };
|
|
24051
24201
|
} catch (e) {
|
|
@@ -24281,6 +24431,29 @@ if (shouldRegisterTool("add_project_source")) {
|
|
|
24281
24431
|
}
|
|
24282
24432
|
});
|
|
24283
24433
|
}
|
|
24434
|
+
if (shouldRegisterTool("update_project")) {
|
|
24435
|
+
server.tool("update_project", "Update project fields: name, description, path, task_list_id. Use new_slug to rename the project slug (cascades to task_lists).", {
|
|
24436
|
+
id: exports_external.string().describe("Project ID"),
|
|
24437
|
+
name: exports_external.string().optional(),
|
|
24438
|
+
description: exports_external.string().optional(),
|
|
24439
|
+
path: exports_external.string().optional().describe("Update global path (use projects-path set for machine-local overrides)"),
|
|
24440
|
+
task_list_id: exports_external.string().optional(),
|
|
24441
|
+
new_slug: exports_external.string().optional().describe("Rename the project slug (kebab-case). Cascades to matching task_list slugs.")
|
|
24442
|
+
}, async ({ id, name, description, path, task_list_id, new_slug }) => {
|
|
24443
|
+
try {
|
|
24444
|
+
const resolvedId = resolveId(id, "projects");
|
|
24445
|
+
if (new_slug || name && !description && !path && !task_list_id) {
|
|
24446
|
+
const result = renameProject(resolvedId, { name, new_slug });
|
|
24447
|
+
const note = result.task_lists_updated > 0 ? ` (${result.task_lists_updated} task list(s) slug updated)` : "";
|
|
24448
|
+
return { content: [{ type: "text", text: `Project updated: ${result.project.id.slice(0, 8)} | ${result.project.name}${note}` }] };
|
|
24449
|
+
}
|
|
24450
|
+
const updated = updateProject(resolvedId, { name, description, path, task_list_id });
|
|
24451
|
+
return { content: [{ type: "text", text: `Project updated: ${updated.id.slice(0, 8)} | ${updated.name}${updated.path ? ` (${updated.path})` : ""}` }] };
|
|
24452
|
+
} catch (e) {
|
|
24453
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
24454
|
+
}
|
|
24455
|
+
});
|
|
24456
|
+
}
|
|
24284
24457
|
if (shouldRegisterTool("remove_project_source")) {
|
|
24285
24458
|
server.tool("remove_project_source", "Remove a data source from a project by source ID.", {
|
|
24286
24459
|
source_id: exports_external.string().describe("Source ID to remove")
|
|
@@ -24544,8 +24717,8 @@ if (shouldRegisterTool("delete_plan")) {
|
|
|
24544
24717
|
});
|
|
24545
24718
|
}
|
|
24546
24719
|
if (shouldRegisterTool("search_tasks")) {
|
|
24547
|
-
server.tool("search_tasks", "Full-text search across tasks with filters.", {
|
|
24548
|
-
query: exports_external.string(),
|
|
24720
|
+
server.tool("search_tasks", "Full-text search across tasks with filters. query is optional \u2014 if omitted, returns all tasks matching the given filters.", {
|
|
24721
|
+
query: exports_external.string().optional(),
|
|
24549
24722
|
project_id: exports_external.string().optional(),
|
|
24550
24723
|
task_list_id: exports_external.string().optional(),
|
|
24551
24724
|
status: exports_external.union([
|
|
@@ -24573,11 +24746,12 @@ if (shouldRegisterTool("search_tasks")) {
|
|
|
24573
24746
|
...filters
|
|
24574
24747
|
});
|
|
24575
24748
|
if (tasks.length === 0) {
|
|
24576
|
-
return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
|
|
24749
|
+
return { content: [{ type: "text", text: query ? `No tasks matching "${query}".` : "No tasks found." }] };
|
|
24577
24750
|
}
|
|
24578
24751
|
const text = tasks.map((t) => `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
24579
24752
|
`);
|
|
24580
|
-
|
|
24753
|
+
const label = query ? `${tasks.length} result(s) for "${query}"` : `${tasks.length} task(s)`;
|
|
24754
|
+
return { content: [{ type: "text", text: `${label}:
|
|
24581
24755
|
${text}` }] };
|
|
24582
24756
|
} catch (e) {
|
|
24583
24757
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -24673,9 +24847,15 @@ if (shouldRegisterTool("list_task_lists")) {
|
|
|
24673
24847
|
if (lists.length === 0) {
|
|
24674
24848
|
return { content: [{ type: "text", text: "No task lists found." }] };
|
|
24675
24849
|
}
|
|
24850
|
+
const db = getDatabase();
|
|
24676
24851
|
const text = lists.map((l) => {
|
|
24677
24852
|
const project = l.project_id ? ` (project: ${l.project_id.slice(0, 8)})` : "";
|
|
24678
|
-
|
|
24853
|
+
const counts = db.query(`SELECT COUNT(*) as total,
|
|
24854
|
+
SUM(CASE WHEN status IN ('pending','in_progress') THEN 1 ELSE 0 END) as active,
|
|
24855
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as done
|
|
24856
|
+
FROM tasks WHERE task_list_id = ? AND archived_at IS NULL`).get(l.id);
|
|
24857
|
+
const taskNote = ` [${counts.active} active, ${counts.done} done / ${counts.total} total]`;
|
|
24858
|
+
return `${l.id.slice(0, 8)} | ${l.name} [${l.slug}]${taskNote}${project}${l.description ? ` - ${l.description}` : ""}`;
|
|
24679
24859
|
}).join(`
|
|
24680
24860
|
`);
|
|
24681
24861
|
return { content: [{ type: "text", text: `${lists.length} task list(s):
|
|
@@ -25263,6 +25443,52 @@ if (shouldRegisterTool("set_reports_to")) {
|
|
|
25263
25443
|
}
|
|
25264
25444
|
});
|
|
25265
25445
|
}
|
|
25446
|
+
if (shouldRegisterTool("archive_completed")) {
|
|
25447
|
+
server.tool("archive_completed", "Archive completed/failed/cancelled tasks to reduce clutter. Archived tasks are hidden from list_tasks and search_tasks by default.", {
|
|
25448
|
+
project_id: exports_external.string().optional().describe("Scope to a project"),
|
|
25449
|
+
task_list_id: exports_external.string().optional().describe("Scope to a task list"),
|
|
25450
|
+
older_than_days: exports_external.number().optional().describe("Only archive tasks last updated more than N days ago. Default: all matching tasks."),
|
|
25451
|
+
status: exports_external.array(exports_external.enum(["completed", "failed", "cancelled"])).optional().describe("Statuses to archive. Default: completed, failed, cancelled."),
|
|
25452
|
+
dry_run: exports_external.boolean().optional().describe("Preview count without archiving. Default: false.")
|
|
25453
|
+
}, async ({ project_id, task_list_id, older_than_days, status, dry_run }) => {
|
|
25454
|
+
try {
|
|
25455
|
+
const filters = {};
|
|
25456
|
+
if (project_id)
|
|
25457
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
25458
|
+
if (task_list_id)
|
|
25459
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
25460
|
+
if (older_than_days !== undefined)
|
|
25461
|
+
filters.older_than_days = older_than_days;
|
|
25462
|
+
if (status)
|
|
25463
|
+
filters.status = status;
|
|
25464
|
+
if (dry_run) {
|
|
25465
|
+
const db = getDatabase();
|
|
25466
|
+
const statuses = status ?? ["completed", "failed", "cancelled"];
|
|
25467
|
+
const conditions = ["archived_at IS NULL", `status IN (${statuses.map(() => "?").join(",")})`];
|
|
25468
|
+
const params = [...statuses];
|
|
25469
|
+
if (filters.project_id) {
|
|
25470
|
+
conditions.push("project_id = ?");
|
|
25471
|
+
params.push(filters.project_id);
|
|
25472
|
+
}
|
|
25473
|
+
if (filters.task_list_id) {
|
|
25474
|
+
conditions.push("task_list_id = ?");
|
|
25475
|
+
params.push(filters.task_list_id);
|
|
25476
|
+
}
|
|
25477
|
+
if (older_than_days !== undefined) {
|
|
25478
|
+
const cutoff = new Date(Date.now() - older_than_days * 86400000).toISOString();
|
|
25479
|
+
conditions.push("updated_at < ?");
|
|
25480
|
+
params.push(cutoff);
|
|
25481
|
+
}
|
|
25482
|
+
const count = db.query(`SELECT COUNT(*) as c FROM tasks WHERE ${conditions.join(" AND ")}`).get(...params).c;
|
|
25483
|
+
return { content: [{ type: "text", text: `Dry run: would archive ${count} task(s).` }] };
|
|
25484
|
+
}
|
|
25485
|
+
const result = archiveTasks(filters);
|
|
25486
|
+
return { content: [{ type: "text", text: `Archived ${result.archived} task(s).` }] };
|
|
25487
|
+
} catch (e) {
|
|
25488
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25489
|
+
}
|
|
25490
|
+
});
|
|
25491
|
+
}
|
|
25266
25492
|
if (shouldRegisterTool("bulk_update_tasks")) {
|
|
25267
25493
|
server.tool("bulk_update_tasks", "Update multiple tasks at once. Two modes: (1) task_ids + shared fields \u2014 apply the same changes to all; (2) updates array \u2014 per-task fields, each entry has id plus any fields to update.", {
|
|
25268
25494
|
task_ids: exports_external.array(exports_external.string()).optional().describe("Task IDs to update with the same fields (mode 1)"),
|
|
@@ -25375,6 +25601,26 @@ if (shouldRegisterTool("get_task_stats")) {
|
|
|
25375
25601
|
if (agent_id)
|
|
25376
25602
|
filters.agent_id = agent_id;
|
|
25377
25603
|
const stats = getTaskStats(Object.keys(filters).length > 0 ? filters : undefined);
|
|
25604
|
+
if (!task_list_id) {
|
|
25605
|
+
const db = getDatabase();
|
|
25606
|
+
const listRows = db.query(`SELECT tl.id, tl.name, tl.slug,
|
|
25607
|
+
COUNT(t.id) as total,
|
|
25608
|
+
SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) as completed,
|
|
25609
|
+
SUM(CASE WHEN t.status IN ('pending','in_progress') THEN 1 ELSE 0 END) as active
|
|
25610
|
+
FROM task_lists tl
|
|
25611
|
+
LEFT JOIN tasks t ON t.task_list_id = tl.id ${filters.project_id ? "AND t.project_id = ?" : ""}
|
|
25612
|
+
${filters.project_id ? "WHERE tl.project_id = ?" : ""}
|
|
25613
|
+
GROUP BY tl.id ORDER BY tl.name`).all(...filters.project_id ? [filters.project_id, filters.project_id] : []);
|
|
25614
|
+
stats.by_task_list = listRows.map((r) => ({
|
|
25615
|
+
id: r.id.slice(0, 8),
|
|
25616
|
+
name: r.name,
|
|
25617
|
+
slug: r.slug,
|
|
25618
|
+
total: r.total,
|
|
25619
|
+
completed: r.completed,
|
|
25620
|
+
active: r.active,
|
|
25621
|
+
completion_rate: r.total > 0 ? Math.round(r.completed / r.total * 100) : 0
|
|
25622
|
+
}));
|
|
25623
|
+
}
|
|
25378
25624
|
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
25379
25625
|
} catch (e) {
|
|
25380
25626
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/agents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMzE,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,KAAK,OAAO,GAAG;IACb,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;IACpC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;CAC5D,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/agents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMzE,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,KAAK,OAAO,GAAG;IACb,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;IACpC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;CAC5D,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,OAAO,GAAG,IAAI,CAuajJ"}
|