@hasna/todos 0.9.69 → 0.9.70
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/dashboard/dist/assets/index-DWpVlvWb.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/index.js +311 -115
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.js +194 -82
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +311 -115
- package/dist/server/index.js +159 -89
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-BXQ39iMX.css +0 -1
- /package/dashboard/dist/assets/{index-B-w1tUlm.js → index-DVzieYOj.js} +0 -0
package/dist/mcp/index.js
CHANGED
|
@@ -533,6 +533,58 @@ var init_database = __esm(() => {
|
|
|
533
533
|
ALTER TABLE task_comments ADD COLUMN type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note'));
|
|
534
534
|
ALTER TABLE task_comments ADD COLUMN progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100));
|
|
535
535
|
INSERT OR IGNORE INTO _migrations (id) VALUES (14);
|
|
536
|
+
`,
|
|
537
|
+
`
|
|
538
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
539
|
+
task_id UNINDEXED,
|
|
540
|
+
title,
|
|
541
|
+
description,
|
|
542
|
+
tags,
|
|
543
|
+
tokenize='unicode61 remove_diacritics 2'
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
547
|
+
SELECT t.rowid, t.id, t.title, COALESCE(t.description, ''),
|
|
548
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = t.id), '')
|
|
549
|
+
FROM tasks t;
|
|
550
|
+
|
|
551
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_ai AFTER INSERT ON tasks BEGIN
|
|
552
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
553
|
+
VALUES (new.rowid, new.id, new.title, COALESCE(new.description, ''), '');
|
|
554
|
+
END;
|
|
555
|
+
|
|
556
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_ad AFTER DELETE ON tasks BEGIN
|
|
557
|
+
DELETE FROM tasks_fts WHERE rowid = old.rowid;
|
|
558
|
+
END;
|
|
559
|
+
|
|
560
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_au AFTER UPDATE OF title, description ON tasks BEGIN
|
|
561
|
+
DELETE FROM tasks_fts WHERE rowid = old.rowid;
|
|
562
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
563
|
+
SELECT new.rowid, new.id, new.title, COALESCE(new.description, ''),
|
|
564
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = new.id), '');
|
|
565
|
+
END;
|
|
566
|
+
|
|
567
|
+
CREATE TRIGGER IF NOT EXISTS task_tags_fts_ai AFTER INSERT ON task_tags BEGIN
|
|
568
|
+
DELETE FROM tasks_fts WHERE rowid = (SELECT rowid FROM tasks WHERE id = new.task_id);
|
|
569
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
570
|
+
SELECT t.rowid, t.id, t.title, COALESCE(t.description, ''),
|
|
571
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = t.id), '')
|
|
572
|
+
FROM tasks t WHERE t.id = new.task_id;
|
|
573
|
+
END;
|
|
574
|
+
|
|
575
|
+
CREATE TRIGGER IF NOT EXISTS task_tags_fts_ad AFTER DELETE ON task_tags BEGIN
|
|
576
|
+
DELETE FROM tasks_fts WHERE rowid = (SELECT rowid FROM tasks WHERE id = old.task_id);
|
|
577
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
578
|
+
SELECT t.rowid, t.id, t.title, COALESCE(t.description, ''),
|
|
579
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = t.id), '')
|
|
580
|
+
FROM tasks t WHERE t.id = old.task_id;
|
|
581
|
+
END;
|
|
582
|
+
|
|
583
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (15);
|
|
584
|
+
`,
|
|
585
|
+
`
|
|
586
|
+
ALTER TABLE tasks ADD COLUMN spawns_template_id TEXT REFERENCES task_templates(id) ON DELETE SET NULL;
|
|
587
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (16);
|
|
536
588
|
`
|
|
537
589
|
];
|
|
538
590
|
});
|
|
@@ -615,6 +667,73 @@ var init_webhooks = __esm(() => {
|
|
|
615
667
|
init_database();
|
|
616
668
|
});
|
|
617
669
|
|
|
670
|
+
// src/db/templates.ts
|
|
671
|
+
var exports_templates = {};
|
|
672
|
+
__export(exports_templates, {
|
|
673
|
+
taskFromTemplate: () => taskFromTemplate,
|
|
674
|
+
listTemplates: () => listTemplates,
|
|
675
|
+
getTemplate: () => getTemplate,
|
|
676
|
+
deleteTemplate: () => deleteTemplate,
|
|
677
|
+
createTemplate: () => createTemplate
|
|
678
|
+
});
|
|
679
|
+
function rowToTemplate(row) {
|
|
680
|
+
return {
|
|
681
|
+
...row,
|
|
682
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
683
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
684
|
+
priority: row.priority || "medium"
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function createTemplate(input, db) {
|
|
688
|
+
const d = db || getDatabase();
|
|
689
|
+
const id = uuid();
|
|
690
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
691
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
692
|
+
id,
|
|
693
|
+
input.name,
|
|
694
|
+
input.title_pattern,
|
|
695
|
+
input.description || null,
|
|
696
|
+
input.priority || "medium",
|
|
697
|
+
JSON.stringify(input.tags || []),
|
|
698
|
+
input.project_id || null,
|
|
699
|
+
input.plan_id || null,
|
|
700
|
+
JSON.stringify(input.metadata || {}),
|
|
701
|
+
now()
|
|
702
|
+
]);
|
|
703
|
+
return getTemplate(id, d);
|
|
704
|
+
}
|
|
705
|
+
function getTemplate(id, db) {
|
|
706
|
+
const d = db || getDatabase();
|
|
707
|
+
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(id);
|
|
708
|
+
return row ? rowToTemplate(row) : null;
|
|
709
|
+
}
|
|
710
|
+
function listTemplates(db) {
|
|
711
|
+
const d = db || getDatabase();
|
|
712
|
+
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
713
|
+
}
|
|
714
|
+
function deleteTemplate(id, db) {
|
|
715
|
+
const d = db || getDatabase();
|
|
716
|
+
return d.run("DELETE FROM task_templates WHERE id = ?", [id]).changes > 0;
|
|
717
|
+
}
|
|
718
|
+
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
719
|
+
const t = getTemplate(templateId, db);
|
|
720
|
+
if (!t)
|
|
721
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
722
|
+
return {
|
|
723
|
+
title: overrides.title || t.title_pattern,
|
|
724
|
+
description: overrides.description ?? t.description ?? undefined,
|
|
725
|
+
priority: overrides.priority ?? t.priority,
|
|
726
|
+
tags: overrides.tags ?? t.tags,
|
|
727
|
+
project_id: overrides.project_id ?? t.project_id ?? undefined,
|
|
728
|
+
plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
|
|
729
|
+
metadata: overrides.metadata ?? t.metadata,
|
|
730
|
+
...overrides
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
var init_templates = __esm(() => {
|
|
734
|
+
init_database();
|
|
735
|
+
});
|
|
736
|
+
|
|
618
737
|
// src/db/agents.ts
|
|
619
738
|
var exports_agents = {};
|
|
620
739
|
__export(exports_agents, {
|
|
@@ -758,73 +877,6 @@ var init_agents = __esm(() => {
|
|
|
758
877
|
init_database();
|
|
759
878
|
});
|
|
760
879
|
|
|
761
|
-
// src/db/templates.ts
|
|
762
|
-
var exports_templates = {};
|
|
763
|
-
__export(exports_templates, {
|
|
764
|
-
taskFromTemplate: () => taskFromTemplate,
|
|
765
|
-
listTemplates: () => listTemplates,
|
|
766
|
-
getTemplate: () => getTemplate,
|
|
767
|
-
deleteTemplate: () => deleteTemplate,
|
|
768
|
-
createTemplate: () => createTemplate
|
|
769
|
-
});
|
|
770
|
-
function rowToTemplate(row) {
|
|
771
|
-
return {
|
|
772
|
-
...row,
|
|
773
|
-
tags: JSON.parse(row.tags || "[]"),
|
|
774
|
-
metadata: JSON.parse(row.metadata || "{}"),
|
|
775
|
-
priority: row.priority || "medium"
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
function createTemplate(input, db) {
|
|
779
|
-
const d = db || getDatabase();
|
|
780
|
-
const id = uuid();
|
|
781
|
-
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
782
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
783
|
-
id,
|
|
784
|
-
input.name,
|
|
785
|
-
input.title_pattern,
|
|
786
|
-
input.description || null,
|
|
787
|
-
input.priority || "medium",
|
|
788
|
-
JSON.stringify(input.tags || []),
|
|
789
|
-
input.project_id || null,
|
|
790
|
-
input.plan_id || null,
|
|
791
|
-
JSON.stringify(input.metadata || {}),
|
|
792
|
-
now()
|
|
793
|
-
]);
|
|
794
|
-
return getTemplate(id, d);
|
|
795
|
-
}
|
|
796
|
-
function getTemplate(id, db) {
|
|
797
|
-
const d = db || getDatabase();
|
|
798
|
-
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(id);
|
|
799
|
-
return row ? rowToTemplate(row) : null;
|
|
800
|
-
}
|
|
801
|
-
function listTemplates(db) {
|
|
802
|
-
const d = db || getDatabase();
|
|
803
|
-
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
804
|
-
}
|
|
805
|
-
function deleteTemplate(id, db) {
|
|
806
|
-
const d = db || getDatabase();
|
|
807
|
-
return d.run("DELETE FROM task_templates WHERE id = ?", [id]).changes > 0;
|
|
808
|
-
}
|
|
809
|
-
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
810
|
-
const t = getTemplate(templateId, db);
|
|
811
|
-
if (!t)
|
|
812
|
-
throw new Error(`Template not found: ${templateId}`);
|
|
813
|
-
return {
|
|
814
|
-
title: overrides.title || t.title_pattern,
|
|
815
|
-
description: overrides.description ?? t.description ?? undefined,
|
|
816
|
-
priority: overrides.priority ?? t.priority,
|
|
817
|
-
tags: overrides.tags ?? t.tags,
|
|
818
|
-
project_id: overrides.project_id ?? t.project_id ?? undefined,
|
|
819
|
-
plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
|
|
820
|
-
metadata: overrides.metadata ?? t.metadata,
|
|
821
|
-
...overrides
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
var init_templates = __esm(() => {
|
|
825
|
-
init_database();
|
|
826
|
-
});
|
|
827
|
-
|
|
828
880
|
// src/mcp/index.ts
|
|
829
881
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
830
882
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -5222,6 +5274,7 @@ function nextOccurrence(rule, from) {
|
|
|
5222
5274
|
|
|
5223
5275
|
// src/db/tasks.ts
|
|
5224
5276
|
init_webhooks();
|
|
5277
|
+
init_templates();
|
|
5225
5278
|
function rowToTask(row) {
|
|
5226
5279
|
return {
|
|
5227
5280
|
...row,
|
|
@@ -5252,8 +5305,8 @@ function createTask(input, db) {
|
|
|
5252
5305
|
const tags = input.tags || [];
|
|
5253
5306
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
5254
5307
|
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
5255
|
-
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)
|
|
5256
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5308
|
+
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, spawns_template_id)
|
|
5309
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5257
5310
|
id,
|
|
5258
5311
|
shortId,
|
|
5259
5312
|
input.project_id || null,
|
|
@@ -5278,7 +5331,8 @@ function createTask(input, db) {
|
|
|
5278
5331
|
null,
|
|
5279
5332
|
null,
|
|
5280
5333
|
input.recurrence_rule || null,
|
|
5281
|
-
input.recurrence_parent_id || null
|
|
5334
|
+
input.recurrence_parent_id || null,
|
|
5335
|
+
input.spawns_template_id || null
|
|
5282
5336
|
]);
|
|
5283
5337
|
if (tags.length > 0) {
|
|
5284
5338
|
insertTaskTags(id, tags, d);
|
|
@@ -5385,19 +5439,25 @@ function listTasks(filter = {}, db) {
|
|
|
5385
5439
|
} else if (filter.has_recurrence === false) {
|
|
5386
5440
|
conditions.push("recurrence_rule IS NULL");
|
|
5387
5441
|
}
|
|
5442
|
+
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
5443
|
+
if (filter.cursor) {
|
|
5444
|
+
try {
|
|
5445
|
+
const decoded = JSON.parse(Buffer.from(filter.cursor, "base64").toString("utf8"));
|
|
5446
|
+
conditions.push(`(${PRIORITY_RANK} > ? OR (${PRIORITY_RANK} = ? AND created_at < ?) OR (${PRIORITY_RANK} = ? AND created_at = ? AND id > ?))`);
|
|
5447
|
+
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
5448
|
+
} catch {}
|
|
5449
|
+
}
|
|
5388
5450
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5389
5451
|
let limitClause = "";
|
|
5390
5452
|
if (filter.limit) {
|
|
5391
5453
|
limitClause = " LIMIT ?";
|
|
5392
5454
|
params.push(filter.limit);
|
|
5393
|
-
if (filter.offset) {
|
|
5455
|
+
if (!filter.cursor && filter.offset) {
|
|
5394
5456
|
limitClause += " OFFSET ?";
|
|
5395
5457
|
params.push(filter.offset);
|
|
5396
5458
|
}
|
|
5397
5459
|
}
|
|
5398
|
-
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY
|
|
5399
|
-
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
5400
|
-
created_at DESC${limitClause}`).all(...params);
|
|
5460
|
+
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC${limitClause}`).all(...params);
|
|
5401
5461
|
return rows.map(rowToTask);
|
|
5402
5462
|
}
|
|
5403
5463
|
function countTasks(filter = {}, db) {
|
|
@@ -5642,10 +5702,25 @@ function completeTask(id, agentId, db, options) {
|
|
|
5642
5702
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
5643
5703
|
spawnedTask = spawnNextRecurrence(task, d);
|
|
5644
5704
|
}
|
|
5705
|
+
let spawnedFromTemplate = null;
|
|
5706
|
+
if (task.spawns_template_id) {
|
|
5707
|
+
try {
|
|
5708
|
+
const input = taskFromTemplate(task.spawns_template_id, {
|
|
5709
|
+
project_id: task.project_id ?? undefined,
|
|
5710
|
+
plan_id: task.plan_id ?? undefined,
|
|
5711
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
5712
|
+
assigned_to: task.assigned_to ?? undefined
|
|
5713
|
+
}, d);
|
|
5714
|
+
spawnedFromTemplate = createTask(input, d);
|
|
5715
|
+
} catch {}
|
|
5716
|
+
}
|
|
5645
5717
|
const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
|
|
5646
5718
|
if (spawnedTask) {
|
|
5647
5719
|
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
5648
5720
|
}
|
|
5721
|
+
if (spawnedFromTemplate) {
|
|
5722
|
+
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
5723
|
+
}
|
|
5649
5724
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
5650
5725
|
}
|
|
5651
5726
|
function lockTask(id, agentId, db) {
|
|
@@ -5853,11 +5928,21 @@ function getNextTask(agentId, filters, db) {
|
|
|
5853
5928
|
}
|
|
5854
5929
|
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')");
|
|
5855
5930
|
const where = conditions.join(" AND ");
|
|
5931
|
+
let recentProjectIds = [];
|
|
5932
|
+
if (agentId) {
|
|
5933
|
+
const recentRows = d.query(`SELECT DISTINCT project_id FROM tasks WHERE assigned_to = ? AND status = 'completed' AND project_id IS NOT NULL ORDER BY completed_at DESC LIMIT 3`).all(agentId);
|
|
5934
|
+
recentProjectIds = recentRows.map((r) => r.project_id);
|
|
5935
|
+
}
|
|
5856
5936
|
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
5857
5937
|
if (agentId) {
|
|
5858
5938
|
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
5859
5939
|
params.push(agentId);
|
|
5860
5940
|
}
|
|
5941
|
+
if (recentProjectIds.length > 0) {
|
|
5942
|
+
const placeholders = recentProjectIds.map(() => "?").join(",");
|
|
5943
|
+
sql += `CASE WHEN project_id IN (${placeholders}) THEN 0 ELSE 1 END, `;
|
|
5944
|
+
params.push(...recentProjectIds);
|
|
5945
|
+
}
|
|
5861
5946
|
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`;
|
|
5862
5947
|
const row = d.query(sql).get(...params);
|
|
5863
5948
|
return row ? rowToTask(row) : null;
|
|
@@ -6363,68 +6448,95 @@ function rowToTask2(row) {
|
|
|
6363
6448
|
requires_approval: Boolean(row.requires_approval)
|
|
6364
6449
|
};
|
|
6365
6450
|
}
|
|
6451
|
+
function hasFts(db) {
|
|
6452
|
+
try {
|
|
6453
|
+
const result = db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='tasks_fts'").get();
|
|
6454
|
+
return result !== null;
|
|
6455
|
+
} catch {
|
|
6456
|
+
return false;
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
function escapeFtsQuery(q) {
|
|
6460
|
+
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
6461
|
+
}
|
|
6366
6462
|
function searchTasks(options, projectId, taskListId, db) {
|
|
6367
6463
|
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
6368
6464
|
const d = db || getDatabase();
|
|
6369
6465
|
clearExpiredLocks(d);
|
|
6370
|
-
const
|
|
6371
|
-
let sql
|
|
6372
|
-
|
|
6466
|
+
const params = [];
|
|
6467
|
+
let sql;
|
|
6468
|
+
if (hasFts(d) && opts.query.trim()) {
|
|
6469
|
+
const ftsQuery = escapeFtsQuery(opts.query);
|
|
6470
|
+
sql = `SELECT t.* FROM tasks t
|
|
6471
|
+
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
6472
|
+
WHERE tasks_fts MATCH ?`;
|
|
6473
|
+
params.push(ftsQuery);
|
|
6474
|
+
} else {
|
|
6475
|
+
const pattern = `%${opts.query}%`;
|
|
6476
|
+
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 ?))`;
|
|
6477
|
+
params.push(pattern, pattern, pattern);
|
|
6478
|
+
}
|
|
6373
6479
|
if (opts.project_id) {
|
|
6374
|
-
sql += " AND project_id = ?";
|
|
6480
|
+
sql += " AND t.project_id = ?";
|
|
6375
6481
|
params.push(opts.project_id);
|
|
6376
6482
|
}
|
|
6377
6483
|
if (opts.task_list_id) {
|
|
6378
|
-
sql += " AND task_list_id = ?";
|
|
6484
|
+
sql += " AND t.task_list_id = ?";
|
|
6379
6485
|
params.push(opts.task_list_id);
|
|
6380
6486
|
}
|
|
6381
6487
|
if (opts.status) {
|
|
6382
6488
|
if (Array.isArray(opts.status)) {
|
|
6383
|
-
sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
|
|
6489
|
+
sql += ` AND t.status IN (${opts.status.map(() => "?").join(",")})`;
|
|
6384
6490
|
params.push(...opts.status);
|
|
6385
6491
|
} else {
|
|
6386
|
-
sql += " AND status = ?";
|
|
6492
|
+
sql += " AND t.status = ?";
|
|
6387
6493
|
params.push(opts.status);
|
|
6388
6494
|
}
|
|
6389
6495
|
}
|
|
6390
6496
|
if (opts.priority) {
|
|
6391
6497
|
if (Array.isArray(opts.priority)) {
|
|
6392
|
-
sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
6498
|
+
sql += ` AND t.priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
6393
6499
|
params.push(...opts.priority);
|
|
6394
6500
|
} else {
|
|
6395
|
-
sql += " AND priority = ?";
|
|
6501
|
+
sql += " AND t.priority = ?";
|
|
6396
6502
|
params.push(opts.priority);
|
|
6397
6503
|
}
|
|
6398
6504
|
}
|
|
6399
6505
|
if (opts.assigned_to) {
|
|
6400
|
-
sql += " AND assigned_to = ?";
|
|
6506
|
+
sql += " AND t.assigned_to = ?";
|
|
6401
6507
|
params.push(opts.assigned_to);
|
|
6402
6508
|
}
|
|
6403
6509
|
if (opts.agent_id) {
|
|
6404
|
-
sql += " AND agent_id = ?";
|
|
6510
|
+
sql += " AND t.agent_id = ?";
|
|
6405
6511
|
params.push(opts.agent_id);
|
|
6406
6512
|
}
|
|
6407
6513
|
if (opts.created_after) {
|
|
6408
|
-
sql += " AND created_at > ?";
|
|
6514
|
+
sql += " AND t.created_at > ?";
|
|
6409
6515
|
params.push(opts.created_after);
|
|
6410
6516
|
}
|
|
6411
6517
|
if (opts.updated_after) {
|
|
6412
|
-
sql += " AND updated_at > ?";
|
|
6518
|
+
sql += " AND t.updated_at > ?";
|
|
6413
6519
|
params.push(opts.updated_after);
|
|
6414
6520
|
}
|
|
6415
6521
|
if (opts.has_dependencies === true) {
|
|
6416
|
-
sql += " AND id IN (SELECT task_id FROM task_dependencies)";
|
|
6522
|
+
sql += " AND t.id IN (SELECT task_id FROM task_dependencies)";
|
|
6417
6523
|
} else if (opts.has_dependencies === false) {
|
|
6418
|
-
sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
6524
|
+
sql += " AND t.id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
6419
6525
|
}
|
|
6420
6526
|
if (opts.is_blocked === true) {
|
|
6421
|
-
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')";
|
|
6527
|
+
sql += " AND t.id IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
6422
6528
|
} else if (opts.is_blocked === false) {
|
|
6423
|
-
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')";
|
|
6529
|
+
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')";
|
|
6530
|
+
}
|
|
6531
|
+
if (hasFts(d) && opts.query.trim()) {
|
|
6532
|
+
sql += ` ORDER BY bm25(tasks_fts),
|
|
6533
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
6534
|
+
t.created_at DESC`;
|
|
6535
|
+
} else {
|
|
6536
|
+
sql += ` ORDER BY
|
|
6537
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
6538
|
+
t.created_at DESC`;
|
|
6424
6539
|
}
|
|
6425
|
-
sql += ` ORDER BY
|
|
6426
|
-
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
6427
|
-
created_at DESC`;
|
|
6428
6540
|
const rows = d.query(sql).all(...params);
|
|
6429
6541
|
return rows.map(rowToTask2);
|
|
6430
6542
|
}
|
|
@@ -6974,7 +7086,9 @@ var MINIMAL_TOOLS = new Set([
|
|
|
6974
7086
|
"get_task",
|
|
6975
7087
|
"start_task",
|
|
6976
7088
|
"add_comment",
|
|
6977
|
-
"get_next_task"
|
|
7089
|
+
"get_next_task",
|
|
7090
|
+
"bootstrap",
|
|
7091
|
+
"get_tasks_changed_since"
|
|
6978
7092
|
]);
|
|
6979
7093
|
var STANDARD_EXCLUDED = new Set([
|
|
6980
7094
|
"get_org_chart",
|
|
@@ -7055,15 +7169,17 @@ function formatTask(task) {
|
|
|
7055
7169
|
const recur = task.recurrence_rule ? ` [\u21BB]` : "";
|
|
7056
7170
|
return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
|
|
7057
7171
|
}
|
|
7058
|
-
function formatTaskDetail(task) {
|
|
7172
|
+
function formatTaskDetail(task, maxDescriptionChars) {
|
|
7059
7173
|
const parts = [
|
|
7060
7174
|
`ID: ${task.id}`,
|
|
7061
7175
|
`Title: ${task.title}`,
|
|
7062
7176
|
`Status: ${task.status}`,
|
|
7063
7177
|
`Priority: ${task.priority}`
|
|
7064
7178
|
];
|
|
7065
|
-
if (task.description)
|
|
7066
|
-
|
|
7179
|
+
if (task.description) {
|
|
7180
|
+
const desc = maxDescriptionChars && task.description.length > maxDescriptionChars ? task.description.slice(0, maxDescriptionChars) + "\u2026" : task.description;
|
|
7181
|
+
parts.push(`Description: ${desc}`);
|
|
7182
|
+
}
|
|
7067
7183
|
if (task.assigned_to)
|
|
7068
7184
|
parts.push(`Assigned to: ${task.assigned_to}`);
|
|
7069
7185
|
if (task.agent_id)
|
|
@@ -7107,7 +7223,8 @@ if (shouldRegisterTool("create_task")) {
|
|
|
7107
7223
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
7108
7224
|
estimated_minutes: exports_external.number().optional(),
|
|
7109
7225
|
requires_approval: exports_external.boolean().optional(),
|
|
7110
|
-
recurrence_rule: exports_external.string().optional()
|
|
7226
|
+
recurrence_rule: exports_external.string().optional(),
|
|
7227
|
+
spawns_template_id: exports_external.string().optional().describe("Template ID to auto-create as next task when this task is completed (pipeline/handoff chains)")
|
|
7111
7228
|
}, async (params) => {
|
|
7112
7229
|
try {
|
|
7113
7230
|
const resolved = { ...params };
|
|
@@ -7127,7 +7244,7 @@ if (shouldRegisterTool("create_task")) {
|
|
|
7127
7244
|
});
|
|
7128
7245
|
}
|
|
7129
7246
|
if (shouldRegisterTool("list_tasks")) {
|
|
7130
|
-
server.tool("list_tasks", "List tasks with optional filters and pagination.", {
|
|
7247
|
+
server.tool("list_tasks", "List tasks with optional filters and pagination. Default limit is 50 \u2014 use offset to page through results.", {
|
|
7131
7248
|
project_id: exports_external.string().optional(),
|
|
7132
7249
|
status: exports_external.union([
|
|
7133
7250
|
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
@@ -7145,11 +7262,15 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
7145
7262
|
due_today: exports_external.boolean().optional(),
|
|
7146
7263
|
overdue: exports_external.boolean().optional(),
|
|
7147
7264
|
limit: exports_external.number().optional(),
|
|
7148
|
-
offset: exports_external.number().optional()
|
|
7265
|
+
offset: exports_external.number().optional(),
|
|
7266
|
+
summary_only: exports_external.boolean().optional().describe("When true, return only id, short_id, title, status, priority \u2014 minimal tokens for navigation"),
|
|
7267
|
+
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.")
|
|
7149
7268
|
}, async (params) => {
|
|
7150
7269
|
try {
|
|
7151
|
-
const { due_today, overdue, ...rest } = params;
|
|
7270
|
+
const { due_today, overdue, summary_only, ...rest } = params;
|
|
7152
7271
|
const resolved = { ...rest };
|
|
7272
|
+
if (resolved.limit === undefined)
|
|
7273
|
+
resolved.limit = 50;
|
|
7153
7274
|
if (resolved.project_id)
|
|
7154
7275
|
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
7155
7276
|
if (resolved.plan_id)
|
|
@@ -7171,6 +7292,9 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
7171
7292
|
return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
|
|
7172
7293
|
}
|
|
7173
7294
|
const text = tasks.map((t) => {
|
|
7295
|
+
if (summary_only) {
|
|
7296
|
+
return `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.priority} ${t.title}`;
|
|
7297
|
+
}
|
|
7174
7298
|
const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
|
|
7175
7299
|
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
7176
7300
|
const due = t.due_at ? ` due:${t.due_at.slice(0, 10)}` : "";
|
|
@@ -7178,10 +7302,23 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
7178
7302
|
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}${due}${recur}`;
|
|
7179
7303
|
}).join(`
|
|
7180
7304
|
`);
|
|
7181
|
-
const
|
|
7182
|
-
|
|
7305
|
+
const currentOffset = resolved.offset || 0;
|
|
7306
|
+
const hasMore = total > (resolved.cursor ? tasks.length : currentOffset + tasks.length);
|
|
7307
|
+
let paginationNote = `
|
|
7308
|
+
(showing ${tasks.length} of ${total}`;
|
|
7309
|
+
if (hasMore) {
|
|
7310
|
+
if (resolved.cursor || tasks.length > 0) {
|
|
7311
|
+
const last = tasks[tasks.length - 1];
|
|
7312
|
+
const priorityRank = { critical: 0, high: 1, medium: 2, low: 3 }[last.priority] ?? 3;
|
|
7313
|
+
const cursorPayload = Buffer.from(JSON.stringify({ p: priorityRank, c: last.created_at, i: last.id })).toString("base64");
|
|
7314
|
+
paginationNote += ` \u2014 next_cursor: ${cursorPayload}`;
|
|
7315
|
+
} else {
|
|
7316
|
+
paginationNote += ` \u2014 use offset: ${currentOffset + tasks.length} to get next page`;
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
paginationNote += ")";
|
|
7183
7320
|
return { content: [{ type: "text", text: `${tasks.length} task(s):
|
|
7184
|
-
${text}${
|
|
7321
|
+
${text}${paginationNote}` }] };
|
|
7185
7322
|
} catch (e) {
|
|
7186
7323
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7187
7324
|
}
|
|
@@ -7189,14 +7326,15 @@ ${text}${pagination}` }] };
|
|
|
7189
7326
|
}
|
|
7190
7327
|
if (shouldRegisterTool("get_task")) {
|
|
7191
7328
|
server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
|
|
7192
|
-
id: exports_external.string()
|
|
7193
|
-
|
|
7329
|
+
id: exports_external.string(),
|
|
7330
|
+
max_description_chars: exports_external.number().optional().describe("Truncate description to this many characters (default: unlimited). Use 300-500 for quick checks.")
|
|
7331
|
+
}, async ({ id, max_description_chars }) => {
|
|
7194
7332
|
try {
|
|
7195
7333
|
const resolvedId = resolveId(id);
|
|
7196
7334
|
const task = getTaskWithRelations(resolvedId);
|
|
7197
7335
|
if (!task)
|
|
7198
7336
|
return { content: [{ type: "text", text: `Task not found: ${id}` }], isError: true };
|
|
7199
|
-
const parts = [formatTaskDetail(task)];
|
|
7337
|
+
const parts = [formatTaskDetail(task, max_description_chars)];
|
|
7200
7338
|
if (task.subtasks.length > 0) {
|
|
7201
7339
|
parts.push(`
|
|
7202
7340
|
Subtasks (${task.subtasks.length}):`);
|
|
@@ -8424,7 +8562,7 @@ if (shouldRegisterTool("get_next_task")) {
|
|
|
8424
8562
|
return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
|
|
8425
8563
|
}
|
|
8426
8564
|
return { content: [{ type: "text", text: `next: ${formatTask(task)}
|
|
8427
|
-
${formatTaskDetail(task)}` }] };
|
|
8565
|
+
${formatTaskDetail(task, 300)}` }] };
|
|
8428
8566
|
} catch (e) {
|
|
8429
8567
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8430
8568
|
}
|
|
@@ -8510,7 +8648,7 @@ if (shouldRegisterTool("claim_next_task")) {
|
|
|
8510
8648
|
return { content: [{ type: "text", text: "No tasks available to claim." }] };
|
|
8511
8649
|
}
|
|
8512
8650
|
return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
|
|
8513
|
-
${formatTaskDetail(task)}` }] };
|
|
8651
|
+
${formatTaskDetail(task, 300)}` }] };
|
|
8514
8652
|
} catch (e) {
|
|
8515
8653
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8516
8654
|
}
|
|
@@ -8595,6 +8733,8 @@ No pending tasks available.`);
|
|
|
8595
8733
|
}
|
|
8596
8734
|
}
|
|
8597
8735
|
}
|
|
8736
|
+
lines.push(`
|
|
8737
|
+
as_of: ${new Date().toISOString()} (pass to get_tasks_changed_since for incremental polling)`);
|
|
8598
8738
|
return { content: [{ type: "text", text: lines.join(`
|
|
8599
8739
|
`) }] };
|
|
8600
8740
|
} catch (e) {
|
|
@@ -8706,6 +8846,58 @@ if (shouldRegisterTool("get_context")) {
|
|
|
8706
8846
|
}
|
|
8707
8847
|
if (next)
|
|
8708
8848
|
lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
|
|
8849
|
+
lines.push(`as_of: ${new Date().toISOString()}`);
|
|
8850
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
8851
|
+
`) }] };
|
|
8852
|
+
} catch (e) {
|
|
8853
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8854
|
+
}
|
|
8855
|
+
});
|
|
8856
|
+
}
|
|
8857
|
+
if (shouldRegisterTool("bootstrap")) {
|
|
8858
|
+
server.tool("bootstrap", "Single call for session start. Returns agent's in-progress task (if resuming), next claimable task, and project health \u2014 no side effects. Replaces 3-4 round trips at cold start.", {
|
|
8859
|
+
agent_id: exports_external.string().optional().describe("Your agent ID \u2014 used to find your active tasks and preferred next task"),
|
|
8860
|
+
project_id: exports_external.string().optional()
|
|
8861
|
+
}, async ({ agent_id, project_id }) => {
|
|
8862
|
+
try {
|
|
8863
|
+
const filters = {};
|
|
8864
|
+
if (project_id)
|
|
8865
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8866
|
+
const f = Object.keys(filters).length > 0 ? filters : undefined;
|
|
8867
|
+
const status = getStatus(f, agent_id);
|
|
8868
|
+
const next = getNextTask(agent_id, f);
|
|
8869
|
+
const lines = [];
|
|
8870
|
+
const myActive = agent_id ? status.active_work.filter((w) => w.assigned_to === agent_id || w.locked_by === agent_id) : [];
|
|
8871
|
+
if (myActive.length > 0) {
|
|
8872
|
+
lines.push(`## Resuming`);
|
|
8873
|
+
for (const w of myActive) {
|
|
8874
|
+
lines.push(`[${w.short_id || w.id.slice(0, 8)}] ${w.priority} \u2014 ${w.title}`);
|
|
8875
|
+
}
|
|
8876
|
+
lines.push("");
|
|
8877
|
+
}
|
|
8878
|
+
if (next) {
|
|
8879
|
+
lines.push(`## Next task to claim`);
|
|
8880
|
+
lines.push(`[${next.short_id || next.id.slice(0, 8)}] ${next.priority} \u2014 ${next.title}`);
|
|
8881
|
+
if (next.description)
|
|
8882
|
+
lines.push(next.description.slice(0, 300) + (next.description.length > 300 ? "\u2026" : ""));
|
|
8883
|
+
lines.push(` call: claim_next_task(agent_id: "${agent_id || "<your-id>"}")`);
|
|
8884
|
+
lines.push("");
|
|
8885
|
+
} else {
|
|
8886
|
+
lines.push(`## No tasks available to claim`);
|
|
8887
|
+
lines.push("");
|
|
8888
|
+
}
|
|
8889
|
+
lines.push(`## Health`);
|
|
8890
|
+
lines.push(`${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
|
|
8891
|
+
if (status.stale_count > 0)
|
|
8892
|
+
lines.push(`\u26A0 ${status.stale_count} stale task(s)`);
|
|
8893
|
+
if (status.overdue_recurring > 0)
|
|
8894
|
+
lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
|
|
8895
|
+
if (status.active_work.length > 0) {
|
|
8896
|
+
const others = agent_id ? status.active_work.filter((w) => w.assigned_to !== agent_id && w.locked_by !== agent_id) : status.active_work;
|
|
8897
|
+
if (others.length > 0) {
|
|
8898
|
+
lines.push(`Other agents active: ${others.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
8899
|
+
}
|
|
8900
|
+
}
|
|
8709
8901
|
return { content: [{ type: "text", text: lines.join(`
|
|
8710
8902
|
`) }] };
|
|
8711
8903
|
} catch (e) {
|
|
@@ -8776,6 +8968,7 @@ if (shouldRegisterTool("search_tools")) {
|
|
|
8776
8968
|
"get_status",
|
|
8777
8969
|
"get_context",
|
|
8778
8970
|
"get_health",
|
|
8971
|
+
"bootstrap",
|
|
8779
8972
|
"decompose_task",
|
|
8780
8973
|
"set_task_status",
|
|
8781
8974
|
"set_task_priority",
|
|
@@ -8793,9 +8986,9 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
8793
8986
|
create_task: `Create a new task.
|
|
8794
8987
|
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)
|
|
8795
8988
|
Example: {title: 'Daily standup', recurrence_rule: 'every weekday', priority: 'medium'}`,
|
|
8796
|
-
list_tasks: `List tasks with optional filters.
|
|
8797
|
-
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)
|
|
8798
|
-
Example: {status: ['pending', 'in_progress'],
|
|
8989
|
+
list_tasks: `List tasks with optional filters. Default limit is 50 to avoid context overflow \u2014 always paginate with offset for large lists.
|
|
8990
|
+
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, default 50), offset(number)
|
|
8991
|
+
Example: {status: ['pending', 'in_progress'], limit: 50, offset: 0}`,
|
|
8799
8992
|
get_task: `Get full task details with subtasks, deps, and comments.
|
|
8800
8993
|
Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
|
|
8801
8994
|
Example: {id: 'a1b2c3d4'}`,
|
|
@@ -8945,8 +9138,8 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
8945
9138
|
get_active_work: `See all in-progress tasks and who is working on them.
|
|
8946
9139
|
Params: project_id(string, optional), task_list_id(string, optional)
|
|
8947
9140
|
Example: {project_id: 'a1b2c3d4'}`,
|
|
8948
|
-
get_tasks_changed_since: `Get tasks modified after a timestamp \u2014
|
|
8949
|
-
Params: since(string, req \u2014 ISO date), project_id(string, optional), task_list_id(string, optional)
|
|
9141
|
+
get_tasks_changed_since: `PREFERRED POLLING PATTERN: Get only tasks modified after a timestamp \u2014 much cheaper than re-fetching everything. Save the as_of timestamp from bootstrap/get_status/get_context and pass it here on your next check.
|
|
9142
|
+
Params: since(string, req \u2014 ISO date from prior as_of), project_id(string, optional), task_list_id(string, optional)
|
|
8950
9143
|
Example: {since: '2026-03-14T10:00:00Z'}`,
|
|
8951
9144
|
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
8952
9145
|
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
@@ -8954,6 +9147,9 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
8954
9147
|
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.
|
|
8955
9148
|
Params: agent_id(string, optional \u2014 prefers tasks assigned to this agent for next_task), project_id(string, optional), task_list_id(string, optional)
|
|
8956
9149
|
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
9150
|
+
bootstrap: `CALL THIS FIRST at session start. Returns your in-progress task (if resuming), next claimable task with description, and project health \u2014 all in one call, no side effects. Eliminates 3-4 round trips.
|
|
9151
|
+
Params: agent_id(string, optional but recommended), project_id(string, optional)
|
|
9152
|
+
Example: {agent_id: 'a1b2c3d4'}`,
|
|
8957
9153
|
decompose_task: `Break a task into subtasks in one call. Subtasks inherit project/plan/list from parent.
|
|
8958
9154
|
Params: parent_id(string, req), subtasks(array, req \u2014 [{title, description, priority, assigned_to, estimated_minutes, tags}]), depends_on_prev(boolean \u2014 chain subtasks sequentially)
|
|
8959
9155
|
Example: {parent_id: 'a1b2c3d4', subtasks: [{title: 'Research'}, {title: 'Implement'}, {title: 'Test'}], depends_on_prev: true}`,
|