@hasna/todos 0.9.69 → 0.9.71
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 +376 -126
- package/dist/db/agents.d.ts +11 -4
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +241 -86
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +376 -126
- package/dist/server/index.js +207 -93
- package/dist/types/index.d.ts +20 -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,63 @@ 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);
|
|
588
|
+
`,
|
|
589
|
+
`
|
|
590
|
+
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
591
|
+
ALTER TABLE agents ADD COLUMN working_dir TEXT;
|
|
592
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (17);
|
|
536
593
|
`
|
|
537
594
|
];
|
|
538
595
|
});
|
|
@@ -615,6 +672,73 @@ var init_webhooks = __esm(() => {
|
|
|
615
672
|
init_database();
|
|
616
673
|
});
|
|
617
674
|
|
|
675
|
+
// src/db/templates.ts
|
|
676
|
+
var exports_templates = {};
|
|
677
|
+
__export(exports_templates, {
|
|
678
|
+
taskFromTemplate: () => taskFromTemplate,
|
|
679
|
+
listTemplates: () => listTemplates,
|
|
680
|
+
getTemplate: () => getTemplate,
|
|
681
|
+
deleteTemplate: () => deleteTemplate,
|
|
682
|
+
createTemplate: () => createTemplate
|
|
683
|
+
});
|
|
684
|
+
function rowToTemplate(row) {
|
|
685
|
+
return {
|
|
686
|
+
...row,
|
|
687
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
688
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
689
|
+
priority: row.priority || "medium"
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function createTemplate(input, db) {
|
|
693
|
+
const d = db || getDatabase();
|
|
694
|
+
const id = uuid();
|
|
695
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
696
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
697
|
+
id,
|
|
698
|
+
input.name,
|
|
699
|
+
input.title_pattern,
|
|
700
|
+
input.description || null,
|
|
701
|
+
input.priority || "medium",
|
|
702
|
+
JSON.stringify(input.tags || []),
|
|
703
|
+
input.project_id || null,
|
|
704
|
+
input.plan_id || null,
|
|
705
|
+
JSON.stringify(input.metadata || {}),
|
|
706
|
+
now()
|
|
707
|
+
]);
|
|
708
|
+
return getTemplate(id, d);
|
|
709
|
+
}
|
|
710
|
+
function getTemplate(id, db) {
|
|
711
|
+
const d = db || getDatabase();
|
|
712
|
+
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(id);
|
|
713
|
+
return row ? rowToTemplate(row) : null;
|
|
714
|
+
}
|
|
715
|
+
function listTemplates(db) {
|
|
716
|
+
const d = db || getDatabase();
|
|
717
|
+
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
718
|
+
}
|
|
719
|
+
function deleteTemplate(id, db) {
|
|
720
|
+
const d = db || getDatabase();
|
|
721
|
+
return d.run("DELETE FROM task_templates WHERE id = ?", [id]).changes > 0;
|
|
722
|
+
}
|
|
723
|
+
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
724
|
+
const t = getTemplate(templateId, db);
|
|
725
|
+
if (!t)
|
|
726
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
727
|
+
return {
|
|
728
|
+
title: overrides.title || t.title_pattern,
|
|
729
|
+
description: overrides.description ?? t.description ?? undefined,
|
|
730
|
+
priority: overrides.priority ?? t.priority,
|
|
731
|
+
tags: overrides.tags ?? t.tags,
|
|
732
|
+
project_id: overrides.project_id ?? t.project_id ?? undefined,
|
|
733
|
+
plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
|
|
734
|
+
metadata: overrides.metadata ?? t.metadata,
|
|
735
|
+
...overrides
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
var init_templates = __esm(() => {
|
|
739
|
+
init_database();
|
|
740
|
+
});
|
|
741
|
+
|
|
618
742
|
// src/db/agents.ts
|
|
619
743
|
var exports_agents = {};
|
|
620
744
|
__export(exports_agents, {
|
|
@@ -622,6 +746,7 @@ __export(exports_agents, {
|
|
|
622
746
|
updateAgent: () => updateAgent,
|
|
623
747
|
registerAgent: () => registerAgent,
|
|
624
748
|
listAgents: () => listAgents,
|
|
749
|
+
isAgentConflict: () => isAgentConflict,
|
|
625
750
|
getOrgChart: () => getOrgChart,
|
|
626
751
|
getDirectReports: () => getDirectReports,
|
|
627
752
|
getAgentByName: () => getAgentByName,
|
|
@@ -643,13 +768,44 @@ function registerAgent(input, db) {
|
|
|
643
768
|
const normalizedName = input.name.trim().toLowerCase();
|
|
644
769
|
const existing = getAgentByName(normalizedName, d);
|
|
645
770
|
if (existing) {
|
|
646
|
-
|
|
771
|
+
const lastSeenMs = new Date(existing.last_seen_at).getTime();
|
|
772
|
+
const isActive = Date.now() - lastSeenMs < AGENT_ACTIVE_WINDOW_MS;
|
|
773
|
+
const sameSession = input.session_id && existing.session_id && input.session_id === existing.session_id;
|
|
774
|
+
const differentSession = input.session_id && existing.session_id && input.session_id !== existing.session_id;
|
|
775
|
+
if (isActive && differentSession) {
|
|
776
|
+
const minutesAgo = Math.round((Date.now() - lastSeenMs) / 60000);
|
|
777
|
+
return {
|
|
778
|
+
conflict: true,
|
|
779
|
+
existing_id: existing.id,
|
|
780
|
+
existing_name: existing.name,
|
|
781
|
+
last_seen_at: existing.last_seen_at,
|
|
782
|
+
session_hint: existing.session_id ? existing.session_id.slice(0, 8) : null,
|
|
783
|
+
working_dir: existing.working_dir,
|
|
784
|
+
message: `Agent "${normalizedName}" is already active (last seen ${minutesAgo}m ago, session ${existing.session_id?.slice(0, 8)}\u2026, dir: ${existing.working_dir ?? "unknown"}). Are you that agent? If so, pass session_id="${existing.session_id}" to reclaim it. Otherwise choose a different name.`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
const updates = ["last_seen_at = ?"];
|
|
788
|
+
const params = [now()];
|
|
789
|
+
if (input.session_id && !sameSession) {
|
|
790
|
+
updates.push("session_id = ?");
|
|
791
|
+
params.push(input.session_id);
|
|
792
|
+
}
|
|
793
|
+
if (input.working_dir) {
|
|
794
|
+
updates.push("working_dir = ?");
|
|
795
|
+
params.push(input.working_dir);
|
|
796
|
+
}
|
|
797
|
+
if (input.description) {
|
|
798
|
+
updates.push("description = ?");
|
|
799
|
+
params.push(input.description);
|
|
800
|
+
}
|
|
801
|
+
params.push(existing.id);
|
|
802
|
+
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
647
803
|
return getAgent(existing.id, d);
|
|
648
804
|
}
|
|
649
805
|
const id = shortUuid();
|
|
650
806
|
const timestamp = now();
|
|
651
|
-
d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, reports_to, org_id, metadata, created_at, last_seen_at)
|
|
652
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
807
|
+
d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, reports_to, org_id, metadata, created_at, last_seen_at, session_id, working_dir)
|
|
808
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
653
809
|
id,
|
|
654
810
|
normalizedName,
|
|
655
811
|
input.description || null,
|
|
@@ -661,10 +817,15 @@ function registerAgent(input, db) {
|
|
|
661
817
|
input.org_id || null,
|
|
662
818
|
JSON.stringify(input.metadata || {}),
|
|
663
819
|
timestamp,
|
|
664
|
-
timestamp
|
|
820
|
+
timestamp,
|
|
821
|
+
input.session_id || null,
|
|
822
|
+
input.working_dir || null
|
|
665
823
|
]);
|
|
666
824
|
return getAgent(id, d);
|
|
667
825
|
}
|
|
826
|
+
function isAgentConflict(result) {
|
|
827
|
+
return result.conflict === true;
|
|
828
|
+
}
|
|
668
829
|
function getAgent(id, db) {
|
|
669
830
|
const d = db || getDatabase();
|
|
670
831
|
const row = d.query("SELECT * FROM agents WHERE id = ?").get(id);
|
|
@@ -754,75 +915,10 @@ function getOrgChart(db) {
|
|
|
754
915
|
}
|
|
755
916
|
return buildTree(null);
|
|
756
917
|
}
|
|
918
|
+
var AGENT_ACTIVE_WINDOW_MS;
|
|
757
919
|
var init_agents = __esm(() => {
|
|
758
920
|
init_database();
|
|
759
|
-
|
|
760
|
-
|
|
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();
|
|
921
|
+
AGENT_ACTIVE_WINDOW_MS = 30 * 60 * 1000;
|
|
826
922
|
});
|
|
827
923
|
|
|
828
924
|
// src/mcp/index.ts
|
|
@@ -5222,6 +5318,7 @@ function nextOccurrence(rule, from) {
|
|
|
5222
5318
|
|
|
5223
5319
|
// src/db/tasks.ts
|
|
5224
5320
|
init_webhooks();
|
|
5321
|
+
init_templates();
|
|
5225
5322
|
function rowToTask(row) {
|
|
5226
5323
|
return {
|
|
5227
5324
|
...row,
|
|
@@ -5252,8 +5349,8 @@ function createTask(input, db) {
|
|
|
5252
5349
|
const tags = input.tags || [];
|
|
5253
5350
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
5254
5351
|
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, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5352
|
+
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)
|
|
5353
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5257
5354
|
id,
|
|
5258
5355
|
shortId,
|
|
5259
5356
|
input.project_id || null,
|
|
@@ -5278,7 +5375,8 @@ function createTask(input, db) {
|
|
|
5278
5375
|
null,
|
|
5279
5376
|
null,
|
|
5280
5377
|
input.recurrence_rule || null,
|
|
5281
|
-
input.recurrence_parent_id || null
|
|
5378
|
+
input.recurrence_parent_id || null,
|
|
5379
|
+
input.spawns_template_id || null
|
|
5282
5380
|
]);
|
|
5283
5381
|
if (tags.length > 0) {
|
|
5284
5382
|
insertTaskTags(id, tags, d);
|
|
@@ -5385,19 +5483,25 @@ function listTasks(filter = {}, db) {
|
|
|
5385
5483
|
} else if (filter.has_recurrence === false) {
|
|
5386
5484
|
conditions.push("recurrence_rule IS NULL");
|
|
5387
5485
|
}
|
|
5486
|
+
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
5487
|
+
if (filter.cursor) {
|
|
5488
|
+
try {
|
|
5489
|
+
const decoded = JSON.parse(Buffer.from(filter.cursor, "base64").toString("utf8"));
|
|
5490
|
+
conditions.push(`(${PRIORITY_RANK} > ? OR (${PRIORITY_RANK} = ? AND created_at < ?) OR (${PRIORITY_RANK} = ? AND created_at = ? AND id > ?))`);
|
|
5491
|
+
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
5492
|
+
} catch {}
|
|
5493
|
+
}
|
|
5388
5494
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5389
5495
|
let limitClause = "";
|
|
5390
5496
|
if (filter.limit) {
|
|
5391
5497
|
limitClause = " LIMIT ?";
|
|
5392
5498
|
params.push(filter.limit);
|
|
5393
|
-
if (filter.offset) {
|
|
5499
|
+
if (!filter.cursor && filter.offset) {
|
|
5394
5500
|
limitClause += " OFFSET ?";
|
|
5395
5501
|
params.push(filter.offset);
|
|
5396
5502
|
}
|
|
5397
5503
|
}
|
|
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);
|
|
5504
|
+
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC${limitClause}`).all(...params);
|
|
5401
5505
|
return rows.map(rowToTask);
|
|
5402
5506
|
}
|
|
5403
5507
|
function countTasks(filter = {}, db) {
|
|
@@ -5642,10 +5746,25 @@ function completeTask(id, agentId, db, options) {
|
|
|
5642
5746
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
5643
5747
|
spawnedTask = spawnNextRecurrence(task, d);
|
|
5644
5748
|
}
|
|
5749
|
+
let spawnedFromTemplate = null;
|
|
5750
|
+
if (task.spawns_template_id) {
|
|
5751
|
+
try {
|
|
5752
|
+
const input = taskFromTemplate(task.spawns_template_id, {
|
|
5753
|
+
project_id: task.project_id ?? undefined,
|
|
5754
|
+
plan_id: task.plan_id ?? undefined,
|
|
5755
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
5756
|
+
assigned_to: task.assigned_to ?? undefined
|
|
5757
|
+
}, d);
|
|
5758
|
+
spawnedFromTemplate = createTask(input, d);
|
|
5759
|
+
} catch {}
|
|
5760
|
+
}
|
|
5645
5761
|
const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
|
|
5646
5762
|
if (spawnedTask) {
|
|
5647
5763
|
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
5648
5764
|
}
|
|
5765
|
+
if (spawnedFromTemplate) {
|
|
5766
|
+
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
5767
|
+
}
|
|
5649
5768
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
5650
5769
|
}
|
|
5651
5770
|
function lockTask(id, agentId, db) {
|
|
@@ -5853,11 +5972,21 @@ function getNextTask(agentId, filters, db) {
|
|
|
5853
5972
|
}
|
|
5854
5973
|
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
5974
|
const where = conditions.join(" AND ");
|
|
5975
|
+
let recentProjectIds = [];
|
|
5976
|
+
if (agentId) {
|
|
5977
|
+
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);
|
|
5978
|
+
recentProjectIds = recentRows.map((r) => r.project_id);
|
|
5979
|
+
}
|
|
5856
5980
|
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
5857
5981
|
if (agentId) {
|
|
5858
5982
|
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
5859
5983
|
params.push(agentId);
|
|
5860
5984
|
}
|
|
5985
|
+
if (recentProjectIds.length > 0) {
|
|
5986
|
+
const placeholders = recentProjectIds.map(() => "?").join(",");
|
|
5987
|
+
sql += `CASE WHEN project_id IN (${placeholders}) THEN 0 ELSE 1 END, `;
|
|
5988
|
+
params.push(...recentProjectIds);
|
|
5989
|
+
}
|
|
5861
5990
|
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
5991
|
const row = d.query(sql).get(...params);
|
|
5863
5992
|
return row ? rowToTask(row) : null;
|
|
@@ -6363,68 +6492,95 @@ function rowToTask2(row) {
|
|
|
6363
6492
|
requires_approval: Boolean(row.requires_approval)
|
|
6364
6493
|
};
|
|
6365
6494
|
}
|
|
6495
|
+
function hasFts(db) {
|
|
6496
|
+
try {
|
|
6497
|
+
const result = db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='tasks_fts'").get();
|
|
6498
|
+
return result !== null;
|
|
6499
|
+
} catch {
|
|
6500
|
+
return false;
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
function escapeFtsQuery(q) {
|
|
6504
|
+
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
6505
|
+
}
|
|
6366
6506
|
function searchTasks(options, projectId, taskListId, db) {
|
|
6367
6507
|
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
6368
6508
|
const d = db || getDatabase();
|
|
6369
6509
|
clearExpiredLocks(d);
|
|
6370
|
-
const
|
|
6371
|
-
let sql
|
|
6372
|
-
|
|
6510
|
+
const params = [];
|
|
6511
|
+
let sql;
|
|
6512
|
+
if (hasFts(d) && opts.query.trim()) {
|
|
6513
|
+
const ftsQuery = escapeFtsQuery(opts.query);
|
|
6514
|
+
sql = `SELECT t.* FROM tasks t
|
|
6515
|
+
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
6516
|
+
WHERE tasks_fts MATCH ?`;
|
|
6517
|
+
params.push(ftsQuery);
|
|
6518
|
+
} else {
|
|
6519
|
+
const pattern = `%${opts.query}%`;
|
|
6520
|
+
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 ?))`;
|
|
6521
|
+
params.push(pattern, pattern, pattern);
|
|
6522
|
+
}
|
|
6373
6523
|
if (opts.project_id) {
|
|
6374
|
-
sql += " AND project_id = ?";
|
|
6524
|
+
sql += " AND t.project_id = ?";
|
|
6375
6525
|
params.push(opts.project_id);
|
|
6376
6526
|
}
|
|
6377
6527
|
if (opts.task_list_id) {
|
|
6378
|
-
sql += " AND task_list_id = ?";
|
|
6528
|
+
sql += " AND t.task_list_id = ?";
|
|
6379
6529
|
params.push(opts.task_list_id);
|
|
6380
6530
|
}
|
|
6381
6531
|
if (opts.status) {
|
|
6382
6532
|
if (Array.isArray(opts.status)) {
|
|
6383
|
-
sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
|
|
6533
|
+
sql += ` AND t.status IN (${opts.status.map(() => "?").join(",")})`;
|
|
6384
6534
|
params.push(...opts.status);
|
|
6385
6535
|
} else {
|
|
6386
|
-
sql += " AND status = ?";
|
|
6536
|
+
sql += " AND t.status = ?";
|
|
6387
6537
|
params.push(opts.status);
|
|
6388
6538
|
}
|
|
6389
6539
|
}
|
|
6390
6540
|
if (opts.priority) {
|
|
6391
6541
|
if (Array.isArray(opts.priority)) {
|
|
6392
|
-
sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
6542
|
+
sql += ` AND t.priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
6393
6543
|
params.push(...opts.priority);
|
|
6394
6544
|
} else {
|
|
6395
|
-
sql += " AND priority = ?";
|
|
6545
|
+
sql += " AND t.priority = ?";
|
|
6396
6546
|
params.push(opts.priority);
|
|
6397
6547
|
}
|
|
6398
6548
|
}
|
|
6399
6549
|
if (opts.assigned_to) {
|
|
6400
|
-
sql += " AND assigned_to = ?";
|
|
6550
|
+
sql += " AND t.assigned_to = ?";
|
|
6401
6551
|
params.push(opts.assigned_to);
|
|
6402
6552
|
}
|
|
6403
6553
|
if (opts.agent_id) {
|
|
6404
|
-
sql += " AND agent_id = ?";
|
|
6554
|
+
sql += " AND t.agent_id = ?";
|
|
6405
6555
|
params.push(opts.agent_id);
|
|
6406
6556
|
}
|
|
6407
6557
|
if (opts.created_after) {
|
|
6408
|
-
sql += " AND created_at > ?";
|
|
6558
|
+
sql += " AND t.created_at > ?";
|
|
6409
6559
|
params.push(opts.created_after);
|
|
6410
6560
|
}
|
|
6411
6561
|
if (opts.updated_after) {
|
|
6412
|
-
sql += " AND updated_at > ?";
|
|
6562
|
+
sql += " AND t.updated_at > ?";
|
|
6413
6563
|
params.push(opts.updated_after);
|
|
6414
6564
|
}
|
|
6415
6565
|
if (opts.has_dependencies === true) {
|
|
6416
|
-
sql += " AND id IN (SELECT task_id FROM task_dependencies)";
|
|
6566
|
+
sql += " AND t.id IN (SELECT task_id FROM task_dependencies)";
|
|
6417
6567
|
} else if (opts.has_dependencies === false) {
|
|
6418
|
-
sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
6568
|
+
sql += " AND t.id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
6419
6569
|
}
|
|
6420
6570
|
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')";
|
|
6571
|
+
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
6572
|
} 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')";
|
|
6573
|
+
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')";
|
|
6574
|
+
}
|
|
6575
|
+
if (hasFts(d) && opts.query.trim()) {
|
|
6576
|
+
sql += ` ORDER BY bm25(tasks_fts),
|
|
6577
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
6578
|
+
t.created_at DESC`;
|
|
6579
|
+
} else {
|
|
6580
|
+
sql += ` ORDER BY
|
|
6581
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
6582
|
+
t.created_at DESC`;
|
|
6424
6583
|
}
|
|
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
6584
|
const rows = d.query(sql).all(...params);
|
|
6429
6585
|
return rows.map(rowToTask2);
|
|
6430
6586
|
}
|
|
@@ -6974,7 +7130,9 @@ var MINIMAL_TOOLS = new Set([
|
|
|
6974
7130
|
"get_task",
|
|
6975
7131
|
"start_task",
|
|
6976
7132
|
"add_comment",
|
|
6977
|
-
"get_next_task"
|
|
7133
|
+
"get_next_task",
|
|
7134
|
+
"bootstrap",
|
|
7135
|
+
"get_tasks_changed_since"
|
|
6978
7136
|
]);
|
|
6979
7137
|
var STANDARD_EXCLUDED = new Set([
|
|
6980
7138
|
"get_org_chart",
|
|
@@ -7055,15 +7213,17 @@ function formatTask(task) {
|
|
|
7055
7213
|
const recur = task.recurrence_rule ? ` [\u21BB]` : "";
|
|
7056
7214
|
return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
|
|
7057
7215
|
}
|
|
7058
|
-
function formatTaskDetail(task) {
|
|
7216
|
+
function formatTaskDetail(task, maxDescriptionChars) {
|
|
7059
7217
|
const parts = [
|
|
7060
7218
|
`ID: ${task.id}`,
|
|
7061
7219
|
`Title: ${task.title}`,
|
|
7062
7220
|
`Status: ${task.status}`,
|
|
7063
7221
|
`Priority: ${task.priority}`
|
|
7064
7222
|
];
|
|
7065
|
-
if (task.description)
|
|
7066
|
-
|
|
7223
|
+
if (task.description) {
|
|
7224
|
+
const desc = maxDescriptionChars && task.description.length > maxDescriptionChars ? task.description.slice(0, maxDescriptionChars) + "\u2026" : task.description;
|
|
7225
|
+
parts.push(`Description: ${desc}`);
|
|
7226
|
+
}
|
|
7067
7227
|
if (task.assigned_to)
|
|
7068
7228
|
parts.push(`Assigned to: ${task.assigned_to}`);
|
|
7069
7229
|
if (task.agent_id)
|
|
@@ -7107,7 +7267,8 @@ if (shouldRegisterTool("create_task")) {
|
|
|
7107
7267
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
7108
7268
|
estimated_minutes: exports_external.number().optional(),
|
|
7109
7269
|
requires_approval: exports_external.boolean().optional(),
|
|
7110
|
-
recurrence_rule: exports_external.string().optional()
|
|
7270
|
+
recurrence_rule: exports_external.string().optional(),
|
|
7271
|
+
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
7272
|
}, async (params) => {
|
|
7112
7273
|
try {
|
|
7113
7274
|
const resolved = { ...params };
|
|
@@ -7127,7 +7288,7 @@ if (shouldRegisterTool("create_task")) {
|
|
|
7127
7288
|
});
|
|
7128
7289
|
}
|
|
7129
7290
|
if (shouldRegisterTool("list_tasks")) {
|
|
7130
|
-
server.tool("list_tasks", "List tasks with optional filters and pagination.", {
|
|
7291
|
+
server.tool("list_tasks", "List tasks with optional filters and pagination. Default limit is 50 \u2014 use offset to page through results.", {
|
|
7131
7292
|
project_id: exports_external.string().optional(),
|
|
7132
7293
|
status: exports_external.union([
|
|
7133
7294
|
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
@@ -7145,11 +7306,15 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
7145
7306
|
due_today: exports_external.boolean().optional(),
|
|
7146
7307
|
overdue: exports_external.boolean().optional(),
|
|
7147
7308
|
limit: exports_external.number().optional(),
|
|
7148
|
-
offset: exports_external.number().optional()
|
|
7309
|
+
offset: exports_external.number().optional(),
|
|
7310
|
+
summary_only: exports_external.boolean().optional().describe("When true, return only id, short_id, title, status, priority \u2014 minimal tokens for navigation"),
|
|
7311
|
+
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
7312
|
}, async (params) => {
|
|
7150
7313
|
try {
|
|
7151
|
-
const { due_today, overdue, ...rest } = params;
|
|
7314
|
+
const { due_today, overdue, summary_only, ...rest } = params;
|
|
7152
7315
|
const resolved = { ...rest };
|
|
7316
|
+
if (resolved.limit === undefined)
|
|
7317
|
+
resolved.limit = 50;
|
|
7153
7318
|
if (resolved.project_id)
|
|
7154
7319
|
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
7155
7320
|
if (resolved.plan_id)
|
|
@@ -7171,6 +7336,9 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
7171
7336
|
return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
|
|
7172
7337
|
}
|
|
7173
7338
|
const text = tasks.map((t) => {
|
|
7339
|
+
if (summary_only) {
|
|
7340
|
+
return `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.priority} ${t.title}`;
|
|
7341
|
+
}
|
|
7174
7342
|
const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
|
|
7175
7343
|
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
7176
7344
|
const due = t.due_at ? ` due:${t.due_at.slice(0, 10)}` : "";
|
|
@@ -7178,10 +7346,23 @@ if (shouldRegisterTool("list_tasks")) {
|
|
|
7178
7346
|
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}${due}${recur}`;
|
|
7179
7347
|
}).join(`
|
|
7180
7348
|
`);
|
|
7181
|
-
const
|
|
7182
|
-
|
|
7349
|
+
const currentOffset = resolved.offset || 0;
|
|
7350
|
+
const hasMore = total > (resolved.cursor ? tasks.length : currentOffset + tasks.length);
|
|
7351
|
+
let paginationNote = `
|
|
7352
|
+
(showing ${tasks.length} of ${total}`;
|
|
7353
|
+
if (hasMore) {
|
|
7354
|
+
if (resolved.cursor || tasks.length > 0) {
|
|
7355
|
+
const last = tasks[tasks.length - 1];
|
|
7356
|
+
const priorityRank = { critical: 0, high: 1, medium: 2, low: 3 }[last.priority] ?? 3;
|
|
7357
|
+
const cursorPayload = Buffer.from(JSON.stringify({ p: priorityRank, c: last.created_at, i: last.id })).toString("base64");
|
|
7358
|
+
paginationNote += ` \u2014 next_cursor: ${cursorPayload}`;
|
|
7359
|
+
} else {
|
|
7360
|
+
paginationNote += ` \u2014 use offset: ${currentOffset + tasks.length} to get next page`;
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7363
|
+
paginationNote += ")";
|
|
7183
7364
|
return { content: [{ type: "text", text: `${tasks.length} task(s):
|
|
7184
|
-
${text}${
|
|
7365
|
+
${text}${paginationNote}` }] };
|
|
7185
7366
|
} catch (e) {
|
|
7186
7367
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7187
7368
|
}
|
|
@@ -7189,14 +7370,15 @@ ${text}${pagination}` }] };
|
|
|
7189
7370
|
}
|
|
7190
7371
|
if (shouldRegisterTool("get_task")) {
|
|
7191
7372
|
server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
|
|
7192
|
-
id: exports_external.string()
|
|
7193
|
-
|
|
7373
|
+
id: exports_external.string(),
|
|
7374
|
+
max_description_chars: exports_external.number().optional().describe("Truncate description to this many characters (default: unlimited). Use 300-500 for quick checks.")
|
|
7375
|
+
}, async ({ id, max_description_chars }) => {
|
|
7194
7376
|
try {
|
|
7195
7377
|
const resolvedId = resolveId(id);
|
|
7196
7378
|
const task = getTaskWithRelations(resolvedId);
|
|
7197
7379
|
if (!task)
|
|
7198
7380
|
return { content: [{ type: "text", text: `Task not found: ${id}` }], isError: true };
|
|
7199
|
-
const parts = [formatTaskDetail(task)];
|
|
7381
|
+
const parts = [formatTaskDetail(task, max_description_chars)];
|
|
7200
7382
|
if (task.subtasks.length > 0) {
|
|
7201
7383
|
parts.push(`
|
|
7202
7384
|
Subtasks (${task.subtasks.length}):`);
|
|
@@ -7670,12 +7852,21 @@ if (shouldRegisterTool("sync")) {
|
|
|
7670
7852
|
});
|
|
7671
7853
|
}
|
|
7672
7854
|
if (shouldRegisterTool("register_agent")) {
|
|
7673
|
-
server.tool("register_agent", "Register an agent (
|
|
7855
|
+
server.tool("register_agent", "Register an agent. Pass session_id (unique per coding session) to prevent name conflicts. Returns conflict error if name is taken by an active agent in a different session.", {
|
|
7674
7856
|
name: exports_external.string(),
|
|
7675
|
-
description: exports_external.string().optional()
|
|
7676
|
-
|
|
7857
|
+
description: exports_external.string().optional(),
|
|
7858
|
+
session_id: exports_external.string().optional().describe("Unique ID for this coding session (e.g. process PID + timestamp, or env var). Used to detect name collisions across sessions. Store it and pass on every register_agent call."),
|
|
7859
|
+
working_dir: exports_external.string().optional().describe("Working directory of this session \u2014 helps identify who holds the name in a conflict")
|
|
7860
|
+
}, async ({ name, description, session_id, working_dir }) => {
|
|
7677
7861
|
try {
|
|
7678
|
-
const
|
|
7862
|
+
const result = registerAgent({ name, description, session_id, working_dir });
|
|
7863
|
+
if (isAgentConflict(result)) {
|
|
7864
|
+
return {
|
|
7865
|
+
content: [{ type: "text", text: `CONFLICT: ${result.message}` }],
|
|
7866
|
+
isError: true
|
|
7867
|
+
};
|
|
7868
|
+
}
|
|
7869
|
+
const agent = result;
|
|
7679
7870
|
return {
|
|
7680
7871
|
content: [{
|
|
7681
7872
|
type: "text",
|
|
@@ -7683,6 +7874,7 @@ if (shouldRegisterTool("register_agent")) {
|
|
|
7683
7874
|
ID: ${agent.id}
|
|
7684
7875
|
Name: ${agent.name}${agent.description ? `
|
|
7685
7876
|
Description: ${agent.description}` : ""}
|
|
7877
|
+
Session: ${agent.session_id ?? "unbound"}
|
|
7686
7878
|
Created: ${agent.created_at}
|
|
7687
7879
|
Last seen: ${agent.last_seen_at}`
|
|
7688
7880
|
}]
|
|
@@ -8424,7 +8616,7 @@ if (shouldRegisterTool("get_next_task")) {
|
|
|
8424
8616
|
return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
|
|
8425
8617
|
}
|
|
8426
8618
|
return { content: [{ type: "text", text: `next: ${formatTask(task)}
|
|
8427
|
-
${formatTaskDetail(task)}` }] };
|
|
8619
|
+
${formatTaskDetail(task, 300)}` }] };
|
|
8428
8620
|
} catch (e) {
|
|
8429
8621
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8430
8622
|
}
|
|
@@ -8510,7 +8702,7 @@ if (shouldRegisterTool("claim_next_task")) {
|
|
|
8510
8702
|
return { content: [{ type: "text", text: "No tasks available to claim." }] };
|
|
8511
8703
|
}
|
|
8512
8704
|
return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
|
|
8513
|
-
${formatTaskDetail(task)}` }] };
|
|
8705
|
+
${formatTaskDetail(task, 300)}` }] };
|
|
8514
8706
|
} catch (e) {
|
|
8515
8707
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8516
8708
|
}
|
|
@@ -8595,6 +8787,8 @@ No pending tasks available.`);
|
|
|
8595
8787
|
}
|
|
8596
8788
|
}
|
|
8597
8789
|
}
|
|
8790
|
+
lines.push(`
|
|
8791
|
+
as_of: ${new Date().toISOString()} (pass to get_tasks_changed_since for incremental polling)`);
|
|
8598
8792
|
return { content: [{ type: "text", text: lines.join(`
|
|
8599
8793
|
`) }] };
|
|
8600
8794
|
} catch (e) {
|
|
@@ -8706,6 +8900,58 @@ if (shouldRegisterTool("get_context")) {
|
|
|
8706
8900
|
}
|
|
8707
8901
|
if (next)
|
|
8708
8902
|
lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
|
|
8903
|
+
lines.push(`as_of: ${new Date().toISOString()}`);
|
|
8904
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
8905
|
+
`) }] };
|
|
8906
|
+
} catch (e) {
|
|
8907
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
8908
|
+
}
|
|
8909
|
+
});
|
|
8910
|
+
}
|
|
8911
|
+
if (shouldRegisterTool("bootstrap")) {
|
|
8912
|
+
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.", {
|
|
8913
|
+
agent_id: exports_external.string().optional().describe("Your agent ID \u2014 used to find your active tasks and preferred next task"),
|
|
8914
|
+
project_id: exports_external.string().optional()
|
|
8915
|
+
}, async ({ agent_id, project_id }) => {
|
|
8916
|
+
try {
|
|
8917
|
+
const filters = {};
|
|
8918
|
+
if (project_id)
|
|
8919
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
8920
|
+
const f = Object.keys(filters).length > 0 ? filters : undefined;
|
|
8921
|
+
const status = getStatus(f, agent_id);
|
|
8922
|
+
const next = getNextTask(agent_id, f);
|
|
8923
|
+
const lines = [];
|
|
8924
|
+
const myActive = agent_id ? status.active_work.filter((w) => w.assigned_to === agent_id || w.locked_by === agent_id) : [];
|
|
8925
|
+
if (myActive.length > 0) {
|
|
8926
|
+
lines.push(`## Resuming`);
|
|
8927
|
+
for (const w of myActive) {
|
|
8928
|
+
lines.push(`[${w.short_id || w.id.slice(0, 8)}] ${w.priority} \u2014 ${w.title}`);
|
|
8929
|
+
}
|
|
8930
|
+
lines.push("");
|
|
8931
|
+
}
|
|
8932
|
+
if (next) {
|
|
8933
|
+
lines.push(`## Next task to claim`);
|
|
8934
|
+
lines.push(`[${next.short_id || next.id.slice(0, 8)}] ${next.priority} \u2014 ${next.title}`);
|
|
8935
|
+
if (next.description)
|
|
8936
|
+
lines.push(next.description.slice(0, 300) + (next.description.length > 300 ? "\u2026" : ""));
|
|
8937
|
+
lines.push(` call: claim_next_task(agent_id: "${agent_id || "<your-id>"}")`);
|
|
8938
|
+
lines.push("");
|
|
8939
|
+
} else {
|
|
8940
|
+
lines.push(`## No tasks available to claim`);
|
|
8941
|
+
lines.push("");
|
|
8942
|
+
}
|
|
8943
|
+
lines.push(`## Health`);
|
|
8944
|
+
lines.push(`${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
|
|
8945
|
+
if (status.stale_count > 0)
|
|
8946
|
+
lines.push(`\u26A0 ${status.stale_count} stale task(s)`);
|
|
8947
|
+
if (status.overdue_recurring > 0)
|
|
8948
|
+
lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
|
|
8949
|
+
if (status.active_work.length > 0) {
|
|
8950
|
+
const others = agent_id ? status.active_work.filter((w) => w.assigned_to !== agent_id && w.locked_by !== agent_id) : status.active_work;
|
|
8951
|
+
if (others.length > 0) {
|
|
8952
|
+
lines.push(`Other agents active: ${others.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8709
8955
|
return { content: [{ type: "text", text: lines.join(`
|
|
8710
8956
|
`) }] };
|
|
8711
8957
|
} catch (e) {
|
|
@@ -8776,6 +9022,7 @@ if (shouldRegisterTool("search_tools")) {
|
|
|
8776
9022
|
"get_status",
|
|
8777
9023
|
"get_context",
|
|
8778
9024
|
"get_health",
|
|
9025
|
+
"bootstrap",
|
|
8779
9026
|
"decompose_task",
|
|
8780
9027
|
"set_task_status",
|
|
8781
9028
|
"set_task_priority",
|
|
@@ -8793,9 +9040,9 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
8793
9040
|
create_task: `Create a new task.
|
|
8794
9041
|
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
9042
|
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'],
|
|
9043
|
+
list_tasks: `List tasks with optional filters. Default limit is 50 to avoid context overflow \u2014 always paginate with offset for large lists.
|
|
9044
|
+
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)
|
|
9045
|
+
Example: {status: ['pending', 'in_progress'], limit: 50, offset: 0}`,
|
|
8799
9046
|
get_task: `Get full task details with subtasks, deps, and comments.
|
|
8800
9047
|
Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
|
|
8801
9048
|
Example: {id: 'a1b2c3d4'}`,
|
|
@@ -8854,9 +9101,9 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
8854
9101
|
delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
|
|
8855
9102
|
Params: id(string, req)
|
|
8856
9103
|
Example: {id: 'a1b2c3d4'}`,
|
|
8857
|
-
register_agent: `Register an agent (
|
|
8858
|
-
Params: name(string, req \u2014 e.g.
|
|
8859
|
-
Example: {name: 'maximus',
|
|
9104
|
+
register_agent: `Register an agent. ALWAYS pass session_id (unique per session) to prevent name conflicts. Returns CONFLICT error if name is active in another session \u2014 pick a different name or reclaim with matching session_id.
|
|
9105
|
+
Params: name(string, req), description(string), session_id(string \u2014 unique per session, e.g. PID+timestamp), working_dir(string)
|
|
9106
|
+
Example: {name: 'maximus', session_id: 'abc123-1741952000', working_dir: '/workspace/platform'}`,
|
|
8860
9107
|
list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
|
|
8861
9108
|
get_agent: `Get agent details by ID or name. Provide one of id or name.
|
|
8862
9109
|
Params: id(string), name(string)
|
|
@@ -8945,8 +9192,8 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
8945
9192
|
get_active_work: `See all in-progress tasks and who is working on them.
|
|
8946
9193
|
Params: project_id(string, optional), task_list_id(string, optional)
|
|
8947
9194
|
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)
|
|
9195
|
+
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.
|
|
9196
|
+
Params: since(string, req \u2014 ISO date from prior as_of), project_id(string, optional), task_list_id(string, optional)
|
|
8950
9197
|
Example: {since: '2026-03-14T10:00:00Z'}`,
|
|
8951
9198
|
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
8952
9199
|
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
@@ -8954,6 +9201,9 @@ if (shouldRegisterTool("describe_tools")) {
|
|
|
8954
9201
|
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
9202
|
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
9203
|
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
9204
|
+
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.
|
|
9205
|
+
Params: agent_id(string, optional but recommended), project_id(string, optional)
|
|
9206
|
+
Example: {agent_id: 'a1b2c3d4'}`,
|
|
8957
9207
|
decompose_task: `Break a task into subtasks in one call. Subtasks inherit project/plan/list from parent.
|
|
8958
9208
|
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
9209
|
Example: {parent_id: 'a1b2c3d4', subtasks: [{title: 'Research'}, {title: 'Implement'}, {title: 'Test'}], depends_on_prev: true}`,
|