@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/cli/index.js
CHANGED
|
@@ -2599,6 +2599,63 @@ var init_database = __esm(() => {
|
|
|
2599
2599
|
ALTER TABLE task_comments ADD COLUMN type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note'));
|
|
2600
2600
|
ALTER TABLE task_comments ADD COLUMN progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100));
|
|
2601
2601
|
INSERT OR IGNORE INTO _migrations (id) VALUES (14);
|
|
2602
|
+
`,
|
|
2603
|
+
`
|
|
2604
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
2605
|
+
task_id UNINDEXED,
|
|
2606
|
+
title,
|
|
2607
|
+
description,
|
|
2608
|
+
tags,
|
|
2609
|
+
tokenize='unicode61 remove_diacritics 2'
|
|
2610
|
+
);
|
|
2611
|
+
|
|
2612
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
2613
|
+
SELECT t.rowid, t.id, t.title, COALESCE(t.description, ''),
|
|
2614
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = t.id), '')
|
|
2615
|
+
FROM tasks t;
|
|
2616
|
+
|
|
2617
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_ai AFTER INSERT ON tasks BEGIN
|
|
2618
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
2619
|
+
VALUES (new.rowid, new.id, new.title, COALESCE(new.description, ''), '');
|
|
2620
|
+
END;
|
|
2621
|
+
|
|
2622
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_ad AFTER DELETE ON tasks BEGIN
|
|
2623
|
+
DELETE FROM tasks_fts WHERE rowid = old.rowid;
|
|
2624
|
+
END;
|
|
2625
|
+
|
|
2626
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_au AFTER UPDATE OF title, description ON tasks BEGIN
|
|
2627
|
+
DELETE FROM tasks_fts WHERE rowid = old.rowid;
|
|
2628
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
2629
|
+
SELECT new.rowid, new.id, new.title, COALESCE(new.description, ''),
|
|
2630
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = new.id), '');
|
|
2631
|
+
END;
|
|
2632
|
+
|
|
2633
|
+
CREATE TRIGGER IF NOT EXISTS task_tags_fts_ai AFTER INSERT ON task_tags BEGIN
|
|
2634
|
+
DELETE FROM tasks_fts WHERE rowid = (SELECT rowid FROM tasks WHERE id = new.task_id);
|
|
2635
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
2636
|
+
SELECT t.rowid, t.id, t.title, COALESCE(t.description, ''),
|
|
2637
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = t.id), '')
|
|
2638
|
+
FROM tasks t WHERE t.id = new.task_id;
|
|
2639
|
+
END;
|
|
2640
|
+
|
|
2641
|
+
CREATE TRIGGER IF NOT EXISTS task_tags_fts_ad AFTER DELETE ON task_tags BEGIN
|
|
2642
|
+
DELETE FROM tasks_fts WHERE rowid = (SELECT rowid FROM tasks WHERE id = old.task_id);
|
|
2643
|
+
INSERT INTO tasks_fts(rowid, task_id, title, description, tags)
|
|
2644
|
+
SELECT t.rowid, t.id, t.title, COALESCE(t.description, ''),
|
|
2645
|
+
COALESCE((SELECT GROUP_CONCAT(tag, ' ') FROM task_tags WHERE task_id = t.id), '')
|
|
2646
|
+
FROM tasks t WHERE t.id = old.task_id;
|
|
2647
|
+
END;
|
|
2648
|
+
|
|
2649
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (15);
|
|
2650
|
+
`,
|
|
2651
|
+
`
|
|
2652
|
+
ALTER TABLE tasks ADD COLUMN spawns_template_id TEXT REFERENCES task_templates(id) ON DELETE SET NULL;
|
|
2653
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (16);
|
|
2654
|
+
`,
|
|
2655
|
+
`
|
|
2656
|
+
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
2657
|
+
ALTER TABLE agents ADD COLUMN working_dir TEXT;
|
|
2658
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (17);
|
|
2602
2659
|
`
|
|
2603
2660
|
];
|
|
2604
2661
|
});
|
|
@@ -3184,6 +3241,73 @@ var init_webhooks = __esm(() => {
|
|
|
3184
3241
|
init_database();
|
|
3185
3242
|
});
|
|
3186
3243
|
|
|
3244
|
+
// src/db/templates.ts
|
|
3245
|
+
var exports_templates = {};
|
|
3246
|
+
__export(exports_templates, {
|
|
3247
|
+
taskFromTemplate: () => taskFromTemplate,
|
|
3248
|
+
listTemplates: () => listTemplates,
|
|
3249
|
+
getTemplate: () => getTemplate,
|
|
3250
|
+
deleteTemplate: () => deleteTemplate,
|
|
3251
|
+
createTemplate: () => createTemplate
|
|
3252
|
+
});
|
|
3253
|
+
function rowToTemplate(row) {
|
|
3254
|
+
return {
|
|
3255
|
+
...row,
|
|
3256
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
3257
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
3258
|
+
priority: row.priority || "medium"
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
function createTemplate(input, db) {
|
|
3262
|
+
const d = db || getDatabase();
|
|
3263
|
+
const id = uuid();
|
|
3264
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
3265
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3266
|
+
id,
|
|
3267
|
+
input.name,
|
|
3268
|
+
input.title_pattern,
|
|
3269
|
+
input.description || null,
|
|
3270
|
+
input.priority || "medium",
|
|
3271
|
+
JSON.stringify(input.tags || []),
|
|
3272
|
+
input.project_id || null,
|
|
3273
|
+
input.plan_id || null,
|
|
3274
|
+
JSON.stringify(input.metadata || {}),
|
|
3275
|
+
now()
|
|
3276
|
+
]);
|
|
3277
|
+
return getTemplate(id, d);
|
|
3278
|
+
}
|
|
3279
|
+
function getTemplate(id, db) {
|
|
3280
|
+
const d = db || getDatabase();
|
|
3281
|
+
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(id);
|
|
3282
|
+
return row ? rowToTemplate(row) : null;
|
|
3283
|
+
}
|
|
3284
|
+
function listTemplates(db) {
|
|
3285
|
+
const d = db || getDatabase();
|
|
3286
|
+
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
3287
|
+
}
|
|
3288
|
+
function deleteTemplate(id, db) {
|
|
3289
|
+
const d = db || getDatabase();
|
|
3290
|
+
return d.run("DELETE FROM task_templates WHERE id = ?", [id]).changes > 0;
|
|
3291
|
+
}
|
|
3292
|
+
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
3293
|
+
const t = getTemplate(templateId, db);
|
|
3294
|
+
if (!t)
|
|
3295
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
3296
|
+
return {
|
|
3297
|
+
title: overrides.title || t.title_pattern,
|
|
3298
|
+
description: overrides.description ?? t.description ?? undefined,
|
|
3299
|
+
priority: overrides.priority ?? t.priority,
|
|
3300
|
+
tags: overrides.tags ?? t.tags,
|
|
3301
|
+
project_id: overrides.project_id ?? t.project_id ?? undefined,
|
|
3302
|
+
plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
|
|
3303
|
+
metadata: overrides.metadata ?? t.metadata,
|
|
3304
|
+
...overrides
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
var init_templates = __esm(() => {
|
|
3308
|
+
init_database();
|
|
3309
|
+
});
|
|
3310
|
+
|
|
3187
3311
|
// src/db/tasks.ts
|
|
3188
3312
|
var exports_tasks = {};
|
|
3189
3313
|
__export(exports_tasks, {
|
|
@@ -3250,8 +3374,8 @@ function createTask(input, db) {
|
|
|
3250
3374
|
const tags = input.tags || [];
|
|
3251
3375
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
3252
3376
|
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
3253
|
-
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)
|
|
3254
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3377
|
+
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)
|
|
3378
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3255
3379
|
id,
|
|
3256
3380
|
shortId,
|
|
3257
3381
|
input.project_id || null,
|
|
@@ -3276,7 +3400,8 @@ function createTask(input, db) {
|
|
|
3276
3400
|
null,
|
|
3277
3401
|
null,
|
|
3278
3402
|
input.recurrence_rule || null,
|
|
3279
|
-
input.recurrence_parent_id || null
|
|
3403
|
+
input.recurrence_parent_id || null,
|
|
3404
|
+
input.spawns_template_id || null
|
|
3280
3405
|
]);
|
|
3281
3406
|
if (tags.length > 0) {
|
|
3282
3407
|
insertTaskTags(id, tags, d);
|
|
@@ -3383,19 +3508,25 @@ function listTasks(filter = {}, db) {
|
|
|
3383
3508
|
} else if (filter.has_recurrence === false) {
|
|
3384
3509
|
conditions.push("recurrence_rule IS NULL");
|
|
3385
3510
|
}
|
|
3511
|
+
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
3512
|
+
if (filter.cursor) {
|
|
3513
|
+
try {
|
|
3514
|
+
const decoded = JSON.parse(Buffer.from(filter.cursor, "base64").toString("utf8"));
|
|
3515
|
+
conditions.push(`(${PRIORITY_RANK} > ? OR (${PRIORITY_RANK} = ? AND created_at < ?) OR (${PRIORITY_RANK} = ? AND created_at = ? AND id > ?))`);
|
|
3516
|
+
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
3517
|
+
} catch {}
|
|
3518
|
+
}
|
|
3386
3519
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3387
3520
|
let limitClause = "";
|
|
3388
3521
|
if (filter.limit) {
|
|
3389
3522
|
limitClause = " LIMIT ?";
|
|
3390
3523
|
params.push(filter.limit);
|
|
3391
|
-
if (filter.offset) {
|
|
3524
|
+
if (!filter.cursor && filter.offset) {
|
|
3392
3525
|
limitClause += " OFFSET ?";
|
|
3393
3526
|
params.push(filter.offset);
|
|
3394
3527
|
}
|
|
3395
3528
|
}
|
|
3396
|
-
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY
|
|
3397
|
-
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
3398
|
-
created_at DESC${limitClause}`).all(...params);
|
|
3529
|
+
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC${limitClause}`).all(...params);
|
|
3399
3530
|
return rows.map(rowToTask);
|
|
3400
3531
|
}
|
|
3401
3532
|
function countTasks(filter = {}, db) {
|
|
@@ -3640,10 +3771,25 @@ function completeTask(id, agentId, db, options) {
|
|
|
3640
3771
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
3641
3772
|
spawnedTask = spawnNextRecurrence(task, d);
|
|
3642
3773
|
}
|
|
3774
|
+
let spawnedFromTemplate = null;
|
|
3775
|
+
if (task.spawns_template_id) {
|
|
3776
|
+
try {
|
|
3777
|
+
const input = taskFromTemplate(task.spawns_template_id, {
|
|
3778
|
+
project_id: task.project_id ?? undefined,
|
|
3779
|
+
plan_id: task.plan_id ?? undefined,
|
|
3780
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
3781
|
+
assigned_to: task.assigned_to ?? undefined
|
|
3782
|
+
}, d);
|
|
3783
|
+
spawnedFromTemplate = createTask(input, d);
|
|
3784
|
+
} catch {}
|
|
3785
|
+
}
|
|
3643
3786
|
const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
|
|
3644
3787
|
if (spawnedTask) {
|
|
3645
3788
|
meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
|
|
3646
3789
|
}
|
|
3790
|
+
if (spawnedFromTemplate) {
|
|
3791
|
+
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
3792
|
+
}
|
|
3647
3793
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
3648
3794
|
}
|
|
3649
3795
|
function lockTask(id, agentId, db) {
|
|
@@ -3855,11 +4001,21 @@ function getNextTask(agentId, filters, db) {
|
|
|
3855
4001
|
}
|
|
3856
4002
|
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')");
|
|
3857
4003
|
const where = conditions.join(" AND ");
|
|
4004
|
+
let recentProjectIds = [];
|
|
4005
|
+
if (agentId) {
|
|
4006
|
+
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);
|
|
4007
|
+
recentProjectIds = recentRows.map((r) => r.project_id);
|
|
4008
|
+
}
|
|
3858
4009
|
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
3859
4010
|
if (agentId) {
|
|
3860
4011
|
sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
3861
4012
|
params.push(agentId);
|
|
3862
4013
|
}
|
|
4014
|
+
if (recentProjectIds.length > 0) {
|
|
4015
|
+
const placeholders = recentProjectIds.map(() => "?").join(",");
|
|
4016
|
+
sql += `CASE WHEN project_id IN (${placeholders}) THEN 0 ELSE 1 END, `;
|
|
4017
|
+
params.push(...recentProjectIds);
|
|
4018
|
+
}
|
|
3863
4019
|
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`;
|
|
3864
4020
|
const row = d.query(sql).get(...params);
|
|
3865
4021
|
return row ? rowToTask(row) : null;
|
|
@@ -4195,6 +4351,7 @@ var init_tasks = __esm(() => {
|
|
|
4195
4351
|
init_audit();
|
|
4196
4352
|
init_recurrence();
|
|
4197
4353
|
init_webhooks();
|
|
4354
|
+
init_templates();
|
|
4198
4355
|
});
|
|
4199
4356
|
|
|
4200
4357
|
// src/db/agents.ts
|
|
@@ -4204,6 +4361,7 @@ __export(exports_agents, {
|
|
|
4204
4361
|
updateAgent: () => updateAgent,
|
|
4205
4362
|
registerAgent: () => registerAgent,
|
|
4206
4363
|
listAgents: () => listAgents,
|
|
4364
|
+
isAgentConflict: () => isAgentConflict,
|
|
4207
4365
|
getOrgChart: () => getOrgChart,
|
|
4208
4366
|
getDirectReports: () => getDirectReports,
|
|
4209
4367
|
getAgentByName: () => getAgentByName,
|
|
@@ -4225,13 +4383,44 @@ function registerAgent(input, db) {
|
|
|
4225
4383
|
const normalizedName = input.name.trim().toLowerCase();
|
|
4226
4384
|
const existing = getAgentByName(normalizedName, d);
|
|
4227
4385
|
if (existing) {
|
|
4228
|
-
|
|
4386
|
+
const lastSeenMs = new Date(existing.last_seen_at).getTime();
|
|
4387
|
+
const isActive = Date.now() - lastSeenMs < AGENT_ACTIVE_WINDOW_MS;
|
|
4388
|
+
const sameSession = input.session_id && existing.session_id && input.session_id === existing.session_id;
|
|
4389
|
+
const differentSession = input.session_id && existing.session_id && input.session_id !== existing.session_id;
|
|
4390
|
+
if (isActive && differentSession) {
|
|
4391
|
+
const minutesAgo = Math.round((Date.now() - lastSeenMs) / 60000);
|
|
4392
|
+
return {
|
|
4393
|
+
conflict: true,
|
|
4394
|
+
existing_id: existing.id,
|
|
4395
|
+
existing_name: existing.name,
|
|
4396
|
+
last_seen_at: existing.last_seen_at,
|
|
4397
|
+
session_hint: existing.session_id ? existing.session_id.slice(0, 8) : null,
|
|
4398
|
+
working_dir: existing.working_dir,
|
|
4399
|
+
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.`
|
|
4400
|
+
};
|
|
4401
|
+
}
|
|
4402
|
+
const updates = ["last_seen_at = ?"];
|
|
4403
|
+
const params = [now()];
|
|
4404
|
+
if (input.session_id && !sameSession) {
|
|
4405
|
+
updates.push("session_id = ?");
|
|
4406
|
+
params.push(input.session_id);
|
|
4407
|
+
}
|
|
4408
|
+
if (input.working_dir) {
|
|
4409
|
+
updates.push("working_dir = ?");
|
|
4410
|
+
params.push(input.working_dir);
|
|
4411
|
+
}
|
|
4412
|
+
if (input.description) {
|
|
4413
|
+
updates.push("description = ?");
|
|
4414
|
+
params.push(input.description);
|
|
4415
|
+
}
|
|
4416
|
+
params.push(existing.id);
|
|
4417
|
+
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
4229
4418
|
return getAgent(existing.id, d);
|
|
4230
4419
|
}
|
|
4231
4420
|
const id = shortUuid();
|
|
4232
4421
|
const timestamp = now();
|
|
4233
|
-
d.run(`INSERT INTO agents (id, name, description, role, title, level, permissions, reports_to, org_id, metadata, created_at, last_seen_at)
|
|
4234
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4422
|
+
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)
|
|
4423
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4235
4424
|
id,
|
|
4236
4425
|
normalizedName,
|
|
4237
4426
|
input.description || null,
|
|
@@ -4243,10 +4432,15 @@ function registerAgent(input, db) {
|
|
|
4243
4432
|
input.org_id || null,
|
|
4244
4433
|
JSON.stringify(input.metadata || {}),
|
|
4245
4434
|
timestamp,
|
|
4246
|
-
timestamp
|
|
4435
|
+
timestamp,
|
|
4436
|
+
input.session_id || null,
|
|
4437
|
+
input.working_dir || null
|
|
4247
4438
|
]);
|
|
4248
4439
|
return getAgent(id, d);
|
|
4249
4440
|
}
|
|
4441
|
+
function isAgentConflict(result) {
|
|
4442
|
+
return result.conflict === true;
|
|
4443
|
+
}
|
|
4250
4444
|
function getAgent(id, db) {
|
|
4251
4445
|
const d = db || getDatabase();
|
|
4252
4446
|
const row = d.query("SELECT * FROM agents WHERE id = ?").get(id);
|
|
@@ -4336,8 +4530,10 @@ function getOrgChart(db) {
|
|
|
4336
4530
|
}
|
|
4337
4531
|
return buildTree(null);
|
|
4338
4532
|
}
|
|
4533
|
+
var AGENT_ACTIVE_WINDOW_MS;
|
|
4339
4534
|
var init_agents = __esm(() => {
|
|
4340
4535
|
init_database();
|
|
4536
|
+
AGENT_ACTIVE_WINDOW_MS = 30 * 60 * 1000;
|
|
4341
4537
|
});
|
|
4342
4538
|
|
|
4343
4539
|
// src/db/task-lists.ts
|
|
@@ -4541,68 +4737,95 @@ function rowToTask2(row) {
|
|
|
4541
4737
|
requires_approval: Boolean(row.requires_approval)
|
|
4542
4738
|
};
|
|
4543
4739
|
}
|
|
4740
|
+
function hasFts(db) {
|
|
4741
|
+
try {
|
|
4742
|
+
const result = db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='tasks_fts'").get();
|
|
4743
|
+
return result !== null;
|
|
4744
|
+
} catch {
|
|
4745
|
+
return false;
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
function escapeFtsQuery(q) {
|
|
4749
|
+
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
4750
|
+
}
|
|
4544
4751
|
function searchTasks(options, projectId, taskListId, db) {
|
|
4545
4752
|
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
4546
4753
|
const d = db || getDatabase();
|
|
4547
4754
|
clearExpiredLocks(d);
|
|
4548
|
-
const
|
|
4549
|
-
let sql
|
|
4550
|
-
|
|
4755
|
+
const params = [];
|
|
4756
|
+
let sql;
|
|
4757
|
+
if (hasFts(d) && opts.query.trim()) {
|
|
4758
|
+
const ftsQuery = escapeFtsQuery(opts.query);
|
|
4759
|
+
sql = `SELECT t.* FROM tasks t
|
|
4760
|
+
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
4761
|
+
WHERE tasks_fts MATCH ?`;
|
|
4762
|
+
params.push(ftsQuery);
|
|
4763
|
+
} else {
|
|
4764
|
+
const pattern = `%${opts.query}%`;
|
|
4765
|
+
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 ?))`;
|
|
4766
|
+
params.push(pattern, pattern, pattern);
|
|
4767
|
+
}
|
|
4551
4768
|
if (opts.project_id) {
|
|
4552
|
-
sql += " AND project_id = ?";
|
|
4769
|
+
sql += " AND t.project_id = ?";
|
|
4553
4770
|
params.push(opts.project_id);
|
|
4554
4771
|
}
|
|
4555
4772
|
if (opts.task_list_id) {
|
|
4556
|
-
sql += " AND task_list_id = ?";
|
|
4773
|
+
sql += " AND t.task_list_id = ?";
|
|
4557
4774
|
params.push(opts.task_list_id);
|
|
4558
4775
|
}
|
|
4559
4776
|
if (opts.status) {
|
|
4560
4777
|
if (Array.isArray(opts.status)) {
|
|
4561
|
-
sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
|
|
4778
|
+
sql += ` AND t.status IN (${opts.status.map(() => "?").join(",")})`;
|
|
4562
4779
|
params.push(...opts.status);
|
|
4563
4780
|
} else {
|
|
4564
|
-
sql += " AND status = ?";
|
|
4781
|
+
sql += " AND t.status = ?";
|
|
4565
4782
|
params.push(opts.status);
|
|
4566
4783
|
}
|
|
4567
4784
|
}
|
|
4568
4785
|
if (opts.priority) {
|
|
4569
4786
|
if (Array.isArray(opts.priority)) {
|
|
4570
|
-
sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
4787
|
+
sql += ` AND t.priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
4571
4788
|
params.push(...opts.priority);
|
|
4572
4789
|
} else {
|
|
4573
|
-
sql += " AND priority = ?";
|
|
4790
|
+
sql += " AND t.priority = ?";
|
|
4574
4791
|
params.push(opts.priority);
|
|
4575
4792
|
}
|
|
4576
4793
|
}
|
|
4577
4794
|
if (opts.assigned_to) {
|
|
4578
|
-
sql += " AND assigned_to = ?";
|
|
4795
|
+
sql += " AND t.assigned_to = ?";
|
|
4579
4796
|
params.push(opts.assigned_to);
|
|
4580
4797
|
}
|
|
4581
4798
|
if (opts.agent_id) {
|
|
4582
|
-
sql += " AND agent_id = ?";
|
|
4799
|
+
sql += " AND t.agent_id = ?";
|
|
4583
4800
|
params.push(opts.agent_id);
|
|
4584
4801
|
}
|
|
4585
4802
|
if (opts.created_after) {
|
|
4586
|
-
sql += " AND created_at > ?";
|
|
4803
|
+
sql += " AND t.created_at > ?";
|
|
4587
4804
|
params.push(opts.created_after);
|
|
4588
4805
|
}
|
|
4589
4806
|
if (opts.updated_after) {
|
|
4590
|
-
sql += " AND updated_at > ?";
|
|
4807
|
+
sql += " AND t.updated_at > ?";
|
|
4591
4808
|
params.push(opts.updated_after);
|
|
4592
4809
|
}
|
|
4593
4810
|
if (opts.has_dependencies === true) {
|
|
4594
|
-
sql += " AND id IN (SELECT task_id FROM task_dependencies)";
|
|
4811
|
+
sql += " AND t.id IN (SELECT task_id FROM task_dependencies)";
|
|
4595
4812
|
} else if (opts.has_dependencies === false) {
|
|
4596
|
-
sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
4813
|
+
sql += " AND t.id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
4597
4814
|
}
|
|
4598
4815
|
if (opts.is_blocked === true) {
|
|
4599
|
-
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')";
|
|
4816
|
+
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')";
|
|
4600
4817
|
} else if (opts.is_blocked === false) {
|
|
4601
|
-
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')";
|
|
4818
|
+
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')";
|
|
4819
|
+
}
|
|
4820
|
+
if (hasFts(d) && opts.query.trim()) {
|
|
4821
|
+
sql += ` ORDER BY bm25(tasks_fts),
|
|
4822
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
4823
|
+
t.created_at DESC`;
|
|
4824
|
+
} else {
|
|
4825
|
+
sql += ` ORDER BY
|
|
4826
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
4827
|
+
t.created_at DESC`;
|
|
4602
4828
|
}
|
|
4603
|
-
sql += ` ORDER BY
|
|
4604
|
-
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
4605
|
-
created_at DESC`;
|
|
4606
4829
|
const rows = d.query(sql).all(...params);
|
|
4607
4830
|
return rows.map(rowToTask2);
|
|
4608
4831
|
}
|
|
@@ -5142,73 +5365,6 @@ var init_sync = __esm(() => {
|
|
|
5142
5365
|
init_config();
|
|
5143
5366
|
});
|
|
5144
5367
|
|
|
5145
|
-
// src/db/templates.ts
|
|
5146
|
-
var exports_templates = {};
|
|
5147
|
-
__export(exports_templates, {
|
|
5148
|
-
taskFromTemplate: () => taskFromTemplate,
|
|
5149
|
-
listTemplates: () => listTemplates,
|
|
5150
|
-
getTemplate: () => getTemplate,
|
|
5151
|
-
deleteTemplate: () => deleteTemplate,
|
|
5152
|
-
createTemplate: () => createTemplate
|
|
5153
|
-
});
|
|
5154
|
-
function rowToTemplate(row) {
|
|
5155
|
-
return {
|
|
5156
|
-
...row,
|
|
5157
|
-
tags: JSON.parse(row.tags || "[]"),
|
|
5158
|
-
metadata: JSON.parse(row.metadata || "{}"),
|
|
5159
|
-
priority: row.priority || "medium"
|
|
5160
|
-
};
|
|
5161
|
-
}
|
|
5162
|
-
function createTemplate(input, db) {
|
|
5163
|
-
const d = db || getDatabase();
|
|
5164
|
-
const id = uuid();
|
|
5165
|
-
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
5166
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5167
|
-
id,
|
|
5168
|
-
input.name,
|
|
5169
|
-
input.title_pattern,
|
|
5170
|
-
input.description || null,
|
|
5171
|
-
input.priority || "medium",
|
|
5172
|
-
JSON.stringify(input.tags || []),
|
|
5173
|
-
input.project_id || null,
|
|
5174
|
-
input.plan_id || null,
|
|
5175
|
-
JSON.stringify(input.metadata || {}),
|
|
5176
|
-
now()
|
|
5177
|
-
]);
|
|
5178
|
-
return getTemplate(id, d);
|
|
5179
|
-
}
|
|
5180
|
-
function getTemplate(id, db) {
|
|
5181
|
-
const d = db || getDatabase();
|
|
5182
|
-
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(id);
|
|
5183
|
-
return row ? rowToTemplate(row) : null;
|
|
5184
|
-
}
|
|
5185
|
-
function listTemplates(db) {
|
|
5186
|
-
const d = db || getDatabase();
|
|
5187
|
-
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
5188
|
-
}
|
|
5189
|
-
function deleteTemplate(id, db) {
|
|
5190
|
-
const d = db || getDatabase();
|
|
5191
|
-
return d.run("DELETE FROM task_templates WHERE id = ?", [id]).changes > 0;
|
|
5192
|
-
}
|
|
5193
|
-
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
5194
|
-
const t = getTemplate(templateId, db);
|
|
5195
|
-
if (!t)
|
|
5196
|
-
throw new Error(`Template not found: ${templateId}`);
|
|
5197
|
-
return {
|
|
5198
|
-
title: overrides.title || t.title_pattern,
|
|
5199
|
-
description: overrides.description ?? t.description ?? undefined,
|
|
5200
|
-
priority: overrides.priority ?? t.priority,
|
|
5201
|
-
tags: overrides.tags ?? t.tags,
|
|
5202
|
-
project_id: overrides.project_id ?? t.project_id ?? undefined,
|
|
5203
|
-
plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
|
|
5204
|
-
metadata: overrides.metadata ?? t.metadata,
|
|
5205
|
-
...overrides
|
|
5206
|
-
};
|
|
5207
|
-
}
|
|
5208
|
-
var init_templates = __esm(() => {
|
|
5209
|
-
init_database();
|
|
5210
|
-
});
|
|
5211
|
-
|
|
5212
5368
|
// node_modules/zod/v3/helpers/util.js
|
|
5213
5369
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
5214
5370
|
const t = typeof data;
|
|
@@ -9256,15 +9412,17 @@ function formatTask(task) {
|
|
|
9256
9412
|
const recur = task.recurrence_rule ? ` [\u21BB]` : "";
|
|
9257
9413
|
return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
|
|
9258
9414
|
}
|
|
9259
|
-
function formatTaskDetail(task) {
|
|
9415
|
+
function formatTaskDetail(task, maxDescriptionChars) {
|
|
9260
9416
|
const parts = [
|
|
9261
9417
|
`ID: ${task.id}`,
|
|
9262
9418
|
`Title: ${task.title}`,
|
|
9263
9419
|
`Status: ${task.status}`,
|
|
9264
9420
|
`Priority: ${task.priority}`
|
|
9265
9421
|
];
|
|
9266
|
-
if (task.description)
|
|
9267
|
-
|
|
9422
|
+
if (task.description) {
|
|
9423
|
+
const desc = maxDescriptionChars && task.description.length > maxDescriptionChars ? task.description.slice(0, maxDescriptionChars) + "\u2026" : task.description;
|
|
9424
|
+
parts.push(`Description: ${desc}`);
|
|
9425
|
+
}
|
|
9268
9426
|
if (task.assigned_to)
|
|
9269
9427
|
parts.push(`Assigned to: ${task.assigned_to}`);
|
|
9270
9428
|
if (task.agent_id)
|
|
@@ -9322,7 +9480,9 @@ var init_mcp = __esm(() => {
|
|
|
9322
9480
|
"get_task",
|
|
9323
9481
|
"start_task",
|
|
9324
9482
|
"add_comment",
|
|
9325
|
-
"get_next_task"
|
|
9483
|
+
"get_next_task",
|
|
9484
|
+
"bootstrap",
|
|
9485
|
+
"get_tasks_changed_since"
|
|
9326
9486
|
]);
|
|
9327
9487
|
STANDARD_EXCLUDED = new Set([
|
|
9328
9488
|
"get_org_chart",
|
|
@@ -9356,7 +9516,8 @@ var init_mcp = __esm(() => {
|
|
|
9356
9516
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
9357
9517
|
estimated_minutes: exports_external.number().optional(),
|
|
9358
9518
|
requires_approval: exports_external.boolean().optional(),
|
|
9359
|
-
recurrence_rule: exports_external.string().optional()
|
|
9519
|
+
recurrence_rule: exports_external.string().optional(),
|
|
9520
|
+
spawns_template_id: exports_external.string().optional().describe("Template ID to auto-create as next task when this task is completed (pipeline/handoff chains)")
|
|
9360
9521
|
}, async (params) => {
|
|
9361
9522
|
try {
|
|
9362
9523
|
const resolved = { ...params };
|
|
@@ -9376,7 +9537,7 @@ var init_mcp = __esm(() => {
|
|
|
9376
9537
|
});
|
|
9377
9538
|
}
|
|
9378
9539
|
if (shouldRegisterTool("list_tasks")) {
|
|
9379
|
-
server.tool("list_tasks", "List tasks with optional filters and pagination.", {
|
|
9540
|
+
server.tool("list_tasks", "List tasks with optional filters and pagination. Default limit is 50 \u2014 use offset to page through results.", {
|
|
9380
9541
|
project_id: exports_external.string().optional(),
|
|
9381
9542
|
status: exports_external.union([
|
|
9382
9543
|
exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
|
|
@@ -9394,11 +9555,15 @@ var init_mcp = __esm(() => {
|
|
|
9394
9555
|
due_today: exports_external.boolean().optional(),
|
|
9395
9556
|
overdue: exports_external.boolean().optional(),
|
|
9396
9557
|
limit: exports_external.number().optional(),
|
|
9397
|
-
offset: exports_external.number().optional()
|
|
9558
|
+
offset: exports_external.number().optional(),
|
|
9559
|
+
summary_only: exports_external.boolean().optional().describe("When true, return only id, short_id, title, status, priority \u2014 minimal tokens for navigation"),
|
|
9560
|
+
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.")
|
|
9398
9561
|
}, async (params) => {
|
|
9399
9562
|
try {
|
|
9400
|
-
const { due_today, overdue, ...rest } = params;
|
|
9563
|
+
const { due_today, overdue, summary_only, ...rest } = params;
|
|
9401
9564
|
const resolved = { ...rest };
|
|
9565
|
+
if (resolved.limit === undefined)
|
|
9566
|
+
resolved.limit = 50;
|
|
9402
9567
|
if (resolved.project_id)
|
|
9403
9568
|
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
9404
9569
|
if (resolved.plan_id)
|
|
@@ -9420,6 +9585,9 @@ var init_mcp = __esm(() => {
|
|
|
9420
9585
|
return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
|
|
9421
9586
|
}
|
|
9422
9587
|
const text = tasks.map((t) => {
|
|
9588
|
+
if (summary_only) {
|
|
9589
|
+
return `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.priority} ${t.title}`;
|
|
9590
|
+
}
|
|
9423
9591
|
const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
|
|
9424
9592
|
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
9425
9593
|
const due = t.due_at ? ` due:${t.due_at.slice(0, 10)}` : "";
|
|
@@ -9427,10 +9595,23 @@ var init_mcp = __esm(() => {
|
|
|
9427
9595
|
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}${due}${recur}`;
|
|
9428
9596
|
}).join(`
|
|
9429
9597
|
`);
|
|
9430
|
-
const
|
|
9431
|
-
|
|
9598
|
+
const currentOffset = resolved.offset || 0;
|
|
9599
|
+
const hasMore = total > (resolved.cursor ? tasks.length : currentOffset + tasks.length);
|
|
9600
|
+
let paginationNote = `
|
|
9601
|
+
(showing ${tasks.length} of ${total}`;
|
|
9602
|
+
if (hasMore) {
|
|
9603
|
+
if (resolved.cursor || tasks.length > 0) {
|
|
9604
|
+
const last = tasks[tasks.length - 1];
|
|
9605
|
+
const priorityRank = { critical: 0, high: 1, medium: 2, low: 3 }[last.priority] ?? 3;
|
|
9606
|
+
const cursorPayload = Buffer.from(JSON.stringify({ p: priorityRank, c: last.created_at, i: last.id })).toString("base64");
|
|
9607
|
+
paginationNote += ` \u2014 next_cursor: ${cursorPayload}`;
|
|
9608
|
+
} else {
|
|
9609
|
+
paginationNote += ` \u2014 use offset: ${currentOffset + tasks.length} to get next page`;
|
|
9610
|
+
}
|
|
9611
|
+
}
|
|
9612
|
+
paginationNote += ")";
|
|
9432
9613
|
return { content: [{ type: "text", text: `${tasks.length} task(s):
|
|
9433
|
-
${text}${
|
|
9614
|
+
${text}${paginationNote}` }] };
|
|
9434
9615
|
} catch (e) {
|
|
9435
9616
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9436
9617
|
}
|
|
@@ -9438,14 +9619,15 @@ ${text}${pagination}` }] };
|
|
|
9438
9619
|
}
|
|
9439
9620
|
if (shouldRegisterTool("get_task")) {
|
|
9440
9621
|
server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
|
|
9441
|
-
id: exports_external.string()
|
|
9442
|
-
|
|
9622
|
+
id: exports_external.string(),
|
|
9623
|
+
max_description_chars: exports_external.number().optional().describe("Truncate description to this many characters (default: unlimited). Use 300-500 for quick checks.")
|
|
9624
|
+
}, async ({ id, max_description_chars }) => {
|
|
9443
9625
|
try {
|
|
9444
9626
|
const resolvedId = resolveId(id);
|
|
9445
9627
|
const task = getTaskWithRelations(resolvedId);
|
|
9446
9628
|
if (!task)
|
|
9447
9629
|
return { content: [{ type: "text", text: `Task not found: ${id}` }], isError: true };
|
|
9448
|
-
const parts = [formatTaskDetail(task)];
|
|
9630
|
+
const parts = [formatTaskDetail(task, max_description_chars)];
|
|
9449
9631
|
if (task.subtasks.length > 0) {
|
|
9450
9632
|
parts.push(`
|
|
9451
9633
|
Subtasks (${task.subtasks.length}):`);
|
|
@@ -9919,12 +10101,21 @@ ${text}` }] };
|
|
|
9919
10101
|
});
|
|
9920
10102
|
}
|
|
9921
10103
|
if (shouldRegisterTool("register_agent")) {
|
|
9922
|
-
server.tool("register_agent", "Register an agent (
|
|
10104
|
+
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.", {
|
|
9923
10105
|
name: exports_external.string(),
|
|
9924
|
-
description: exports_external.string().optional()
|
|
9925
|
-
|
|
10106
|
+
description: exports_external.string().optional(),
|
|
10107
|
+
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."),
|
|
10108
|
+
working_dir: exports_external.string().optional().describe("Working directory of this session \u2014 helps identify who holds the name in a conflict")
|
|
10109
|
+
}, async ({ name, description, session_id, working_dir }) => {
|
|
9926
10110
|
try {
|
|
9927
|
-
const
|
|
10111
|
+
const result = registerAgent({ name, description, session_id, working_dir });
|
|
10112
|
+
if (isAgentConflict(result)) {
|
|
10113
|
+
return {
|
|
10114
|
+
content: [{ type: "text", text: `CONFLICT: ${result.message}` }],
|
|
10115
|
+
isError: true
|
|
10116
|
+
};
|
|
10117
|
+
}
|
|
10118
|
+
const agent = result;
|
|
9928
10119
|
return {
|
|
9929
10120
|
content: [{
|
|
9930
10121
|
type: "text",
|
|
@@ -9932,6 +10123,7 @@ ${text}` }] };
|
|
|
9932
10123
|
ID: ${agent.id}
|
|
9933
10124
|
Name: ${agent.name}${agent.description ? `
|
|
9934
10125
|
Description: ${agent.description}` : ""}
|
|
10126
|
+
Session: ${agent.session_id ?? "unbound"}
|
|
9935
10127
|
Created: ${agent.created_at}
|
|
9936
10128
|
Last seen: ${agent.last_seen_at}`
|
|
9937
10129
|
}]
|
|
@@ -10673,7 +10865,7 @@ ${lines.join(`
|
|
|
10673
10865
|
return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
|
|
10674
10866
|
}
|
|
10675
10867
|
return { content: [{ type: "text", text: `next: ${formatTask(task)}
|
|
10676
|
-
${formatTaskDetail(task)}` }] };
|
|
10868
|
+
${formatTaskDetail(task, 300)}` }] };
|
|
10677
10869
|
} catch (e) {
|
|
10678
10870
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10679
10871
|
}
|
|
@@ -10759,7 +10951,7 @@ ${text}` }] };
|
|
|
10759
10951
|
return { content: [{ type: "text", text: "No tasks available to claim." }] };
|
|
10760
10952
|
}
|
|
10761
10953
|
return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
|
|
10762
|
-
${formatTaskDetail(task)}` }] };
|
|
10954
|
+
${formatTaskDetail(task, 300)}` }] };
|
|
10763
10955
|
} catch (e) {
|
|
10764
10956
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10765
10957
|
}
|
|
@@ -10844,6 +11036,8 @@ No pending tasks available.`);
|
|
|
10844
11036
|
}
|
|
10845
11037
|
}
|
|
10846
11038
|
}
|
|
11039
|
+
lines.push(`
|
|
11040
|
+
as_of: ${new Date().toISOString()} (pass to get_tasks_changed_since for incremental polling)`);
|
|
10847
11041
|
return { content: [{ type: "text", text: lines.join(`
|
|
10848
11042
|
`) }] };
|
|
10849
11043
|
} catch (e) {
|
|
@@ -10955,6 +11149,58 @@ ${checks.map((c) => ` ${c.status === "ok" ? "\u2713" : "\u26A0"} ${c.name}: ${c
|
|
|
10955
11149
|
}
|
|
10956
11150
|
if (next)
|
|
10957
11151
|
lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
|
|
11152
|
+
lines.push(`as_of: ${new Date().toISOString()}`);
|
|
11153
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
11154
|
+
`) }] };
|
|
11155
|
+
} catch (e) {
|
|
11156
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11157
|
+
}
|
|
11158
|
+
});
|
|
11159
|
+
}
|
|
11160
|
+
if (shouldRegisterTool("bootstrap")) {
|
|
11161
|
+
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.", {
|
|
11162
|
+
agent_id: exports_external.string().optional().describe("Your agent ID \u2014 used to find your active tasks and preferred next task"),
|
|
11163
|
+
project_id: exports_external.string().optional()
|
|
11164
|
+
}, async ({ agent_id, project_id }) => {
|
|
11165
|
+
try {
|
|
11166
|
+
const filters = {};
|
|
11167
|
+
if (project_id)
|
|
11168
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
11169
|
+
const f = Object.keys(filters).length > 0 ? filters : undefined;
|
|
11170
|
+
const status = getStatus(f, agent_id);
|
|
11171
|
+
const next = getNextTask(agent_id, f);
|
|
11172
|
+
const lines = [];
|
|
11173
|
+
const myActive = agent_id ? status.active_work.filter((w) => w.assigned_to === agent_id || w.locked_by === agent_id) : [];
|
|
11174
|
+
if (myActive.length > 0) {
|
|
11175
|
+
lines.push(`## Resuming`);
|
|
11176
|
+
for (const w of myActive) {
|
|
11177
|
+
lines.push(`[${w.short_id || w.id.slice(0, 8)}] ${w.priority} \u2014 ${w.title}`);
|
|
11178
|
+
}
|
|
11179
|
+
lines.push("");
|
|
11180
|
+
}
|
|
11181
|
+
if (next) {
|
|
11182
|
+
lines.push(`## Next task to claim`);
|
|
11183
|
+
lines.push(`[${next.short_id || next.id.slice(0, 8)}] ${next.priority} \u2014 ${next.title}`);
|
|
11184
|
+
if (next.description)
|
|
11185
|
+
lines.push(next.description.slice(0, 300) + (next.description.length > 300 ? "\u2026" : ""));
|
|
11186
|
+
lines.push(` call: claim_next_task(agent_id: "${agent_id || "<your-id>"}")`);
|
|
11187
|
+
lines.push("");
|
|
11188
|
+
} else {
|
|
11189
|
+
lines.push(`## No tasks available to claim`);
|
|
11190
|
+
lines.push("");
|
|
11191
|
+
}
|
|
11192
|
+
lines.push(`## Health`);
|
|
11193
|
+
lines.push(`${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
|
|
11194
|
+
if (status.stale_count > 0)
|
|
11195
|
+
lines.push(`\u26A0 ${status.stale_count} stale task(s)`);
|
|
11196
|
+
if (status.overdue_recurring > 0)
|
|
11197
|
+
lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
|
|
11198
|
+
if (status.active_work.length > 0) {
|
|
11199
|
+
const others = agent_id ? status.active_work.filter((w) => w.assigned_to !== agent_id && w.locked_by !== agent_id) : status.active_work;
|
|
11200
|
+
if (others.length > 0) {
|
|
11201
|
+
lines.push(`Other agents active: ${others.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
11202
|
+
}
|
|
11203
|
+
}
|
|
10958
11204
|
return { content: [{ type: "text", text: lines.join(`
|
|
10959
11205
|
`) }] };
|
|
10960
11206
|
} catch (e) {
|
|
@@ -11025,6 +11271,7 @@ ${checks.map((c) => ` ${c.status === "ok" ? "\u2713" : "\u26A0"} ${c.name}: ${c
|
|
|
11025
11271
|
"get_status",
|
|
11026
11272
|
"get_context",
|
|
11027
11273
|
"get_health",
|
|
11274
|
+
"bootstrap",
|
|
11028
11275
|
"decompose_task",
|
|
11029
11276
|
"set_task_status",
|
|
11030
11277
|
"set_task_priority",
|
|
@@ -11042,9 +11289,9 @@ ${checks.map((c) => ` ${c.status === "ok" ? "\u2713" : "\u26A0"} ${c.name}: ${c
|
|
|
11042
11289
|
create_task: `Create a new task.
|
|
11043
11290
|
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)
|
|
11044
11291
|
Example: {title: 'Daily standup', recurrence_rule: 'every weekday', priority: 'medium'}`,
|
|
11045
|
-
list_tasks: `List tasks with optional filters.
|
|
11046
|
-
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)
|
|
11047
|
-
Example: {status: ['pending', 'in_progress'],
|
|
11292
|
+
list_tasks: `List tasks with optional filters. Default limit is 50 to avoid context overflow \u2014 always paginate with offset for large lists.
|
|
11293
|
+
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)
|
|
11294
|
+
Example: {status: ['pending', 'in_progress'], limit: 50, offset: 0}`,
|
|
11048
11295
|
get_task: `Get full task details with subtasks, deps, and comments.
|
|
11049
11296
|
Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
|
|
11050
11297
|
Example: {id: 'a1b2c3d4'}`,
|
|
@@ -11103,9 +11350,9 @@ ${checks.map((c) => ` ${c.status === "ok" ? "\u2713" : "\u26A0"} ${c.name}: ${c
|
|
|
11103
11350
|
delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
|
|
11104
11351
|
Params: id(string, req)
|
|
11105
11352
|
Example: {id: 'a1b2c3d4'}`,
|
|
11106
|
-
register_agent: `Register an agent (
|
|
11107
|
-
Params: name(string, req \u2014 e.g.
|
|
11108
|
-
Example: {name: 'maximus',
|
|
11353
|
+
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.
|
|
11354
|
+
Params: name(string, req), description(string), session_id(string \u2014 unique per session, e.g. PID+timestamp), working_dir(string)
|
|
11355
|
+
Example: {name: 'maximus', session_id: 'abc123-1741952000', working_dir: '/workspace/platform'}`,
|
|
11109
11356
|
list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
|
|
11110
11357
|
get_agent: `Get agent details by ID or name. Provide one of id or name.
|
|
11111
11358
|
Params: id(string), name(string)
|
|
@@ -11194,8 +11441,8 @@ ${checks.map((c) => ` ${c.status === "ok" ? "\u2713" : "\u26A0"} ${c.name}: ${c
|
|
|
11194
11441
|
get_active_work: `See all in-progress tasks and who is working on them.
|
|
11195
11442
|
Params: project_id(string, optional), task_list_id(string, optional)
|
|
11196
11443
|
Example: {project_id: 'a1b2c3d4'}`,
|
|
11197
|
-
get_tasks_changed_since: `Get tasks modified after a timestamp \u2014
|
|
11198
|
-
Params: since(string, req \u2014 ISO date), project_id(string, optional), task_list_id(string, optional)
|
|
11444
|
+
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.
|
|
11445
|
+
Params: since(string, req \u2014 ISO date from prior as_of), project_id(string, optional), task_list_id(string, optional)
|
|
11199
11446
|
Example: {since: '2026-03-14T10:00:00Z'}`,
|
|
11200
11447
|
get_stale_tasks: `Find stale in_progress tasks with no recent activity.
|
|
11201
11448
|
Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
|
|
@@ -11203,6 +11450,9 @@ ${checks.map((c) => ` ${c.status === "ok" ? "\u2713" : "\u26A0"} ${c.name}: ${c
|
|
|
11203
11450
|
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.
|
|
11204
11451
|
Params: agent_id(string, optional \u2014 prefers tasks assigned to this agent for next_task), project_id(string, optional), task_list_id(string, optional)
|
|
11205
11452
|
Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
|
|
11453
|
+
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.
|
|
11454
|
+
Params: agent_id(string, optional but recommended), project_id(string, optional)
|
|
11455
|
+
Example: {agent_id: 'a1b2c3d4'}`,
|
|
11206
11456
|
decompose_task: `Break a task into subtasks in one call. Subtasks inherit project/plan/list from parent.
|
|
11207
11457
|
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)
|
|
11208
11458
|
Example: {parent_id: 'a1b2c3d4', subtasks: [{title: 'Research'}, {title: 'Implement'}, {title: 'Test'}], depends_on_prev: true}`,
|