@hasna/todos 0.11.32 → 0.11.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1717 -463
- package/dist/db/comments.d.ts +3 -0
- package/dist/db/comments.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts +1 -0
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-relations.d.ts +24 -0
- package/dist/db/task-relations.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +1 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.js +67 -8
- package/dist/lib/logger.d.ts +2 -2
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +2343 -560
- package/dist/mcp/tools/agents.d.ts.map +1 -1
- package/dist/mcp/tools/task-adv-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-auto-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp/tools/task-meta-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-workflow-tools.d.ts.map +1 -1
- package/dist/server/index.js +68 -9
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -810,6 +810,26 @@ var init_migrations = __esm(() => {
|
|
|
810
810
|
ALTER TABLE tasks ADD COLUMN current_step TEXT;
|
|
811
811
|
ALTER TABLE tasks ADD COLUMN total_steps INTEGER;
|
|
812
812
|
INSERT OR IGNORE INTO _migrations (id) VALUES (48);
|
|
813
|
+
`,
|
|
814
|
+
`
|
|
815
|
+
CREATE TABLE IF NOT EXISTS cycles (
|
|
816
|
+
id TEXT PRIMARY KEY,
|
|
817
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
818
|
+
number INTEGER NOT NULL,
|
|
819
|
+
start_date TEXT NOT NULL,
|
|
820
|
+
end_date TEXT NOT NULL,
|
|
821
|
+
duration_weeks INTEGER NOT NULL DEFAULT 1,
|
|
822
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
|
|
823
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
824
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
825
|
+
);
|
|
826
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_project ON cycles(project_id);
|
|
827
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_number ON cycles(number);
|
|
828
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_status ON cycles(status);
|
|
829
|
+
CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date);
|
|
830
|
+
ALTER TABLE tasks ADD COLUMN cycle_id TEXT REFERENCES cycles(id) ON DELETE SET NULL;
|
|
831
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL;
|
|
832
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (49);
|
|
813
833
|
`
|
|
814
834
|
];
|
|
815
835
|
});
|
|
@@ -13203,6 +13223,23 @@ function loadConfig() {
|
|
|
13203
13223
|
cached = config;
|
|
13204
13224
|
return cached;
|
|
13205
13225
|
}
|
|
13226
|
+
function getAgentPoolForProject(workingDir) {
|
|
13227
|
+
const config = loadConfig();
|
|
13228
|
+
if (workingDir && config.project_pools) {
|
|
13229
|
+
let bestKey = null;
|
|
13230
|
+
let bestLen = 0;
|
|
13231
|
+
for (const key of Object.keys(config.project_pools)) {
|
|
13232
|
+
if (workingDir.startsWith(key) && key.length > bestLen) {
|
|
13233
|
+
bestKey = key;
|
|
13234
|
+
bestLen = key.length;
|
|
13235
|
+
}
|
|
13236
|
+
}
|
|
13237
|
+
if (bestKey && config.project_pools[bestKey]) {
|
|
13238
|
+
return config.project_pools[bestKey];
|
|
13239
|
+
}
|
|
13240
|
+
}
|
|
13241
|
+
return config.agent_pool || null;
|
|
13242
|
+
}
|
|
13206
13243
|
function getCompletionGuardConfig(projectPath) {
|
|
13207
13244
|
const config = loadConfig();
|
|
13208
13245
|
const global2 = { ...GUARD_DEFAULTS, ...config.completion_guard };
|
|
@@ -13224,6 +13261,27 @@ var init_config2 = __esm(() => {
|
|
|
13224
13261
|
});
|
|
13225
13262
|
|
|
13226
13263
|
// src/db/projects.ts
|
|
13264
|
+
var exports_projects = {};
|
|
13265
|
+
__export(exports_projects, {
|
|
13266
|
+
updateProject: () => updateProject,
|
|
13267
|
+
slugify: () => slugify,
|
|
13268
|
+
setMachineLocalPath: () => setMachineLocalPath,
|
|
13269
|
+
renameProject: () => renameProject,
|
|
13270
|
+
removeProjectSource: () => removeProjectSource,
|
|
13271
|
+
removeMachineLocalPath: () => removeMachineLocalPath,
|
|
13272
|
+
nextTaskShortId: () => nextTaskShortId,
|
|
13273
|
+
listProjects: () => listProjects,
|
|
13274
|
+
listProjectSources: () => listProjectSources,
|
|
13275
|
+
listMachineLocalPaths: () => listMachineLocalPaths,
|
|
13276
|
+
getProjectWithSources: () => getProjectWithSources,
|
|
13277
|
+
getProjectByPath: () => getProjectByPath,
|
|
13278
|
+
getProject: () => getProject,
|
|
13279
|
+
getMachineLocalPath: () => getMachineLocalPath,
|
|
13280
|
+
ensureProject: () => ensureProject,
|
|
13281
|
+
deleteProject: () => deleteProject,
|
|
13282
|
+
createProject: () => createProject,
|
|
13283
|
+
addProjectSource: () => addProjectSource
|
|
13284
|
+
});
|
|
13227
13285
|
function slugify(name) {
|
|
13228
13286
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
13229
13287
|
}
|
|
@@ -13262,6 +13320,18 @@ function getProject(id, db) {
|
|
|
13262
13320
|
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
13263
13321
|
return row;
|
|
13264
13322
|
}
|
|
13323
|
+
function getProjectByPath(path, db) {
|
|
13324
|
+
const d = db || getDatabase();
|
|
13325
|
+
try {
|
|
13326
|
+
const machineId = getMachineId(d);
|
|
13327
|
+
const machineRow = d.query(`SELECT p.* FROM projects p
|
|
13328
|
+
JOIN project_machine_paths pmp ON pmp.project_id = p.id
|
|
13329
|
+
WHERE pmp.machine_id = ? AND pmp.path = ?`).get(machineId, path);
|
|
13330
|
+
if (machineRow)
|
|
13331
|
+
return machineRow;
|
|
13332
|
+
} catch {}
|
|
13333
|
+
return d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
13334
|
+
}
|
|
13265
13335
|
function listProjects(db) {
|
|
13266
13336
|
const d = db || getDatabase();
|
|
13267
13337
|
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
@@ -13293,11 +13363,141 @@ function updateProject(id, input, db) {
|
|
|
13293
13363
|
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
13294
13364
|
return getProject(id, d);
|
|
13295
13365
|
}
|
|
13366
|
+
function renameProject(id, input, db) {
|
|
13367
|
+
const d = db || getDatabase();
|
|
13368
|
+
const project = getProject(id, d);
|
|
13369
|
+
if (!project)
|
|
13370
|
+
throw new ProjectNotFoundError(id);
|
|
13371
|
+
let taskListsUpdated = 0;
|
|
13372
|
+
const ts = now();
|
|
13373
|
+
if (input.new_slug !== undefined) {
|
|
13374
|
+
const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
|
|
13375
|
+
if (!normalised)
|
|
13376
|
+
throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
|
|
13377
|
+
const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
|
|
13378
|
+
if (conflict)
|
|
13379
|
+
throw new Error(`Slug "${normalised}" is already used by another project`);
|
|
13380
|
+
const oldSlug = project.task_list_id;
|
|
13381
|
+
d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
|
|
13382
|
+
if (oldSlug) {
|
|
13383
|
+
const result = d.run("UPDATE task_lists SET slug = ?, name = COALESCE(?, name), updated_at = ? WHERE project_id = ? AND slug = ?", [normalised, input.name ?? null, ts, id, oldSlug]);
|
|
13384
|
+
taskListsUpdated = result.changes;
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
13387
|
+
if (input.name !== undefined) {
|
|
13388
|
+
d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
|
|
13389
|
+
}
|
|
13390
|
+
return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
|
|
13391
|
+
}
|
|
13296
13392
|
function deleteProject(id, db) {
|
|
13297
13393
|
const d = db || getDatabase();
|
|
13298
13394
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
13299
13395
|
return result.changes > 0;
|
|
13300
13396
|
}
|
|
13397
|
+
function rowToSource(row) {
|
|
13398
|
+
return {
|
|
13399
|
+
...row,
|
|
13400
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {}
|
|
13401
|
+
};
|
|
13402
|
+
}
|
|
13403
|
+
function addProjectSource(input, db) {
|
|
13404
|
+
const d = db || getDatabase();
|
|
13405
|
+
const id = uuid();
|
|
13406
|
+
const timestamp = now();
|
|
13407
|
+
d.run(`INSERT INTO project_sources (id, project_id, type, name, uri, description, metadata, created_at, updated_at)
|
|
13408
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
13409
|
+
id,
|
|
13410
|
+
input.project_id,
|
|
13411
|
+
input.type,
|
|
13412
|
+
input.name,
|
|
13413
|
+
input.uri,
|
|
13414
|
+
input.description || null,
|
|
13415
|
+
JSON.stringify(input.metadata || {}),
|
|
13416
|
+
timestamp,
|
|
13417
|
+
timestamp
|
|
13418
|
+
]);
|
|
13419
|
+
return rowToSource(d.query("SELECT * FROM project_sources WHERE id = ?").get(id));
|
|
13420
|
+
}
|
|
13421
|
+
function removeProjectSource(id, db) {
|
|
13422
|
+
const d = db || getDatabase();
|
|
13423
|
+
const result = d.run("DELETE FROM project_sources WHERE id = ?", [id]);
|
|
13424
|
+
return result.changes > 0;
|
|
13425
|
+
}
|
|
13426
|
+
function listProjectSources(projectId, db) {
|
|
13427
|
+
const d = db || getDatabase();
|
|
13428
|
+
const rows = d.query("SELECT * FROM project_sources WHERE project_id = ? ORDER BY name").all(projectId);
|
|
13429
|
+
return rows.map(rowToSource);
|
|
13430
|
+
}
|
|
13431
|
+
function getProjectWithSources(id, db) {
|
|
13432
|
+
const d = db || getDatabase();
|
|
13433
|
+
const project = getProject(id, d);
|
|
13434
|
+
if (!project)
|
|
13435
|
+
return null;
|
|
13436
|
+
project.sources = listProjectSources(id, d);
|
|
13437
|
+
return project;
|
|
13438
|
+
}
|
|
13439
|
+
function nextTaskShortId(projectId, db) {
|
|
13440
|
+
const d = db || getDatabase();
|
|
13441
|
+
const project = getProject(projectId, d);
|
|
13442
|
+
if (!project || !project.task_prefix)
|
|
13443
|
+
return null;
|
|
13444
|
+
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
|
|
13445
|
+
const updated = getProject(projectId, d);
|
|
13446
|
+
const padded = String(updated.task_counter).padStart(5, "0");
|
|
13447
|
+
return `${updated.task_prefix}-${padded}`;
|
|
13448
|
+
}
|
|
13449
|
+
function ensureProject(name, path, db) {
|
|
13450
|
+
const d = db || getDatabase();
|
|
13451
|
+
const existing = getProjectByPath(path, d);
|
|
13452
|
+
if (existing) {
|
|
13453
|
+
if (!existing.task_prefix) {
|
|
13454
|
+
const prefix = generatePrefix(existing.name, d);
|
|
13455
|
+
d.run("UPDATE projects SET task_prefix = ?, updated_at = ? WHERE id = ?", [prefix, now(), existing.id]);
|
|
13456
|
+
return getProject(existing.id, d);
|
|
13457
|
+
}
|
|
13458
|
+
setMachineLocalPath(existing.id, path, d);
|
|
13459
|
+
return existing;
|
|
13460
|
+
}
|
|
13461
|
+
const project = createProject({ name, path }, d);
|
|
13462
|
+
setMachineLocalPath(project.id, path, d);
|
|
13463
|
+
return project;
|
|
13464
|
+
}
|
|
13465
|
+
function setMachineLocalPath(projectId, path, db) {
|
|
13466
|
+
const d = db || getDatabase();
|
|
13467
|
+
const machineId = getMachineId(d);
|
|
13468
|
+
const ts = now();
|
|
13469
|
+
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, machineId);
|
|
13470
|
+
if (existing) {
|
|
13471
|
+
if (existing.path !== path) {
|
|
13472
|
+
d.run("UPDATE project_machine_paths SET path = ?, updated_at = ? WHERE id = ?", [path, ts, existing.id]);
|
|
13473
|
+
}
|
|
13474
|
+
return d.query("SELECT * FROM project_machine_paths WHERE id = ?").get(existing.id);
|
|
13475
|
+
}
|
|
13476
|
+
const id = uuid();
|
|
13477
|
+
d.run("INSERT INTO project_machine_paths (id, project_id, machine_id, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, machineId, path, ts, ts]);
|
|
13478
|
+
return d.query("SELECT * FROM project_machine_paths WHERE id = ?").get(id);
|
|
13479
|
+
}
|
|
13480
|
+
function getMachineLocalPath(projectId, db) {
|
|
13481
|
+
const d = db || getDatabase();
|
|
13482
|
+
try {
|
|
13483
|
+
const machineId = getMachineId(d);
|
|
13484
|
+
const row = d.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, machineId);
|
|
13485
|
+
if (row)
|
|
13486
|
+
return row.path;
|
|
13487
|
+
} catch {}
|
|
13488
|
+
const project = getProject(projectId, d);
|
|
13489
|
+
return project?.path ?? null;
|
|
13490
|
+
}
|
|
13491
|
+
function listMachineLocalPaths(projectId, db) {
|
|
13492
|
+
const d = db || getDatabase();
|
|
13493
|
+
return d.query("SELECT * FROM project_machine_paths WHERE project_id = ? ORDER BY machine_id").all(projectId);
|
|
13494
|
+
}
|
|
13495
|
+
function removeMachineLocalPath(projectId, machineId, db) {
|
|
13496
|
+
const d = db || getDatabase();
|
|
13497
|
+
const mid = machineId ?? getMachineId(d);
|
|
13498
|
+
const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
|
|
13499
|
+
return result.changes > 0;
|
|
13500
|
+
}
|
|
13301
13501
|
var init_projects = __esm(() => {
|
|
13302
13502
|
init_types2();
|
|
13303
13503
|
init_database();
|
|
@@ -13558,8 +13758,8 @@ function createTask(input, db) {
|
|
|
13558
13758
|
let id = uuid();
|
|
13559
13759
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
13560
13760
|
try {
|
|
13561
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_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, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
|
|
13562
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
13761
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
|
|
13762
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
13563
13763
|
id,
|
|
13564
13764
|
null,
|
|
13565
13765
|
input.project_id || null,
|
|
@@ -13581,6 +13781,10 @@ function createTask(input, db) {
|
|
|
13581
13781
|
timestamp,
|
|
13582
13782
|
input.due_at || null,
|
|
13583
13783
|
input.estimated_minutes || null,
|
|
13784
|
+
input.confidence ?? null,
|
|
13785
|
+
input.retry_count ?? 0,
|
|
13786
|
+
input.max_retries ?? 3,
|
|
13787
|
+
input.retry_after ?? null,
|
|
13584
13788
|
input.requires_approval ? 1 : 0,
|
|
13585
13789
|
null,
|
|
13586
13790
|
null,
|
|
@@ -13605,11 +13809,11 @@ function createTask(input, db) {
|
|
|
13605
13809
|
if (tags.length > 0) {
|
|
13606
13810
|
insertTaskTags(id, tags, d);
|
|
13607
13811
|
}
|
|
13608
|
-
const task =
|
|
13812
|
+
const task = getTask(id, d);
|
|
13609
13813
|
dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
|
|
13610
13814
|
return task;
|
|
13611
13815
|
}
|
|
13612
|
-
function
|
|
13816
|
+
function getTask(id, db) {
|
|
13613
13817
|
const d = db || getDatabase();
|
|
13614
13818
|
const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
13615
13819
|
if (!row)
|
|
@@ -13618,7 +13822,7 @@ function getTask2(id, db) {
|
|
|
13618
13822
|
}
|
|
13619
13823
|
function getTaskWithRelations(id, db) {
|
|
13620
13824
|
const d = db || getDatabase();
|
|
13621
|
-
const task =
|
|
13825
|
+
const task = getTask(id, d);
|
|
13622
13826
|
if (!task)
|
|
13623
13827
|
return null;
|
|
13624
13828
|
const subtaskRows = d.query("SELECT * FROM tasks WHERE parent_id = ? ORDER BY created_at").all(id);
|
|
@@ -13632,7 +13836,7 @@ function getTaskWithRelations(id, db) {
|
|
|
13632
13836
|
WHERE td.depends_on = ?`).all(id);
|
|
13633
13837
|
const blocked_by = blockedByRows.map(rowToTask);
|
|
13634
13838
|
const comments = d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(id);
|
|
13635
|
-
const parent = task.parent_id ?
|
|
13839
|
+
const parent = task.parent_id ? getTask(task.parent_id, d) : null;
|
|
13636
13840
|
const checklist = getChecklist(id, d);
|
|
13637
13841
|
return {
|
|
13638
13842
|
...task,
|
|
@@ -13819,14 +14023,16 @@ function countTasks(filter = {}, db) {
|
|
|
13819
14023
|
}
|
|
13820
14024
|
function updateTask(id, input, db) {
|
|
13821
14025
|
const d = db || getDatabase();
|
|
13822
|
-
const task =
|
|
14026
|
+
const task = getTask(id, d);
|
|
13823
14027
|
if (!task)
|
|
13824
14028
|
throw new TaskNotFoundError(id);
|
|
13825
14029
|
if (task.version !== input.version) {
|
|
13826
14030
|
throw new VersionConflictError(id, input.version, task.version);
|
|
13827
14031
|
}
|
|
14032
|
+
const timestamp = now();
|
|
14033
|
+
const completionTimestamp = input.completed_at ?? timestamp;
|
|
13828
14034
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
13829
|
-
const params = [
|
|
14035
|
+
const params = [timestamp];
|
|
13830
14036
|
if (input.title !== undefined) {
|
|
13831
14037
|
sets.push("title = ?");
|
|
13832
14038
|
params.push(input.title);
|
|
@@ -13843,13 +14049,17 @@ function updateTask(id, input, db) {
|
|
|
13843
14049
|
params.push(input.status);
|
|
13844
14050
|
if (input.status === "completed") {
|
|
13845
14051
|
sets.push("completed_at = ?");
|
|
13846
|
-
params.push(
|
|
14052
|
+
params.push(completionTimestamp);
|
|
13847
14053
|
}
|
|
13848
14054
|
}
|
|
13849
14055
|
if (input.priority !== undefined) {
|
|
13850
14056
|
sets.push("priority = ?");
|
|
13851
14057
|
params.push(input.priority);
|
|
13852
14058
|
}
|
|
14059
|
+
if (input.project_id !== undefined) {
|
|
14060
|
+
sets.push("project_id = ?");
|
|
14061
|
+
params.push(input.project_id);
|
|
14062
|
+
}
|
|
13853
14063
|
if (input.assigned_to !== undefined) {
|
|
13854
14064
|
sets.push("assigned_to = ?");
|
|
13855
14065
|
params.push(input.assigned_to);
|
|
@@ -13878,6 +14088,30 @@ function updateTask(id, input, db) {
|
|
|
13878
14088
|
sets.push("estimated_minutes = ?");
|
|
13879
14089
|
params.push(input.estimated_minutes);
|
|
13880
14090
|
}
|
|
14091
|
+
if (input.actual_minutes !== undefined) {
|
|
14092
|
+
sets.push("actual_minutes = ?");
|
|
14093
|
+
params.push(input.actual_minutes);
|
|
14094
|
+
}
|
|
14095
|
+
if (input.completed_at !== undefined && input.status !== "completed") {
|
|
14096
|
+
sets.push("completed_at = ?");
|
|
14097
|
+
params.push(input.completed_at);
|
|
14098
|
+
}
|
|
14099
|
+
if (input.confidence !== undefined) {
|
|
14100
|
+
sets.push("confidence = ?");
|
|
14101
|
+
params.push(input.confidence);
|
|
14102
|
+
}
|
|
14103
|
+
if (input.retry_count !== undefined) {
|
|
14104
|
+
sets.push("retry_count = ?");
|
|
14105
|
+
params.push(input.retry_count);
|
|
14106
|
+
}
|
|
14107
|
+
if (input.max_retries !== undefined) {
|
|
14108
|
+
sets.push("max_retries = ?");
|
|
14109
|
+
params.push(input.max_retries);
|
|
14110
|
+
}
|
|
14111
|
+
if (input.retry_after !== undefined) {
|
|
14112
|
+
sets.push("retry_after = ?");
|
|
14113
|
+
params.push(input.retry_after);
|
|
14114
|
+
}
|
|
13881
14115
|
if (input.requires_approval !== undefined) {
|
|
13882
14116
|
sets.push("requires_approval = ?");
|
|
13883
14117
|
params.push(input.requires_approval ? 1 : 0);
|
|
@@ -13899,7 +14133,7 @@ function updateTask(id, input, db) {
|
|
|
13899
14133
|
params.push(id, input.version);
|
|
13900
14134
|
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
13901
14135
|
if (result.changes === 0) {
|
|
13902
|
-
const current =
|
|
14136
|
+
const current = getTask(id, d);
|
|
13903
14137
|
throw new VersionConflictError(id, input.version, current?.version ?? -1);
|
|
13904
14138
|
}
|
|
13905
14139
|
if (input.tags !== undefined) {
|
|
@@ -13928,11 +14162,16 @@ function updateTask(id, input, db) {
|
|
|
13928
14162
|
tags: input.tags ?? task.tags,
|
|
13929
14163
|
metadata: input.metadata ?? task.metadata,
|
|
13930
14164
|
version: task.version + 1,
|
|
13931
|
-
updated_at:
|
|
13932
|
-
completed_at: input.status === "completed" ?
|
|
14165
|
+
updated_at: timestamp,
|
|
14166
|
+
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : task.completed_at,
|
|
14167
|
+
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
14168
|
+
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
14169
|
+
retry_count: input.retry_count ?? task.retry_count,
|
|
14170
|
+
max_retries: input.max_retries ?? task.max_retries,
|
|
14171
|
+
retry_after: input.retry_after !== undefined ? input.retry_after : task.retry_after,
|
|
13933
14172
|
requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
|
|
13934
14173
|
approved_by: input.approved_by ?? task.approved_by,
|
|
13935
|
-
approved_at: input.approved_by ?
|
|
14174
|
+
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
13936
14175
|
};
|
|
13937
14176
|
}
|
|
13938
14177
|
function deleteTask(id, db) {
|
|
@@ -14085,9 +14324,9 @@ var init_templates = __esm(() => {
|
|
|
14085
14324
|
// src/db/task-graph.ts
|
|
14086
14325
|
function addDependency(taskId, dependsOn, db) {
|
|
14087
14326
|
const d = db || getDatabase();
|
|
14088
|
-
if (!
|
|
14327
|
+
if (!getTask(taskId, d))
|
|
14089
14328
|
throw new TaskNotFoundError(taskId);
|
|
14090
|
-
if (!
|
|
14329
|
+
if (!getTask(dependsOn, d))
|
|
14091
14330
|
throw new TaskNotFoundError(dependsOn);
|
|
14092
14331
|
if (wouldCreateCycle(taskId, dependsOn, d)) {
|
|
14093
14332
|
throw new DependencyCycleError(taskId, dependsOn);
|
|
@@ -14109,7 +14348,7 @@ function getTaskDependents(taskId, db) {
|
|
|
14109
14348
|
}
|
|
14110
14349
|
function cloneTask(taskId, overrides, db) {
|
|
14111
14350
|
const d = db || getDatabase();
|
|
14112
|
-
const source =
|
|
14351
|
+
const source = getTask(taskId, d);
|
|
14113
14352
|
if (!source)
|
|
14114
14353
|
throw new TaskNotFoundError(taskId);
|
|
14115
14354
|
const input = {
|
|
@@ -14132,13 +14371,13 @@ function cloneTask(taskId, overrides, db) {
|
|
|
14132
14371
|
}
|
|
14133
14372
|
function getTaskGraph(taskId, direction = "both", db) {
|
|
14134
14373
|
const d = db || getDatabase();
|
|
14135
|
-
const task =
|
|
14374
|
+
const task = getTask(taskId, d);
|
|
14136
14375
|
if (!task)
|
|
14137
14376
|
throw new TaskNotFoundError(taskId);
|
|
14138
14377
|
function toNode(t) {
|
|
14139
14378
|
const deps = getTaskDependencies(t.id, d);
|
|
14140
14379
|
const hasUnfinishedDeps = deps.some((dep) => {
|
|
14141
|
-
const depTask =
|
|
14380
|
+
const depTask = getTask(dep.depends_on, d);
|
|
14142
14381
|
return depTask && depTask.status !== "completed";
|
|
14143
14382
|
});
|
|
14144
14383
|
return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
|
|
@@ -14149,7 +14388,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
14149
14388
|
visited.add(id);
|
|
14150
14389
|
const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
|
|
14151
14390
|
return deps.map((dep) => {
|
|
14152
|
-
const depTask =
|
|
14391
|
+
const depTask = getTask(dep.depends_on, d);
|
|
14153
14392
|
if (!depTask)
|
|
14154
14393
|
return null;
|
|
14155
14394
|
return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
|
|
@@ -14161,7 +14400,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
14161
14400
|
visited.add(id);
|
|
14162
14401
|
const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
|
|
14163
14402
|
return dependents.map((dep) => {
|
|
14164
|
-
const depTask =
|
|
14403
|
+
const depTask = getTask(dep.task_id, d);
|
|
14165
14404
|
if (!depTask)
|
|
14166
14405
|
return null;
|
|
14167
14406
|
return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
|
|
@@ -14174,7 +14413,7 @@ function getTaskGraph(taskId, direction = "both", db) {
|
|
|
14174
14413
|
}
|
|
14175
14414
|
function moveTask(taskId, target, db) {
|
|
14176
14415
|
const d = db || getDatabase();
|
|
14177
|
-
const task =
|
|
14416
|
+
const task = getTask(taskId, d);
|
|
14178
14417
|
if (!task)
|
|
14179
14418
|
throw new TaskNotFoundError(taskId);
|
|
14180
14419
|
const sets = ["updated_at = ?", "version = version + 1"];
|
|
@@ -14193,7 +14432,7 @@ function moveTask(taskId, target, db) {
|
|
|
14193
14432
|
}
|
|
14194
14433
|
params.push(taskId);
|
|
14195
14434
|
d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
14196
|
-
return
|
|
14435
|
+
return getTask(taskId, d);
|
|
14197
14436
|
}
|
|
14198
14437
|
function wouldCreateCycle(taskId, dependsOn, db) {
|
|
14199
14438
|
const visited = new Set;
|
|
@@ -14226,7 +14465,7 @@ function getBlockingDeps(id, db) {
|
|
|
14226
14465
|
return [];
|
|
14227
14466
|
const blocking = [];
|
|
14228
14467
|
for (const dep of deps) {
|
|
14229
|
-
const task =
|
|
14468
|
+
const task = getTask(dep.depends_on, d);
|
|
14230
14469
|
if (task && task.status !== "completed")
|
|
14231
14470
|
blocking.push(task);
|
|
14232
14471
|
}
|
|
@@ -14234,7 +14473,7 @@ function getBlockingDeps(id, db) {
|
|
|
14234
14473
|
}
|
|
14235
14474
|
function startTask(id, agentId, db) {
|
|
14236
14475
|
const d = db || getDatabase();
|
|
14237
|
-
const task =
|
|
14476
|
+
const task = getTask(id, d);
|
|
14238
14477
|
if (!task)
|
|
14239
14478
|
throw new TaskNotFoundError(id);
|
|
14240
14479
|
const blocking = getBlockingDeps(id, d);
|
|
@@ -14257,7 +14496,7 @@ function startTask(id, agentId, db) {
|
|
|
14257
14496
|
}
|
|
14258
14497
|
function completeTask(id, agentId, db, options) {
|
|
14259
14498
|
const d = db || getDatabase();
|
|
14260
|
-
const task =
|
|
14499
|
+
const task = getTask(id, d);
|
|
14261
14500
|
if (!task)
|
|
14262
14501
|
throw new TaskNotFoundError(id);
|
|
14263
14502
|
if (agentId && task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
|
|
@@ -14273,14 +14512,14 @@ function completeTask(id, agentId, db, options) {
|
|
|
14273
14512
|
completionMeta._completion = { confidence: options.confidence };
|
|
14274
14513
|
}
|
|
14275
14514
|
const hasMeta = Object.keys(completionMeta).length > 0;
|
|
14276
|
-
const timestamp = now();
|
|
14515
|
+
const timestamp = options?.completed_at || now();
|
|
14277
14516
|
const confidence = options?.confidence !== undefined ? options.confidence : null;
|
|
14278
14517
|
const tx = d.transaction(() => {
|
|
14279
14518
|
if (hasMeta) {
|
|
14280
14519
|
const meta2 = { ...task.metadata, ...completionMeta };
|
|
14281
14520
|
const metaResult = d.run("UPDATE tasks SET metadata = ?, version = version + 1, updated_at = ? WHERE id = ? AND version = ?", [JSON.stringify(meta2), timestamp, id, task.version]);
|
|
14282
14521
|
if (metaResult.changes === 0) {
|
|
14283
|
-
const current =
|
|
14522
|
+
const current = getTask(id, d);
|
|
14284
14523
|
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
14285
14524
|
}
|
|
14286
14525
|
}
|
|
@@ -14337,7 +14576,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
14337
14576
|
}
|
|
14338
14577
|
function lockTask(id, agentId, db) {
|
|
14339
14578
|
const d = db || getDatabase();
|
|
14340
|
-
const task =
|
|
14579
|
+
const task = getTask(id, d);
|
|
14341
14580
|
if (!task)
|
|
14342
14581
|
throw new TaskNotFoundError(id);
|
|
14343
14582
|
if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
|
|
@@ -14348,7 +14587,7 @@ function lockTask(id, agentId, db) {
|
|
|
14348
14587
|
const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
|
|
14349
14588
|
WHERE id = ? AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp, timestamp, id, agentId, cutoff]);
|
|
14350
14589
|
if (result.changes === 0) {
|
|
14351
|
-
const current =
|
|
14590
|
+
const current = getTask(id, d);
|
|
14352
14591
|
if (!current)
|
|
14353
14592
|
throw new TaskNotFoundError(id);
|
|
14354
14593
|
if (current.locked_by && !isLockExpired(current.locked_at)) {
|
|
@@ -14364,7 +14603,7 @@ function lockTask(id, agentId, db) {
|
|
|
14364
14603
|
}
|
|
14365
14604
|
function unlockTask(id, agentId, db) {
|
|
14366
14605
|
const d = db || getDatabase();
|
|
14367
|
-
const task =
|
|
14606
|
+
const task = getTask(id, d);
|
|
14368
14607
|
if (!task)
|
|
14369
14608
|
throw new TaskNotFoundError(id);
|
|
14370
14609
|
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
@@ -14465,7 +14704,7 @@ function getTasksChangedSince(since, filters, db) {
|
|
|
14465
14704
|
}
|
|
14466
14705
|
function failTask(id, agentId, reason, options, db) {
|
|
14467
14706
|
const d = db || getDatabase();
|
|
14468
|
-
const task =
|
|
14707
|
+
const task = getTask(id, d);
|
|
14469
14708
|
if (!task)
|
|
14470
14709
|
throw new TaskNotFoundError(id);
|
|
14471
14710
|
const meta = {
|
|
@@ -14664,7 +14903,7 @@ function getStatus(filters, agentId, options, db) {
|
|
|
14664
14903
|
}
|
|
14665
14904
|
function decomposeTasks(parentId, subtasks, options, db) {
|
|
14666
14905
|
const d = db || getDatabase();
|
|
14667
|
-
const parent =
|
|
14906
|
+
const parent = getTask(parentId, d);
|
|
14668
14907
|
if (!parent)
|
|
14669
14908
|
throw new TaskNotFoundError(parentId);
|
|
14670
14909
|
const created = [];
|
|
@@ -14695,7 +14934,7 @@ function decomposeTasks(parentId, subtasks, options, db) {
|
|
|
14695
14934
|
function setTaskStatus(id, status, _agentId, db) {
|
|
14696
14935
|
const d = db || getDatabase();
|
|
14697
14936
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
14698
|
-
const task =
|
|
14937
|
+
const task = getTask(id, d);
|
|
14699
14938
|
if (!task)
|
|
14700
14939
|
throw new TaskNotFoundError(id);
|
|
14701
14940
|
if (task.status === status)
|
|
@@ -14713,7 +14952,7 @@ function setTaskStatus(id, status, _agentId, db) {
|
|
|
14713
14952
|
function setTaskPriority(id, priority, _agentId, db) {
|
|
14714
14953
|
const d = db || getDatabase();
|
|
14715
14954
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
14716
|
-
const task =
|
|
14955
|
+
const task = getTask(id, d);
|
|
14717
14956
|
if (!task)
|
|
14718
14957
|
throw new TaskNotFoundError(id);
|
|
14719
14958
|
if (task.priority === priority)
|
|
@@ -14821,7 +15060,7 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
14821
15060
|
const tx = d.transaction(() => {
|
|
14822
15061
|
for (const id of taskIds) {
|
|
14823
15062
|
try {
|
|
14824
|
-
const task =
|
|
15063
|
+
const task = getTask(id, d);
|
|
14825
15064
|
if (!task) {
|
|
14826
15065
|
failed.push({ id, error: "Task not found" });
|
|
14827
15066
|
continue;
|
|
@@ -14836,6 +15075,30 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
14836
15075
|
tx();
|
|
14837
15076
|
return { updated, failed };
|
|
14838
15077
|
}
|
|
15078
|
+
function bulkDeleteTasks(taskIds, force = false, db) {
|
|
15079
|
+
const d = db || getDatabase();
|
|
15080
|
+
let deleted = 0;
|
|
15081
|
+
let skipped = 0;
|
|
15082
|
+
const failed = [];
|
|
15083
|
+
const tx = d.transaction(() => {
|
|
15084
|
+
for (const id of taskIds) {
|
|
15085
|
+
try {
|
|
15086
|
+
const childCount = d.query("SELECT COUNT(*) as count FROM tasks WHERE parent_id = ?").get(id);
|
|
15087
|
+
if (!force && childCount.count > 0) {
|
|
15088
|
+
skipped++;
|
|
15089
|
+
continue;
|
|
15090
|
+
}
|
|
15091
|
+
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
15092
|
+
if (result.changes > 0)
|
|
15093
|
+
deleted++;
|
|
15094
|
+
} catch (e) {
|
|
15095
|
+
failed.push({ id, error: e instanceof Error ? e.message : String(e) });
|
|
15096
|
+
}
|
|
15097
|
+
}
|
|
15098
|
+
});
|
|
15099
|
+
tx();
|
|
15100
|
+
return { deleted, skipped, failed };
|
|
15101
|
+
}
|
|
14839
15102
|
function archiveTasks(options, db) {
|
|
14840
15103
|
const d = db || getDatabase();
|
|
14841
15104
|
const conditions = ["archived_at IS NULL"];
|
|
@@ -14860,10 +15123,33 @@ function archiveTasks(options, db) {
|
|
|
14860
15123
|
const result = d.run(`UPDATE tasks SET archived_at = ? WHERE ${conditions.join(" AND ")}`, [ts, ...params]);
|
|
14861
15124
|
return { archived: result.changes };
|
|
14862
15125
|
}
|
|
15126
|
+
function archiveCompletedTasks(days = 7, projectId, db) {
|
|
15127
|
+
return archiveTasks({
|
|
15128
|
+
project_id: projectId,
|
|
15129
|
+
older_than_days: days,
|
|
15130
|
+
status: ["completed"]
|
|
15131
|
+
}, db).archived;
|
|
15132
|
+
}
|
|
15133
|
+
function getArchivedTasks(opts = {}, db) {
|
|
15134
|
+
const d = db || getDatabase();
|
|
15135
|
+
const conditions = ["archived_at IS NOT NULL"];
|
|
15136
|
+
const params = [];
|
|
15137
|
+
if (opts.project_id) {
|
|
15138
|
+
conditions.push("project_id = ?");
|
|
15139
|
+
params.push(opts.project_id);
|
|
15140
|
+
}
|
|
15141
|
+
let limitClause = "";
|
|
15142
|
+
if (opts.limit) {
|
|
15143
|
+
limitClause = " LIMIT ?";
|
|
15144
|
+
params.push(opts.limit);
|
|
15145
|
+
}
|
|
15146
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY archived_at DESC${limitClause}`).all(...params);
|
|
15147
|
+
return rows.map(rowToTask);
|
|
15148
|
+
}
|
|
14863
15149
|
function unarchiveTask(id, db) {
|
|
14864
15150
|
const d = db || getDatabase();
|
|
14865
15151
|
d.run("UPDATE tasks SET archived_at = NULL WHERE id = ?", [id]);
|
|
14866
|
-
return
|
|
15152
|
+
return getTask(id, d);
|
|
14867
15153
|
}
|
|
14868
15154
|
function getOverdueTasks(projectId, db) {
|
|
14869
15155
|
const d = db || getDatabase();
|
|
@@ -14878,6 +15164,80 @@ function getOverdueTasks(projectId, db) {
|
|
|
14878
15164
|
const rows = d.query(query).all(...params);
|
|
14879
15165
|
return rows.map(rowToTask);
|
|
14880
15166
|
}
|
|
15167
|
+
function notifyUpcomingDeadlines(opts = {}, db) {
|
|
15168
|
+
const d = db || getDatabase();
|
|
15169
|
+
const hours = opts.hours ?? 24;
|
|
15170
|
+
const start = new Date().toISOString();
|
|
15171
|
+
const end = new Date(Date.now() + hours * 60 * 60 * 1000).toISOString();
|
|
15172
|
+
const conditions = [
|
|
15173
|
+
"archived_at IS NULL",
|
|
15174
|
+
"due_at IS NOT NULL",
|
|
15175
|
+
"due_at >= ?",
|
|
15176
|
+
"due_at <= ?",
|
|
15177
|
+
"status NOT IN ('completed', 'cancelled', 'failed')"
|
|
15178
|
+
];
|
|
15179
|
+
const params = [start, end];
|
|
15180
|
+
if (opts.project_id) {
|
|
15181
|
+
conditions.push("project_id = ?");
|
|
15182
|
+
params.push(opts.project_id);
|
|
15183
|
+
}
|
|
15184
|
+
if (opts.agent_id) {
|
|
15185
|
+
conditions.push("assigned_to = ?");
|
|
15186
|
+
params.push(opts.agent_id);
|
|
15187
|
+
}
|
|
15188
|
+
const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC`).all(...params);
|
|
15189
|
+
return rows.map(rowToTask);
|
|
15190
|
+
}
|
|
15191
|
+
function getBlockedTasks(projectId, db) {
|
|
15192
|
+
const d = db || getDatabase();
|
|
15193
|
+
const params = [];
|
|
15194
|
+
let projectClause = "";
|
|
15195
|
+
if (projectId) {
|
|
15196
|
+
projectClause = "AND t.project_id = ?";
|
|
15197
|
+
params.push(projectId);
|
|
15198
|
+
}
|
|
15199
|
+
const rows = d.query(`
|
|
15200
|
+
SELECT t.*, GROUP_CONCAT(dep.id) AS blocked_by_ids
|
|
15201
|
+
FROM tasks t
|
|
15202
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
15203
|
+
JOIN tasks dep ON dep.id = td.depends_on
|
|
15204
|
+
WHERE t.archived_at IS NULL
|
|
15205
|
+
AND t.status NOT IN ('completed', 'cancelled', 'failed')
|
|
15206
|
+
AND dep.status NOT IN ('completed', 'cancelled')
|
|
15207
|
+
${projectClause}
|
|
15208
|
+
GROUP BY t.id
|
|
15209
|
+
ORDER BY t.priority DESC, t.created_at DESC
|
|
15210
|
+
`).all(...params);
|
|
15211
|
+
return rows.map((row) => ({
|
|
15212
|
+
...rowToTask(row),
|
|
15213
|
+
blocked_by: row.blocked_by_ids ? row.blocked_by_ids.split(",") : []
|
|
15214
|
+
}));
|
|
15215
|
+
}
|
|
15216
|
+
function getBlockingTasks(projectId, db) {
|
|
15217
|
+
const d = db || getDatabase();
|
|
15218
|
+
const params = [];
|
|
15219
|
+
let projectClause = "";
|
|
15220
|
+
if (projectId) {
|
|
15221
|
+
projectClause = "AND blocker.project_id = ?";
|
|
15222
|
+
params.push(projectId);
|
|
15223
|
+
}
|
|
15224
|
+
const rows = d.query(`
|
|
15225
|
+
SELECT blocker.*, COUNT(DISTINCT blocked.id) AS blocking_count
|
|
15226
|
+
FROM tasks blocker
|
|
15227
|
+
JOIN task_dependencies td ON td.depends_on = blocker.id
|
|
15228
|
+
JOIN tasks blocked ON blocked.id = td.task_id
|
|
15229
|
+
WHERE blocker.archived_at IS NULL
|
|
15230
|
+
AND blocker.status NOT IN ('completed', 'cancelled', 'failed')
|
|
15231
|
+
AND blocked.status NOT IN ('completed', 'cancelled', 'failed')
|
|
15232
|
+
${projectClause}
|
|
15233
|
+
GROUP BY blocker.id
|
|
15234
|
+
ORDER BY blocking_count DESC, blocker.created_at DESC
|
|
15235
|
+
`).all(...params);
|
|
15236
|
+
return rows.map((row) => ({
|
|
15237
|
+
...rowToTask(row),
|
|
15238
|
+
blocking_count: row.blocking_count
|
|
15239
|
+
}));
|
|
15240
|
+
}
|
|
14881
15241
|
function logTime(input, db) {
|
|
14882
15242
|
const d = db || getDatabase();
|
|
14883
15243
|
const id = uuid();
|
|
@@ -14958,6 +15318,7 @@ __export(exports_tasks, {
|
|
|
14958
15318
|
removeDependency: () => removeDependency,
|
|
14959
15319
|
redistributeStaleTasks: () => redistributeStaleTasks,
|
|
14960
15320
|
notifyWatchers: () => notifyWatchers,
|
|
15321
|
+
notifyUpcomingDeadlines: () => notifyUpcomingDeadlines,
|
|
14961
15322
|
moveTask: () => moveTask,
|
|
14962
15323
|
logTime: () => logTime,
|
|
14963
15324
|
logCost: () => logCost,
|
|
@@ -14973,12 +15334,15 @@ __export(exports_tasks, {
|
|
|
14973
15334
|
getTaskGraph: () => getTaskGraph,
|
|
14974
15335
|
getTaskDependents: () => getTaskDependents,
|
|
14975
15336
|
getTaskDependencies: () => getTaskDependencies,
|
|
14976
|
-
getTask: () =>
|
|
15337
|
+
getTask: () => getTask,
|
|
14977
15338
|
getStatus: () => getStatus,
|
|
14978
15339
|
getStaleTasks: () => getStaleTasks,
|
|
14979
15340
|
getOverdueTasks: () => getOverdueTasks,
|
|
14980
15341
|
getNextTask: () => getNextTask,
|
|
15342
|
+
getBlockingTasks: () => getBlockingTasks,
|
|
14981
15343
|
getBlockingDeps: () => getBlockingDeps,
|
|
15344
|
+
getBlockedTasks: () => getBlockedTasks,
|
|
15345
|
+
getArchivedTasks: () => getArchivedTasks,
|
|
14982
15346
|
getActiveWork: () => getActiveWork,
|
|
14983
15347
|
failTask: () => failTask,
|
|
14984
15348
|
deleteTask: () => deleteTask,
|
|
@@ -14990,8 +15354,10 @@ __export(exports_tasks, {
|
|
|
14990
15354
|
claimOrSteal: () => claimOrSteal,
|
|
14991
15355
|
claimNextTask: () => claimNextTask,
|
|
14992
15356
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
15357
|
+
bulkDeleteTasks: () => bulkDeleteTasks,
|
|
14993
15358
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
14994
15359
|
archiveTasks: () => archiveTasks,
|
|
15360
|
+
archiveCompletedTasks: () => archiveCompletedTasks,
|
|
14995
15361
|
addDependency: () => addDependency
|
|
14996
15362
|
});
|
|
14997
15363
|
var init_tasks = __esm(() => {
|
|
@@ -15169,90 +15535,495 @@ var init_dispatch = __esm(() => {
|
|
|
15169
15535
|
init_dispatch_formatter();
|
|
15170
15536
|
});
|
|
15171
15537
|
|
|
15172
|
-
// src/db/
|
|
15173
|
-
var
|
|
15174
|
-
__export(
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15180
|
-
|
|
15181
|
-
findTasksByFile: () => findTasksByFile,
|
|
15182
|
-
detectFileConflicts: () => detectFileConflicts,
|
|
15183
|
-
bulkFindTasksByFiles: () => bulkFindTasksByFiles,
|
|
15184
|
-
bulkAddTaskFiles: () => bulkAddTaskFiles,
|
|
15185
|
-
addTaskFile: () => addTaskFile
|
|
15538
|
+
// src/db/comments.ts
|
|
15539
|
+
var exports_comments = {};
|
|
15540
|
+
__export(exports_comments, {
|
|
15541
|
+
updateComment: () => updateComment,
|
|
15542
|
+
logProgress: () => logProgress,
|
|
15543
|
+
listComments: () => listComments,
|
|
15544
|
+
getComment: () => getComment,
|
|
15545
|
+
deleteComment: () => deleteComment,
|
|
15546
|
+
addComment: () => addComment
|
|
15186
15547
|
});
|
|
15187
|
-
function
|
|
15548
|
+
function addComment(input, db) {
|
|
15188
15549
|
const d = db || getDatabase();
|
|
15550
|
+
if (!getTask(input.task_id, d)) {
|
|
15551
|
+
throw new TaskNotFoundError(input.task_id);
|
|
15552
|
+
}
|
|
15189
15553
|
const id = uuid();
|
|
15190
15554
|
const timestamp = now();
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15555
|
+
d.run(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
15556
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
15557
|
+
id,
|
|
15558
|
+
input.task_id,
|
|
15559
|
+
input.agent_id || null,
|
|
15560
|
+
input.session_id || null,
|
|
15561
|
+
input.content,
|
|
15562
|
+
input.type || "comment",
|
|
15563
|
+
input.progress_pct ?? null,
|
|
15564
|
+
timestamp
|
|
15565
|
+
]);
|
|
15566
|
+
return getComment(id, d);
|
|
15199
15567
|
}
|
|
15200
|
-
function
|
|
15201
|
-
|
|
15202
|
-
return d.query("SELECT * FROM task_files WHERE id = ?").get(id);
|
|
15568
|
+
function logProgress(taskId, message, pct, agentId, db) {
|
|
15569
|
+
return addComment({ task_id: taskId, content: message, type: "progress", progress_pct: pct, agent_id: agentId }, db);
|
|
15203
15570
|
}
|
|
15204
|
-
function
|
|
15571
|
+
function getComment(id, db) {
|
|
15205
15572
|
const d = db || getDatabase();
|
|
15206
|
-
return d.query("SELECT * FROM
|
|
15573
|
+
return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
15207
15574
|
}
|
|
15208
|
-
function
|
|
15575
|
+
function listComments(taskId, db) {
|
|
15209
15576
|
const d = db || getDatabase();
|
|
15210
|
-
return d.query("SELECT * FROM
|
|
15577
|
+
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
15211
15578
|
}
|
|
15212
|
-
function
|
|
15579
|
+
function updateComment(id, input, db) {
|
|
15213
15580
|
const d = db || getDatabase();
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15581
|
+
d.run("UPDATE task_comments SET content = ? WHERE id = ?", [input.content, id]);
|
|
15582
|
+
const comment = getComment(id, d);
|
|
15583
|
+
if (!comment) {
|
|
15584
|
+
throw new Error(`Comment not found: ${id}`);
|
|
15585
|
+
}
|
|
15586
|
+
return comment;
|
|
15218
15587
|
}
|
|
15219
|
-
function
|
|
15588
|
+
function deleteComment(id, db) {
|
|
15220
15589
|
const d = db || getDatabase();
|
|
15221
|
-
const result = d.run("DELETE FROM
|
|
15590
|
+
const result = d.run("DELETE FROM task_comments WHERE id = ?", [id]);
|
|
15222
15591
|
return result.changes > 0;
|
|
15223
15592
|
}
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
|
|
15234
|
-
|
|
15235
|
-
|
|
15236
|
-
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
15593
|
+
var init_comments = __esm(() => {
|
|
15594
|
+
init_types2();
|
|
15595
|
+
init_database();
|
|
15596
|
+
init_tasks();
|
|
15597
|
+
});
|
|
15598
|
+
|
|
15599
|
+
// src/lib/search.ts
|
|
15600
|
+
var exports_search = {};
|
|
15601
|
+
__export(exports_search, {
|
|
15602
|
+
searchTasks: () => searchTasks
|
|
15603
|
+
});
|
|
15604
|
+
function rowToTask2(row) {
|
|
15605
|
+
return {
|
|
15606
|
+
...row,
|
|
15607
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
15608
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
15609
|
+
status: row.status,
|
|
15610
|
+
priority: row.priority,
|
|
15611
|
+
requires_approval: Boolean(row.requires_approval)
|
|
15612
|
+
};
|
|
15241
15613
|
}
|
|
15242
|
-
function
|
|
15614
|
+
function hasFts(db) {
|
|
15615
|
+
try {
|
|
15616
|
+
const result = db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='tasks_fts'").get();
|
|
15617
|
+
return result !== null;
|
|
15618
|
+
} catch {
|
|
15619
|
+
return false;
|
|
15620
|
+
}
|
|
15621
|
+
}
|
|
15622
|
+
function escapeFtsQuery(q) {
|
|
15623
|
+
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
15624
|
+
}
|
|
15625
|
+
function searchTasks(options, projectId, taskListId, db) {
|
|
15626
|
+
const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
|
|
15243
15627
|
const d = db || getDatabase();
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
const
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15628
|
+
clearExpiredLocks(d);
|
|
15629
|
+
const params = [];
|
|
15630
|
+
let sql;
|
|
15631
|
+
const raw = opts.query?.trim() ?? "";
|
|
15632
|
+
const q = raw === "*" ? "" : raw;
|
|
15633
|
+
if (hasFts(d) && q) {
|
|
15634
|
+
const ftsQuery = escapeFtsQuery(q);
|
|
15635
|
+
sql = `SELECT t.* FROM tasks t
|
|
15636
|
+
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
15637
|
+
WHERE tasks_fts MATCH ?`;
|
|
15638
|
+
params.push(ftsQuery);
|
|
15639
|
+
} else if (q) {
|
|
15640
|
+
const pattern = `%${q}%`;
|
|
15641
|
+
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 ?))`;
|
|
15642
|
+
params.push(pattern, pattern, pattern);
|
|
15643
|
+
} else {
|
|
15644
|
+
sql = `SELECT * FROM tasks t WHERE 1=1`;
|
|
15645
|
+
}
|
|
15646
|
+
if (opts.project_id) {
|
|
15647
|
+
sql += " AND t.project_id = ?";
|
|
15648
|
+
params.push(opts.project_id);
|
|
15649
|
+
}
|
|
15650
|
+
if (opts.task_list_id) {
|
|
15651
|
+
sql += " AND t.task_list_id = ?";
|
|
15652
|
+
params.push(opts.task_list_id);
|
|
15653
|
+
}
|
|
15654
|
+
if (opts.status) {
|
|
15655
|
+
if (Array.isArray(opts.status)) {
|
|
15656
|
+
sql += ` AND t.status IN (${opts.status.map(() => "?").join(",")})`;
|
|
15657
|
+
params.push(...opts.status);
|
|
15658
|
+
} else {
|
|
15659
|
+
sql += " AND t.status = ?";
|
|
15660
|
+
params.push(opts.status);
|
|
15661
|
+
}
|
|
15662
|
+
}
|
|
15663
|
+
if (opts.priority) {
|
|
15664
|
+
if (Array.isArray(opts.priority)) {
|
|
15665
|
+
sql += ` AND t.priority IN (${opts.priority.map(() => "?").join(",")})`;
|
|
15666
|
+
params.push(...opts.priority);
|
|
15667
|
+
} else {
|
|
15668
|
+
sql += " AND t.priority = ?";
|
|
15669
|
+
params.push(opts.priority);
|
|
15670
|
+
}
|
|
15671
|
+
}
|
|
15672
|
+
if (opts.assigned_to) {
|
|
15673
|
+
sql += " AND t.assigned_to = ?";
|
|
15674
|
+
params.push(opts.assigned_to);
|
|
15675
|
+
}
|
|
15676
|
+
if (opts.agent_id) {
|
|
15677
|
+
sql += " AND t.agent_id = ?";
|
|
15678
|
+
params.push(opts.agent_id);
|
|
15679
|
+
}
|
|
15680
|
+
if (opts.created_after) {
|
|
15681
|
+
sql += " AND t.created_at > ?";
|
|
15682
|
+
params.push(opts.created_after);
|
|
15683
|
+
}
|
|
15684
|
+
if (opts.updated_after) {
|
|
15685
|
+
sql += " AND t.updated_at > ?";
|
|
15686
|
+
params.push(opts.updated_after);
|
|
15687
|
+
}
|
|
15688
|
+
if (opts.has_dependencies === true) {
|
|
15689
|
+
sql += " AND t.id IN (SELECT task_id FROM task_dependencies)";
|
|
15690
|
+
} else if (opts.has_dependencies === false) {
|
|
15691
|
+
sql += " AND t.id NOT IN (SELECT task_id FROM task_dependencies)";
|
|
15692
|
+
}
|
|
15693
|
+
if (opts.is_blocked === true) {
|
|
15694
|
+
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')";
|
|
15695
|
+
} else if (opts.is_blocked === false) {
|
|
15696
|
+
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')";
|
|
15697
|
+
}
|
|
15698
|
+
if (hasFts(d) && q) {
|
|
15699
|
+
sql += ` ORDER BY bm25(tasks_fts),
|
|
15700
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
15701
|
+
t.created_at DESC`;
|
|
15702
|
+
} else {
|
|
15703
|
+
sql += ` ORDER BY
|
|
15704
|
+
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
15705
|
+
t.created_at DESC`;
|
|
15706
|
+
}
|
|
15707
|
+
const rows = d.query(sql).all(...params);
|
|
15708
|
+
return rows.map(rowToTask2);
|
|
15709
|
+
}
|
|
15710
|
+
var init_search = __esm(() => {
|
|
15711
|
+
init_database();
|
|
15712
|
+
});
|
|
15713
|
+
|
|
15714
|
+
// src/db/handoffs.ts
|
|
15715
|
+
var exports_handoffs = {};
|
|
15716
|
+
__export(exports_handoffs, {
|
|
15717
|
+
listHandoffs: () => listHandoffs,
|
|
15718
|
+
getLatestHandoff: () => getLatestHandoff,
|
|
15719
|
+
createHandoff: () => createHandoff
|
|
15720
|
+
});
|
|
15721
|
+
function createHandoff(input, db) {
|
|
15722
|
+
const d = db || getDatabase();
|
|
15723
|
+
const id = uuid();
|
|
15724
|
+
const timestamp = now();
|
|
15725
|
+
d.run(`INSERT INTO handoffs (id, agent_id, project_id, summary, completed, in_progress, blockers, next_steps, created_at)
|
|
15726
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
15727
|
+
id,
|
|
15728
|
+
input.agent_id || null,
|
|
15729
|
+
input.project_id || null,
|
|
15730
|
+
input.summary,
|
|
15731
|
+
input.completed ? JSON.stringify(input.completed) : null,
|
|
15732
|
+
input.in_progress ? JSON.stringify(input.in_progress) : null,
|
|
15733
|
+
input.blockers ? JSON.stringify(input.blockers) : null,
|
|
15734
|
+
input.next_steps ? JSON.stringify(input.next_steps) : null,
|
|
15735
|
+
timestamp
|
|
15736
|
+
]);
|
|
15737
|
+
return {
|
|
15738
|
+
id,
|
|
15739
|
+
agent_id: input.agent_id || null,
|
|
15740
|
+
project_id: input.project_id || null,
|
|
15741
|
+
summary: input.summary,
|
|
15742
|
+
completed: input.completed || null,
|
|
15743
|
+
in_progress: input.in_progress || null,
|
|
15744
|
+
blockers: input.blockers || null,
|
|
15745
|
+
next_steps: input.next_steps || null,
|
|
15746
|
+
created_at: timestamp
|
|
15747
|
+
};
|
|
15748
|
+
}
|
|
15749
|
+
function rowToHandoff(row) {
|
|
15750
|
+
return {
|
|
15751
|
+
...row,
|
|
15752
|
+
completed: row.completed ? JSON.parse(row.completed) : null,
|
|
15753
|
+
in_progress: row.in_progress ? JSON.parse(row.in_progress) : null,
|
|
15754
|
+
blockers: row.blockers ? JSON.parse(row.blockers) : null,
|
|
15755
|
+
next_steps: row.next_steps ? JSON.parse(row.next_steps) : null
|
|
15756
|
+
};
|
|
15757
|
+
}
|
|
15758
|
+
function listHandoffs(projectId, limit = 10, db) {
|
|
15759
|
+
const d = db || getDatabase();
|
|
15760
|
+
if (projectId) {
|
|
15761
|
+
return d.query("SELECT * FROM handoffs WHERE project_id = ? ORDER BY rowid DESC LIMIT ?").all(projectId, limit).map(rowToHandoff);
|
|
15762
|
+
}
|
|
15763
|
+
return d.query("SELECT * FROM handoffs ORDER BY rowid DESC LIMIT ?").all(limit).map(rowToHandoff);
|
|
15764
|
+
}
|
|
15765
|
+
function getLatestHandoff(agentId, projectId, db) {
|
|
15766
|
+
const d = db || getDatabase();
|
|
15767
|
+
let query = "SELECT * FROM handoffs WHERE 1=1";
|
|
15768
|
+
const params = [];
|
|
15769
|
+
if (agentId) {
|
|
15770
|
+
query += " AND agent_id = ?";
|
|
15771
|
+
params.push(agentId);
|
|
15772
|
+
}
|
|
15773
|
+
if (projectId) {
|
|
15774
|
+
query += " AND project_id = ?";
|
|
15775
|
+
params.push(projectId);
|
|
15776
|
+
}
|
|
15777
|
+
query += " ORDER BY rowid DESC LIMIT 1";
|
|
15778
|
+
const row = d.query(query).get(...params);
|
|
15779
|
+
return row ? rowToHandoff(row) : null;
|
|
15780
|
+
}
|
|
15781
|
+
var init_handoffs = __esm(() => {
|
|
15782
|
+
init_database();
|
|
15783
|
+
});
|
|
15784
|
+
|
|
15785
|
+
// src/lib/auto-assign.ts
|
|
15786
|
+
var exports_auto_assign = {};
|
|
15787
|
+
__export(exports_auto_assign, {
|
|
15788
|
+
findBestAgent: () => findBestAgent,
|
|
15789
|
+
autoAssignTask: () => autoAssignTask
|
|
15790
|
+
});
|
|
15791
|
+
function findBestAgent(_task, db) {
|
|
15792
|
+
const d = db || getDatabase();
|
|
15793
|
+
const agents = listAgents(d).filter((a) => (a.role || "agent") === "agent");
|
|
15794
|
+
if (agents.length === 0)
|
|
15795
|
+
return null;
|
|
15796
|
+
const inProgressTasks = listTasks({ status: "in_progress" }, d);
|
|
15797
|
+
const idToName = new Map;
|
|
15798
|
+
const load = new Map;
|
|
15799
|
+
for (const a of agents) {
|
|
15800
|
+
idToName.set(a.id, a.name);
|
|
15801
|
+
load.set(a.id, 0);
|
|
15802
|
+
}
|
|
15803
|
+
for (const t of inProgressTasks) {
|
|
15804
|
+
const agentId = t.assigned_to || t.agent_id;
|
|
15805
|
+
if (agentId && load.has(agentId)) {
|
|
15806
|
+
load.set(agentId, (load.get(agentId) || 0) + 1);
|
|
15807
|
+
}
|
|
15808
|
+
}
|
|
15809
|
+
let bestAgent = agents[0].name;
|
|
15810
|
+
let bestLoad = load.get(agents[0].id) ?? 0;
|
|
15811
|
+
for (const a of agents) {
|
|
15812
|
+
const l = load.get(a.id) ?? 0;
|
|
15813
|
+
if (l < bestLoad) {
|
|
15814
|
+
bestAgent = a.name;
|
|
15815
|
+
bestLoad = l;
|
|
15816
|
+
}
|
|
15817
|
+
}
|
|
15818
|
+
return bestAgent;
|
|
15819
|
+
}
|
|
15820
|
+
function getAgentWorkloads(d) {
|
|
15821
|
+
const rows = d.query("SELECT assigned_to, COUNT(*) as count FROM tasks WHERE status = 'in_progress' AND assigned_to IS NOT NULL GROUP BY assigned_to").all();
|
|
15822
|
+
return new Map(rows.map((r) => [r.assigned_to, r.count]));
|
|
15823
|
+
}
|
|
15824
|
+
function buildPrompt(task, agents) {
|
|
15825
|
+
const agentList = agents.map((a) => `- ${a.name} (role: ${a.role}, caps: [${a.capabilities.join(", ")}], active_tasks: ${a.in_progress_tasks})`).join(`
|
|
15826
|
+
`);
|
|
15827
|
+
return `You are a task routing assistant. Given a task and available agents, choose the SINGLE best agent.
|
|
15828
|
+
|
|
15829
|
+
TASK:
|
|
15830
|
+
Title: ${task.title}
|
|
15831
|
+
Priority: ${task.priority}
|
|
15832
|
+
Tags: ${task.tags.join(", ") || "none"}
|
|
15833
|
+
Description: ${task.description?.slice(0, 300) || "none"}
|
|
15834
|
+
|
|
15835
|
+
AVAILABLE AGENTS:
|
|
15836
|
+
${agentList}
|
|
15837
|
+
|
|
15838
|
+
Rules:
|
|
15839
|
+
- Match task tags/content to agent capabilities
|
|
15840
|
+
- Prefer agents with fewer active tasks
|
|
15841
|
+
- Prefer agents whose role fits the task (lead for critical, developer for features, qa for testing)
|
|
15842
|
+
- If no clear match, pick the agent with fewest active tasks
|
|
15843
|
+
|
|
15844
|
+
Respond with ONLY a JSON object: {"agent_name": "<name>", "reason": "<one sentence>"}`;
|
|
15845
|
+
}
|
|
15846
|
+
async function callCerebras(prompt, apiKey) {
|
|
15847
|
+
try {
|
|
15848
|
+
const resp = await fetch(CEREBRAS_API_URL, {
|
|
15849
|
+
method: "POST",
|
|
15850
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
15851
|
+
body: JSON.stringify({
|
|
15852
|
+
model: CEREBRAS_MODEL,
|
|
15853
|
+
messages: [{ role: "user", content: prompt }],
|
|
15854
|
+
max_tokens: 150,
|
|
15855
|
+
temperature: 0
|
|
15856
|
+
}),
|
|
15857
|
+
signal: AbortSignal.timeout(1e4)
|
|
15858
|
+
});
|
|
15859
|
+
if (!resp.ok)
|
|
15860
|
+
return null;
|
|
15861
|
+
const data = await resp.json();
|
|
15862
|
+
const content = data?.choices?.[0]?.message?.content?.trim();
|
|
15863
|
+
if (!content)
|
|
15864
|
+
return null;
|
|
15865
|
+
const match = content.match(/\{[^}]+\}/s);
|
|
15866
|
+
if (!match)
|
|
15867
|
+
return null;
|
|
15868
|
+
return JSON.parse(match[0]);
|
|
15869
|
+
} catch {
|
|
15870
|
+
return null;
|
|
15871
|
+
}
|
|
15872
|
+
}
|
|
15873
|
+
async function autoAssignTask(taskId, db) {
|
|
15874
|
+
const d = db || getDatabase();
|
|
15875
|
+
const task = getTask(taskId, d);
|
|
15876
|
+
if (!task)
|
|
15877
|
+
throw new Error(`Task ${taskId} not found`);
|
|
15878
|
+
const agents = listAgents(d).filter((a) => a.status === "active");
|
|
15879
|
+
if (agents.length === 0) {
|
|
15880
|
+
return { task_id: taskId, assigned_to: null, agent_name: null, method: "no_agents" };
|
|
15881
|
+
}
|
|
15882
|
+
const workloads = getAgentWorkloads(d);
|
|
15883
|
+
const apiKey = process.env["CEREBRAS_API_KEY"];
|
|
15884
|
+
let selectedAgent = null;
|
|
15885
|
+
let method = "capability_match";
|
|
15886
|
+
let reason;
|
|
15887
|
+
if (apiKey) {
|
|
15888
|
+
const agentData = agents.map((a) => ({
|
|
15889
|
+
id: a.id,
|
|
15890
|
+
name: a.name,
|
|
15891
|
+
role: a.role || "agent",
|
|
15892
|
+
capabilities: a.capabilities || [],
|
|
15893
|
+
in_progress_tasks: workloads.get(a.id) ?? 0
|
|
15894
|
+
}));
|
|
15895
|
+
const result = await callCerebras(buildPrompt({
|
|
15896
|
+
title: task.title,
|
|
15897
|
+
description: task.description,
|
|
15898
|
+
priority: task.priority,
|
|
15899
|
+
tags: task.tags || []
|
|
15900
|
+
}, agentData), apiKey);
|
|
15901
|
+
if (result?.agent_name) {
|
|
15902
|
+
selectedAgent = agents.find((a) => a.name === result.agent_name) ?? null;
|
|
15903
|
+
if (selectedAgent) {
|
|
15904
|
+
method = "cerebras";
|
|
15905
|
+
reason = result.reason;
|
|
15906
|
+
}
|
|
15907
|
+
}
|
|
15908
|
+
}
|
|
15909
|
+
if (!selectedAgent) {
|
|
15910
|
+
const taskTags = task.tags || [];
|
|
15911
|
+
const capable = getCapableAgents(taskTags, { min_score: 0, limit: 10 }, d);
|
|
15912
|
+
if (capable.length > 0) {
|
|
15913
|
+
const sorted = capable.sort((a, b) => {
|
|
15914
|
+
if (b.score !== a.score)
|
|
15915
|
+
return b.score - a.score;
|
|
15916
|
+
return (workloads.get(a.agent.id) ?? 0) - (workloads.get(b.agent.id) ?? 0);
|
|
15917
|
+
});
|
|
15918
|
+
selectedAgent = sorted[0].agent;
|
|
15919
|
+
reason = `Capability match (score: ${sorted[0].score.toFixed(2)})`;
|
|
15920
|
+
} else {
|
|
15921
|
+
selectedAgent = agents.slice().sort((a, b) => (workloads.get(a.id) ?? 0) - (workloads.get(b.id) ?? 0))[0];
|
|
15922
|
+
reason = `Least busy agent (${workloads.get(selectedAgent.id) ?? 0} active tasks)`;
|
|
15923
|
+
}
|
|
15924
|
+
}
|
|
15925
|
+
if (selectedAgent) {
|
|
15926
|
+
updateTask(taskId, { assigned_to: selectedAgent.id, version: task.version }, d);
|
|
15927
|
+
}
|
|
15928
|
+
return {
|
|
15929
|
+
task_id: taskId,
|
|
15930
|
+
assigned_to: selectedAgent?.id ?? null,
|
|
15931
|
+
agent_name: selectedAgent?.name ?? null,
|
|
15932
|
+
method,
|
|
15933
|
+
reason
|
|
15934
|
+
};
|
|
15935
|
+
}
|
|
15936
|
+
var CEREBRAS_API_URL = "https://api.cerebras.ai/v1/chat/completions", CEREBRAS_MODEL = "llama-3.3-70b";
|
|
15937
|
+
var init_auto_assign = __esm(() => {
|
|
15938
|
+
init_database();
|
|
15939
|
+
init_tasks();
|
|
15940
|
+
init_agents();
|
|
15941
|
+
});
|
|
15942
|
+
|
|
15943
|
+
// src/db/task-files.ts
|
|
15944
|
+
var exports_task_files = {};
|
|
15945
|
+
__export(exports_task_files, {
|
|
15946
|
+
updateTaskFileStatus: () => updateTaskFileStatus,
|
|
15947
|
+
removeTaskFile: () => removeTaskFile,
|
|
15948
|
+
listTaskFiles: () => listTaskFiles,
|
|
15949
|
+
listActiveFiles: () => listActiveFiles,
|
|
15950
|
+
getTaskFile: () => getTaskFile,
|
|
15951
|
+
getFileHeatMap: () => getFileHeatMap,
|
|
15952
|
+
findTasksByFile: () => findTasksByFile,
|
|
15953
|
+
detectFileConflicts: () => detectFileConflicts,
|
|
15954
|
+
bulkFindTasksByFiles: () => bulkFindTasksByFiles,
|
|
15955
|
+
bulkAddTaskFiles: () => bulkAddTaskFiles,
|
|
15956
|
+
addTaskFile: () => addTaskFile
|
|
15957
|
+
});
|
|
15958
|
+
function addTaskFile(input, db) {
|
|
15959
|
+
const d = db || getDatabase();
|
|
15960
|
+
const id = uuid();
|
|
15961
|
+
const timestamp = now();
|
|
15962
|
+
const existing = d.query("SELECT id FROM task_files WHERE task_id = ? AND path = ?").get(input.task_id, input.path);
|
|
15963
|
+
if (existing) {
|
|
15964
|
+
d.run("UPDATE task_files SET status = ?, agent_id = ?, note = ?, updated_at = ? WHERE id = ?", [input.status || "active", input.agent_id || null, input.note || null, timestamp, existing.id]);
|
|
15965
|
+
return getTaskFile(existing.id, d);
|
|
15966
|
+
}
|
|
15967
|
+
d.run(`INSERT INTO task_files (id, task_id, path, status, agent_id, note, created_at, updated_at)
|
|
15968
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.task_id, input.path, input.status || "active", input.agent_id || null, input.note || null, timestamp, timestamp]);
|
|
15969
|
+
return getTaskFile(id, d);
|
|
15970
|
+
}
|
|
15971
|
+
function getTaskFile(id, db) {
|
|
15972
|
+
const d = db || getDatabase();
|
|
15973
|
+
return d.query("SELECT * FROM task_files WHERE id = ?").get(id);
|
|
15974
|
+
}
|
|
15975
|
+
function listTaskFiles(taskId, db) {
|
|
15976
|
+
const d = db || getDatabase();
|
|
15977
|
+
return d.query("SELECT * FROM task_files WHERE task_id = ? ORDER BY path").all(taskId);
|
|
15978
|
+
}
|
|
15979
|
+
function findTasksByFile(path, db) {
|
|
15980
|
+
const d = db || getDatabase();
|
|
15981
|
+
return d.query("SELECT * FROM task_files WHERE path = ? AND status != 'removed' ORDER BY updated_at DESC").all(path);
|
|
15982
|
+
}
|
|
15983
|
+
function updateTaskFileStatus(taskId, path, status, agentId, db) {
|
|
15984
|
+
const d = db || getDatabase();
|
|
15985
|
+
const timestamp = now();
|
|
15986
|
+
d.run("UPDATE task_files SET status = ?, agent_id = COALESCE(?, agent_id), updated_at = ? WHERE task_id = ? AND path = ?", [status, agentId || null, timestamp, taskId, path]);
|
|
15987
|
+
const row = d.query("SELECT * FROM task_files WHERE task_id = ? AND path = ?").get(taskId, path);
|
|
15988
|
+
return row;
|
|
15989
|
+
}
|
|
15990
|
+
function removeTaskFile(taskId, path, db) {
|
|
15991
|
+
const d = db || getDatabase();
|
|
15992
|
+
const result = d.run("DELETE FROM task_files WHERE task_id = ? AND path = ?", [taskId, path]);
|
|
15993
|
+
return result.changes > 0;
|
|
15994
|
+
}
|
|
15995
|
+
function detectFileConflicts(taskId, paths, db) {
|
|
15996
|
+
const d = db || getDatabase();
|
|
15997
|
+
if (paths.length === 0)
|
|
15998
|
+
return [];
|
|
15999
|
+
const placeholders = paths.map(() => "?").join(", ");
|
|
16000
|
+
const rows = d.query(`
|
|
16001
|
+
SELECT tf.path, tf.agent_id AS conflicting_agent_id, t.id AS conflicting_task_id,
|
|
16002
|
+
t.title AS conflicting_task_title, t.status AS conflicting_task_status
|
|
16003
|
+
FROM task_files tf
|
|
16004
|
+
JOIN tasks t ON tf.task_id = t.id
|
|
16005
|
+
WHERE tf.path IN (${placeholders})
|
|
16006
|
+
AND tf.task_id != ?
|
|
16007
|
+
AND tf.status != 'removed'
|
|
16008
|
+
AND t.status = 'in_progress'
|
|
16009
|
+
ORDER BY tf.updated_at DESC
|
|
16010
|
+
`).all(...paths, taskId);
|
|
16011
|
+
return rows;
|
|
16012
|
+
}
|
|
16013
|
+
function bulkFindTasksByFiles(paths, db) {
|
|
16014
|
+
const d = db || getDatabase();
|
|
16015
|
+
if (paths.length === 0)
|
|
16016
|
+
return [];
|
|
16017
|
+
const placeholders = paths.map(() => "?").join(", ");
|
|
16018
|
+
const rows = d.query(`SELECT tf.*, t.status AS task_status FROM task_files tf
|
|
16019
|
+
JOIN tasks t ON tf.task_id = t.id
|
|
16020
|
+
WHERE tf.path IN (${placeholders}) AND tf.status != 'removed'
|
|
16021
|
+
ORDER BY tf.updated_at DESC`).all(...paths);
|
|
16022
|
+
const byPath = new Map;
|
|
16023
|
+
for (const path of paths)
|
|
16024
|
+
byPath.set(path, []);
|
|
16025
|
+
for (const row of rows) {
|
|
16026
|
+
byPath.get(row.path)?.push(row);
|
|
15256
16027
|
}
|
|
15257
16028
|
return paths.map((path) => {
|
|
15258
16029
|
const tasks = byPath.get(path) ?? [];
|
|
@@ -15461,6 +16232,645 @@ var init_task_relationships = __esm(() => {
|
|
|
15461
16232
|
];
|
|
15462
16233
|
});
|
|
15463
16234
|
|
|
16235
|
+
// src/db/task-commits.ts
|
|
16236
|
+
var exports_task_commits = {};
|
|
16237
|
+
__export(exports_task_commits, {
|
|
16238
|
+
unlinkTaskCommit: () => unlinkTaskCommit,
|
|
16239
|
+
linkTaskToCommit: () => linkTaskToCommit,
|
|
16240
|
+
getTaskCommits: () => getTaskCommits,
|
|
16241
|
+
findTaskByCommit: () => findTaskByCommit
|
|
16242
|
+
});
|
|
16243
|
+
function rowToCommit(row) {
|
|
16244
|
+
return {
|
|
16245
|
+
...row,
|
|
16246
|
+
files_changed: row.files_changed ? JSON.parse(row.files_changed) : null
|
|
16247
|
+
};
|
|
16248
|
+
}
|
|
16249
|
+
function linkTaskToCommit(input, db) {
|
|
16250
|
+
const d = db || getDatabase();
|
|
16251
|
+
const existing = d.query("SELECT * FROM task_commits WHERE task_id = ? AND sha = ?").get(input.task_id, input.sha);
|
|
16252
|
+
if (existing) {
|
|
16253
|
+
d.run("UPDATE task_commits SET message = COALESCE(?, message), author = COALESCE(?, author), files_changed = COALESCE(?, files_changed), committed_at = COALESCE(?, committed_at) WHERE id = ?", [input.message ?? null, input.author ?? null, input.files_changed ? JSON.stringify(input.files_changed) : null, input.committed_at ?? null, existing.id]);
|
|
16254
|
+
return rowToCommit(d.query("SELECT * FROM task_commits WHERE id = ?").get(existing.id));
|
|
16255
|
+
}
|
|
16256
|
+
const id = uuid();
|
|
16257
|
+
d.run("INSERT INTO task_commits (id, task_id, sha, message, author, files_changed, committed_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, input.task_id, input.sha, input.message ?? null, input.author ?? null, input.files_changed ? JSON.stringify(input.files_changed) : null, input.committed_at ?? null, now()]);
|
|
16258
|
+
return rowToCommit(d.query("SELECT * FROM task_commits WHERE id = ?").get(id));
|
|
16259
|
+
}
|
|
16260
|
+
function getTaskCommits(taskId, db) {
|
|
16261
|
+
const d = db || getDatabase();
|
|
16262
|
+
return d.query("SELECT * FROM task_commits WHERE task_id = ? ORDER BY committed_at DESC, created_at DESC").all(taskId).map(rowToCommit);
|
|
16263
|
+
}
|
|
16264
|
+
function findTaskByCommit(sha, db) {
|
|
16265
|
+
const d = db || getDatabase();
|
|
16266
|
+
const row = d.query("SELECT * FROM task_commits WHERE sha = ? OR sha LIKE ? LIMIT 1").get(sha, `${sha}%`);
|
|
16267
|
+
if (!row)
|
|
16268
|
+
return null;
|
|
16269
|
+
return { task_id: row.task_id, commit: rowToCommit(row) };
|
|
16270
|
+
}
|
|
16271
|
+
function unlinkTaskCommit(taskId, sha, db) {
|
|
16272
|
+
const d = db || getDatabase();
|
|
16273
|
+
return d.run("DELETE FROM task_commits WHERE task_id = ? AND (sha = ? OR sha LIKE ?)", [taskId, sha, `${sha}%`]).changes > 0;
|
|
16274
|
+
}
|
|
16275
|
+
var init_task_commits = __esm(() => {
|
|
16276
|
+
init_database();
|
|
16277
|
+
});
|
|
16278
|
+
|
|
16279
|
+
// src/db/file-locks.ts
|
|
16280
|
+
var exports_file_locks = {};
|
|
16281
|
+
__export(exports_file_locks, {
|
|
16282
|
+
unlockFile: () => unlockFile,
|
|
16283
|
+
lockFile: () => lockFile,
|
|
16284
|
+
listFileLocks: () => listFileLocks,
|
|
16285
|
+
forceUnlockFile: () => forceUnlockFile,
|
|
16286
|
+
cleanExpiredFileLocks: () => cleanExpiredFileLocks,
|
|
16287
|
+
checkFileLock: () => checkFileLock,
|
|
16288
|
+
FILE_LOCK_DEFAULT_TTL_SECONDS: () => FILE_LOCK_DEFAULT_TTL_SECONDS
|
|
16289
|
+
});
|
|
16290
|
+
function expiresAt(ttlSeconds) {
|
|
16291
|
+
return new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
16292
|
+
}
|
|
16293
|
+
function cleanExpiredFileLocks(db) {
|
|
16294
|
+
const d = db || getDatabase();
|
|
16295
|
+
const result = d.run("DELETE FROM file_locks WHERE expires_at <= ?", [now()]);
|
|
16296
|
+
return result.changes;
|
|
16297
|
+
}
|
|
16298
|
+
function lockFile(input, db) {
|
|
16299
|
+
const d = db || getDatabase();
|
|
16300
|
+
const ttl = input.ttl_seconds ?? FILE_LOCK_DEFAULT_TTL_SECONDS;
|
|
16301
|
+
const expiry = expiresAt(ttl);
|
|
16302
|
+
const timestamp = now();
|
|
16303
|
+
cleanExpiredFileLocks(d);
|
|
16304
|
+
const existing = d.query("SELECT * FROM file_locks WHERE path = ?").get(input.path);
|
|
16305
|
+
if (existing) {
|
|
16306
|
+
if (existing.agent_id === input.agent_id) {
|
|
16307
|
+
d.run("UPDATE file_locks SET expires_at = ?, task_id = COALESCE(?, task_id) WHERE id = ?", [expiry, input.task_id ?? null, existing.id]);
|
|
16308
|
+
return d.query("SELECT * FROM file_locks WHERE id = ?").get(existing.id);
|
|
16309
|
+
}
|
|
16310
|
+
throw new LockError(input.path, existing.agent_id);
|
|
16311
|
+
}
|
|
16312
|
+
const id = uuid();
|
|
16313
|
+
d.run("INSERT INTO file_locks (id, path, agent_id, task_id, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, input.path, input.agent_id, input.task_id ?? null, expiry, timestamp]);
|
|
16314
|
+
return d.query("SELECT * FROM file_locks WHERE id = ?").get(id);
|
|
16315
|
+
}
|
|
16316
|
+
function unlockFile(path, agentId, db) {
|
|
16317
|
+
const d = db || getDatabase();
|
|
16318
|
+
cleanExpiredFileLocks(d);
|
|
16319
|
+
const result = d.run("DELETE FROM file_locks WHERE path = ? AND agent_id = ?", [path, agentId]);
|
|
16320
|
+
return result.changes > 0;
|
|
16321
|
+
}
|
|
16322
|
+
function checkFileLock(path, db) {
|
|
16323
|
+
const d = db || getDatabase();
|
|
16324
|
+
cleanExpiredFileLocks(d);
|
|
16325
|
+
return d.query("SELECT * FROM file_locks WHERE path = ?").get(path);
|
|
16326
|
+
}
|
|
16327
|
+
function listFileLocks(agentId, db) {
|
|
16328
|
+
const d = db || getDatabase();
|
|
16329
|
+
cleanExpiredFileLocks(d);
|
|
16330
|
+
if (agentId) {
|
|
16331
|
+
return d.query("SELECT * FROM file_locks WHERE agent_id = ? ORDER BY created_at DESC").all(agentId);
|
|
16332
|
+
}
|
|
16333
|
+
return d.query("SELECT * FROM file_locks ORDER BY created_at DESC").all();
|
|
16334
|
+
}
|
|
16335
|
+
function forceUnlockFile(path, db) {
|
|
16336
|
+
const d = db || getDatabase();
|
|
16337
|
+
const result = d.run("DELETE FROM file_locks WHERE path = ?", [path]);
|
|
16338
|
+
return result.changes > 0;
|
|
16339
|
+
}
|
|
16340
|
+
var FILE_LOCK_DEFAULT_TTL_SECONDS;
|
|
16341
|
+
var init_file_locks = __esm(() => {
|
|
16342
|
+
init_database();
|
|
16343
|
+
init_types2();
|
|
16344
|
+
FILE_LOCK_DEFAULT_TTL_SECONDS = 30 * 60;
|
|
16345
|
+
});
|
|
16346
|
+
|
|
16347
|
+
// src/db/kg.ts
|
|
16348
|
+
var exports_kg = {};
|
|
16349
|
+
__export(exports_kg, {
|
|
16350
|
+
syncKgEdges: () => syncKgEdges,
|
|
16351
|
+
removeKgEdges: () => removeKgEdges,
|
|
16352
|
+
getRelated: () => getRelated,
|
|
16353
|
+
getImpactAnalysis: () => getImpactAnalysis,
|
|
16354
|
+
getCriticalPath: () => getCriticalPath,
|
|
16355
|
+
findPath: () => findPath,
|
|
16356
|
+
addKgEdge: () => addKgEdge
|
|
16357
|
+
});
|
|
16358
|
+
function rowToEdge(row) {
|
|
16359
|
+
return {
|
|
16360
|
+
...row,
|
|
16361
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
16362
|
+
};
|
|
16363
|
+
}
|
|
16364
|
+
function syncKgEdges(db) {
|
|
16365
|
+
const d = db || getDatabase();
|
|
16366
|
+
let synced = 0;
|
|
16367
|
+
const tx = d.transaction(() => {
|
|
16368
|
+
const deps = d.query("SELECT task_id, depends_on FROM task_dependencies").all();
|
|
16369
|
+
for (const dep of deps) {
|
|
16370
|
+
synced += upsertEdge(d, dep.task_id, "task", dep.depends_on, "task", "depends_on");
|
|
16371
|
+
}
|
|
16372
|
+
const assignments = d.query("SELECT id, assigned_to FROM tasks WHERE assigned_to IS NOT NULL").all();
|
|
16373
|
+
for (const a of assignments) {
|
|
16374
|
+
synced += upsertEdge(d, a.id, "task", a.assigned_to, "agent", "assigned_to");
|
|
16375
|
+
}
|
|
16376
|
+
const agents = d.query("SELECT id, reports_to FROM agents WHERE reports_to IS NOT NULL").all();
|
|
16377
|
+
for (const a of agents) {
|
|
16378
|
+
synced += upsertEdge(d, a.id, "agent", a.reports_to, "agent", "reports_to");
|
|
16379
|
+
}
|
|
16380
|
+
const files = d.query("SELECT task_id, path FROM task_files WHERE status != 'removed'").all();
|
|
16381
|
+
for (const f of files) {
|
|
16382
|
+
synced += upsertEdge(d, f.task_id, "task", f.path, "file", "references_file");
|
|
16383
|
+
}
|
|
16384
|
+
const taskProjects = d.query("SELECT id, project_id FROM tasks WHERE project_id IS NOT NULL").all();
|
|
16385
|
+
for (const tp of taskProjects) {
|
|
16386
|
+
synced += upsertEdge(d, tp.id, "task", tp.project_id, "project", "in_project");
|
|
16387
|
+
}
|
|
16388
|
+
const taskPlans = d.query("SELECT id, plan_id FROM tasks WHERE plan_id IS NOT NULL").all();
|
|
16389
|
+
for (const tp of taskPlans) {
|
|
16390
|
+
synced += upsertEdge(d, tp.id, "task", tp.plan_id, "plan", "in_plan");
|
|
16391
|
+
}
|
|
16392
|
+
try {
|
|
16393
|
+
const rels = d.query("SELECT source_task_id, target_task_id, relationship_type FROM task_relationships").all();
|
|
16394
|
+
for (const r of rels) {
|
|
16395
|
+
synced += upsertEdge(d, r.source_task_id, "task", r.target_task_id, "task", r.relationship_type);
|
|
16396
|
+
}
|
|
16397
|
+
} catch {}
|
|
16398
|
+
});
|
|
16399
|
+
tx();
|
|
16400
|
+
return { synced };
|
|
16401
|
+
}
|
|
16402
|
+
function upsertEdge(d, sourceId, sourceType, targetId, targetType, relationType, weight = 1) {
|
|
16403
|
+
try {
|
|
16404
|
+
d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
|
|
16405
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, '{}', ?)`, [uuid(), sourceId, sourceType, targetId, targetType, relationType, weight, now()]);
|
|
16406
|
+
return 1;
|
|
16407
|
+
} catch {
|
|
16408
|
+
return 0;
|
|
16409
|
+
}
|
|
16410
|
+
}
|
|
16411
|
+
function getRelated(entityId, opts, db) {
|
|
16412
|
+
const d = db || getDatabase();
|
|
16413
|
+
const direction = opts?.direction || "both";
|
|
16414
|
+
const conditions = [];
|
|
16415
|
+
const params = [];
|
|
16416
|
+
if (direction === "outgoing" || direction === "both") {
|
|
16417
|
+
conditions.push("source_id = ?");
|
|
16418
|
+
params.push(entityId);
|
|
16419
|
+
}
|
|
16420
|
+
if (direction === "incoming" || direction === "both") {
|
|
16421
|
+
conditions.push("target_id = ?");
|
|
16422
|
+
params.push(entityId);
|
|
16423
|
+
}
|
|
16424
|
+
let sql = `SELECT * FROM kg_edges WHERE (${conditions.join(" OR ")})`;
|
|
16425
|
+
if (opts?.relation_type) {
|
|
16426
|
+
sql += " AND relation_type = ?";
|
|
16427
|
+
params.push(opts.relation_type);
|
|
16428
|
+
}
|
|
16429
|
+
if (opts?.entity_type) {
|
|
16430
|
+
sql += " AND (source_type = ? OR target_type = ?)";
|
|
16431
|
+
params.push(opts.entity_type, opts.entity_type);
|
|
16432
|
+
}
|
|
16433
|
+
sql += " ORDER BY weight DESC, created_at DESC";
|
|
16434
|
+
if (opts?.limit) {
|
|
16435
|
+
sql += " LIMIT ?";
|
|
16436
|
+
params.push(opts.limit);
|
|
16437
|
+
}
|
|
16438
|
+
return d.query(sql).all(...params).map(rowToEdge);
|
|
16439
|
+
}
|
|
16440
|
+
function findPath(sourceId, targetId, opts, db) {
|
|
16441
|
+
const d = db || getDatabase();
|
|
16442
|
+
const maxDepth = opts?.max_depth || 5;
|
|
16443
|
+
const visited = new Set;
|
|
16444
|
+
const queue = [{ id: sourceId, path: [] }];
|
|
16445
|
+
const results = [];
|
|
16446
|
+
visited.add(sourceId);
|
|
16447
|
+
while (queue.length > 0) {
|
|
16448
|
+
const current = queue.shift();
|
|
16449
|
+
if (current.path.length >= maxDepth)
|
|
16450
|
+
continue;
|
|
16451
|
+
let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
|
|
16452
|
+
const params = [current.id];
|
|
16453
|
+
if (opts?.relation_types && opts.relation_types.length > 0) {
|
|
16454
|
+
const placeholders = opts.relation_types.map(() => "?").join(",");
|
|
16455
|
+
sql += ` AND relation_type IN (${placeholders})`;
|
|
16456
|
+
params.push(...opts.relation_types);
|
|
16457
|
+
}
|
|
16458
|
+
const edges = d.query(sql).all(...params).map(rowToEdge);
|
|
16459
|
+
for (const edge of edges) {
|
|
16460
|
+
const nextId = edge.target_id;
|
|
16461
|
+
const newPath = [...current.path, edge];
|
|
16462
|
+
if (nextId === targetId) {
|
|
16463
|
+
results.push(newPath);
|
|
16464
|
+
if (results.length >= 3)
|
|
16465
|
+
return results;
|
|
16466
|
+
continue;
|
|
16467
|
+
}
|
|
16468
|
+
if (!visited.has(nextId)) {
|
|
16469
|
+
visited.add(nextId);
|
|
16470
|
+
queue.push({ id: nextId, path: newPath });
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16473
|
+
}
|
|
16474
|
+
return results;
|
|
16475
|
+
}
|
|
16476
|
+
function getImpactAnalysis(entityId, opts, db) {
|
|
16477
|
+
const d = db || getDatabase();
|
|
16478
|
+
const maxDepth = opts?.max_depth || 3;
|
|
16479
|
+
const results = [];
|
|
16480
|
+
const visited = new Set;
|
|
16481
|
+
visited.add(entityId);
|
|
16482
|
+
const queue = [{ id: entityId, depth: 0 }];
|
|
16483
|
+
while (queue.length > 0) {
|
|
16484
|
+
const current = queue.shift();
|
|
16485
|
+
if (current.depth >= maxDepth)
|
|
16486
|
+
continue;
|
|
16487
|
+
let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
|
|
16488
|
+
const params = [current.id];
|
|
16489
|
+
if (opts?.relation_types && opts.relation_types.length > 0) {
|
|
16490
|
+
const placeholders = opts.relation_types.map(() => "?").join(",");
|
|
16491
|
+
sql += ` AND relation_type IN (${placeholders})`;
|
|
16492
|
+
params.push(...opts.relation_types);
|
|
16493
|
+
}
|
|
16494
|
+
const edges = d.query(sql).all(...params).map(rowToEdge);
|
|
16495
|
+
for (const edge of edges) {
|
|
16496
|
+
if (!visited.has(edge.target_id)) {
|
|
16497
|
+
visited.add(edge.target_id);
|
|
16498
|
+
results.push({
|
|
16499
|
+
entity_id: edge.target_id,
|
|
16500
|
+
entity_type: edge.target_type,
|
|
16501
|
+
depth: current.depth + 1,
|
|
16502
|
+
relation: edge.relation_type
|
|
16503
|
+
});
|
|
16504
|
+
queue.push({ id: edge.target_id, depth: current.depth + 1 });
|
|
16505
|
+
}
|
|
16506
|
+
}
|
|
16507
|
+
}
|
|
16508
|
+
return results;
|
|
16509
|
+
}
|
|
16510
|
+
function getCriticalPath(opts, db) {
|
|
16511
|
+
const d = db || getDatabase();
|
|
16512
|
+
let sql = `SELECT source_id, target_id FROM kg_edges WHERE relation_type = 'depends_on'`;
|
|
16513
|
+
const params = [];
|
|
16514
|
+
if (opts?.project_id) {
|
|
16515
|
+
sql += ` AND source_id IN (SELECT id FROM tasks WHERE project_id = ?)`;
|
|
16516
|
+
params.push(opts.project_id);
|
|
16517
|
+
}
|
|
16518
|
+
const edges = d.query(sql).all(...params);
|
|
16519
|
+
const blocks = new Map;
|
|
16520
|
+
for (const e of edges) {
|
|
16521
|
+
if (!blocks.has(e.target_id))
|
|
16522
|
+
blocks.set(e.target_id, new Set);
|
|
16523
|
+
blocks.get(e.target_id).add(e.source_id);
|
|
16524
|
+
}
|
|
16525
|
+
const results = [];
|
|
16526
|
+
for (const [taskId] of blocks) {
|
|
16527
|
+
const visited = new Set;
|
|
16528
|
+
const q = [taskId];
|
|
16529
|
+
let maxDepth = 0;
|
|
16530
|
+
let depth = 0;
|
|
16531
|
+
let levelSize = q.length;
|
|
16532
|
+
while (q.length > 0) {
|
|
16533
|
+
const node = q.shift();
|
|
16534
|
+
levelSize--;
|
|
16535
|
+
const downstream = blocks.get(node);
|
|
16536
|
+
if (downstream) {
|
|
16537
|
+
for (const d2 of downstream) {
|
|
16538
|
+
if (!visited.has(d2)) {
|
|
16539
|
+
visited.add(d2);
|
|
16540
|
+
q.push(d2);
|
|
16541
|
+
}
|
|
16542
|
+
}
|
|
16543
|
+
}
|
|
16544
|
+
if (levelSize === 0) {
|
|
16545
|
+
depth++;
|
|
16546
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
16547
|
+
levelSize = q.length;
|
|
16548
|
+
}
|
|
16549
|
+
}
|
|
16550
|
+
if (visited.size > 0) {
|
|
16551
|
+
results.push({ task_id: taskId, blocking_count: visited.size, depth: maxDepth });
|
|
16552
|
+
}
|
|
16553
|
+
}
|
|
16554
|
+
results.sort((a, b) => b.blocking_count - a.blocking_count);
|
|
16555
|
+
return results.slice(0, opts?.limit || 20);
|
|
16556
|
+
}
|
|
16557
|
+
function addKgEdge(sourceId, sourceType, targetId, targetType, relationType, weight = 1, metadata, db) {
|
|
16558
|
+
const d = db || getDatabase();
|
|
16559
|
+
const id = uuid();
|
|
16560
|
+
const timestamp = now();
|
|
16561
|
+
d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
|
|
16562
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, sourceId, sourceType, targetId, targetType, relationType, weight, JSON.stringify(metadata || {}), timestamp]);
|
|
16563
|
+
return { id, source_id: sourceId, source_type: sourceType, target_id: targetId, target_type: targetType, relation_type: relationType, weight, metadata: metadata || {}, created_at: timestamp };
|
|
16564
|
+
}
|
|
16565
|
+
function removeKgEdges(sourceId, targetId, relationType, db) {
|
|
16566
|
+
const d = db || getDatabase();
|
|
16567
|
+
if (relationType) {
|
|
16568
|
+
return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ? AND relation_type = ?", [sourceId, targetId, relationType]).changes;
|
|
16569
|
+
}
|
|
16570
|
+
return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ?", [sourceId, targetId]).changes;
|
|
16571
|
+
}
|
|
16572
|
+
var init_kg = __esm(() => {
|
|
16573
|
+
init_database();
|
|
16574
|
+
});
|
|
16575
|
+
|
|
16576
|
+
// src/db/project-agent-roles.ts
|
|
16577
|
+
var exports_project_agent_roles = {};
|
|
16578
|
+
__export(exports_project_agent_roles, {
|
|
16579
|
+
setProjectAgentRole: () => setProjectAgentRole,
|
|
16580
|
+
removeProjectAgentRole: () => removeProjectAgentRole,
|
|
16581
|
+
listProjectAgentRoles: () => listProjectAgentRoles,
|
|
16582
|
+
getProjectOrgChart: () => getProjectOrgChart,
|
|
16583
|
+
getAgentProjectRoles: () => getAgentProjectRoles
|
|
16584
|
+
});
|
|
16585
|
+
function rowToRole(row) {
|
|
16586
|
+
return { ...row, is_lead: row.is_lead === 1 };
|
|
16587
|
+
}
|
|
16588
|
+
function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
|
|
16589
|
+
const d = db || getDatabase();
|
|
16590
|
+
const existing = d.query("SELECT * FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?").get(projectId, agentId, role);
|
|
16591
|
+
if (existing) {
|
|
16592
|
+
d.run("UPDATE project_agent_roles SET is_lead = ? WHERE id = ?", [isLead ? 1 : 0, existing.id]);
|
|
16593
|
+
return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
|
|
16594
|
+
}
|
|
16595
|
+
const id = uuid();
|
|
16596
|
+
d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0, now()]);
|
|
16597
|
+
return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
|
|
16598
|
+
}
|
|
16599
|
+
function removeProjectAgentRole(projectId, agentId, role, db) {
|
|
16600
|
+
const d = db || getDatabase();
|
|
16601
|
+
if (role) {
|
|
16602
|
+
return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?", [projectId, agentId, role]).changes;
|
|
16603
|
+
}
|
|
16604
|
+
return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ?", [projectId, agentId]).changes;
|
|
16605
|
+
}
|
|
16606
|
+
function listProjectAgentRoles(projectId, db) {
|
|
16607
|
+
const d = db || getDatabase();
|
|
16608
|
+
return d.query("SELECT * FROM project_agent_roles WHERE project_id = ? ORDER BY role, created_at").all(projectId).map(rowToRole);
|
|
16609
|
+
}
|
|
16610
|
+
function getAgentProjectRoles(agentId, db) {
|
|
16611
|
+
const d = db || getDatabase();
|
|
16612
|
+
return d.query("SELECT * FROM project_agent_roles WHERE agent_id = ? ORDER BY project_id, role").all(agentId).map(rowToRole);
|
|
16613
|
+
}
|
|
16614
|
+
function getProjectOrgChart(projectId, opts, db) {
|
|
16615
|
+
const d = db || getDatabase();
|
|
16616
|
+
const globalTree = getOrgChart(d);
|
|
16617
|
+
const projectRoles = listProjectAgentRoles(projectId, d);
|
|
16618
|
+
const rolesByAgent = new Map;
|
|
16619
|
+
for (const pr of projectRoles) {
|
|
16620
|
+
if (!rolesByAgent.has(pr.agent_id))
|
|
16621
|
+
rolesByAgent.set(pr.agent_id, { roles: [], isLead: false });
|
|
16622
|
+
const entry = rolesByAgent.get(pr.agent_id);
|
|
16623
|
+
entry.roles.push(pr.role);
|
|
16624
|
+
if (pr.is_lead)
|
|
16625
|
+
entry.isLead = true;
|
|
16626
|
+
}
|
|
16627
|
+
function augmentTree(nodes) {
|
|
16628
|
+
return nodes.map((n) => {
|
|
16629
|
+
const override = rolesByAgent.get(n.agent.id);
|
|
16630
|
+
return {
|
|
16631
|
+
...n,
|
|
16632
|
+
reports: augmentTree(n.reports),
|
|
16633
|
+
project_roles: override?.roles ?? [],
|
|
16634
|
+
is_project_lead: override?.isLead ?? false
|
|
16635
|
+
};
|
|
16636
|
+
}).filter((n) => {
|
|
16637
|
+
if (!opts?.filter_to_project)
|
|
16638
|
+
return true;
|
|
16639
|
+
const hasRole = n.project_roles.length > 0;
|
|
16640
|
+
const hasDescendant = n.reports.length > 0;
|
|
16641
|
+
return hasRole || hasDescendant;
|
|
16642
|
+
});
|
|
16643
|
+
}
|
|
16644
|
+
return augmentTree(globalTree);
|
|
16645
|
+
}
|
|
16646
|
+
var init_project_agent_roles = __esm(() => {
|
|
16647
|
+
init_database();
|
|
16648
|
+
init_agents();
|
|
16649
|
+
});
|
|
16650
|
+
|
|
16651
|
+
// src/db/patrol.ts
|
|
16652
|
+
var exports_patrol = {};
|
|
16653
|
+
__export(exports_patrol, {
|
|
16654
|
+
patrolTasks: () => patrolTasks,
|
|
16655
|
+
getReviewQueue: () => getReviewQueue
|
|
16656
|
+
});
|
|
16657
|
+
function rowToTask3(row) {
|
|
16658
|
+
return {
|
|
16659
|
+
...row,
|
|
16660
|
+
status: row.status,
|
|
16661
|
+
priority: row.priority,
|
|
16662
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
16663
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
16664
|
+
requires_approval: Boolean(row.requires_approval)
|
|
16665
|
+
};
|
|
16666
|
+
}
|
|
16667
|
+
function patrolTasks(opts, db) {
|
|
16668
|
+
const d = db || getDatabase();
|
|
16669
|
+
const stuckMinutes = opts?.stuck_minutes || 60;
|
|
16670
|
+
const confidenceThreshold = opts?.confidence_threshold || 0.5;
|
|
16671
|
+
const issues = [];
|
|
16672
|
+
let projectFilter = "";
|
|
16673
|
+
const projectParams = [];
|
|
16674
|
+
if (opts?.project_id) {
|
|
16675
|
+
projectFilter = " AND project_id = ?";
|
|
16676
|
+
projectParams.push(opts.project_id);
|
|
16677
|
+
}
|
|
16678
|
+
const stuckCutoff = new Date(Date.now() - stuckMinutes * 60 * 1000).toISOString();
|
|
16679
|
+
const stuckRows = d.query(`SELECT * FROM tasks WHERE status = 'in_progress' AND updated_at < ?${projectFilter} ORDER BY updated_at ASC`).all(stuckCutoff, ...projectParams);
|
|
16680
|
+
for (const row of stuckRows) {
|
|
16681
|
+
const task = rowToTask3(row);
|
|
16682
|
+
const minutesStuck = Math.round((Date.now() - new Date(task.updated_at).getTime()) / 60000);
|
|
16683
|
+
issues.push({
|
|
16684
|
+
type: "stuck",
|
|
16685
|
+
task_id: task.id,
|
|
16686
|
+
task_title: task.title,
|
|
16687
|
+
severity: minutesStuck > 480 ? "critical" : minutesStuck > 120 ? "high" : "medium",
|
|
16688
|
+
detail: `In progress for ${minutesStuck} minutes without update`
|
|
16689
|
+
});
|
|
16690
|
+
}
|
|
16691
|
+
const lowConfRows = d.query(`SELECT * FROM tasks WHERE status = 'completed' AND confidence IS NOT NULL AND confidence < ?${projectFilter} ORDER BY confidence ASC`).all(confidenceThreshold, ...projectParams);
|
|
16692
|
+
for (const row of lowConfRows) {
|
|
16693
|
+
const task = rowToTask3(row);
|
|
16694
|
+
issues.push({
|
|
16695
|
+
type: "low_confidence",
|
|
16696
|
+
task_id: task.id,
|
|
16697
|
+
task_title: task.title,
|
|
16698
|
+
severity: (task.confidence ?? 0) < 0.3 ? "high" : "medium",
|
|
16699
|
+
detail: `Completed with confidence ${task.confidence} (threshold: ${confidenceThreshold})`
|
|
16700
|
+
});
|
|
16701
|
+
}
|
|
16702
|
+
const orphanedRows = d.query(`SELECT * FROM tasks WHERE status = 'pending' AND project_id IS NULL AND agent_id IS NULL AND assigned_to IS NULL ORDER BY created_at ASC`).all();
|
|
16703
|
+
for (const row of orphanedRows) {
|
|
16704
|
+
const task = rowToTask3(row);
|
|
16705
|
+
issues.push({
|
|
16706
|
+
type: "orphaned",
|
|
16707
|
+
task_id: task.id,
|
|
16708
|
+
task_title: task.title,
|
|
16709
|
+
severity: "low",
|
|
16710
|
+
detail: "Pending task with no project, no agent, and no assignee"
|
|
16711
|
+
});
|
|
16712
|
+
}
|
|
16713
|
+
const needsReviewRows = d.query(`SELECT * FROM tasks WHERE status = 'completed' AND requires_approval = 1 AND approved_by IS NULL${projectFilter} ORDER BY completed_at DESC`).all(...projectParams);
|
|
16714
|
+
for (const row of needsReviewRows) {
|
|
16715
|
+
const task = rowToTask3(row);
|
|
16716
|
+
issues.push({
|
|
16717
|
+
type: "needs_review",
|
|
16718
|
+
task_id: task.id,
|
|
16719
|
+
task_title: task.title,
|
|
16720
|
+
severity: "medium",
|
|
16721
|
+
detail: "Completed but requires approval \u2014 not yet reviewed"
|
|
16722
|
+
});
|
|
16723
|
+
}
|
|
16724
|
+
const pendingWithDeps = d.query(`SELECT t.* FROM tasks t
|
|
16725
|
+
WHERE t.status = 'pending'${projectFilter}
|
|
16726
|
+
AND t.id IN (SELECT task_id FROM task_dependencies)`).all(...projectParams);
|
|
16727
|
+
for (const row of pendingWithDeps) {
|
|
16728
|
+
const deps = d.query(`SELECT d.depends_on, t.status FROM task_dependencies d
|
|
16729
|
+
JOIN tasks t ON t.id = d.depends_on
|
|
16730
|
+
WHERE d.task_id = ?`).all(row.id);
|
|
16731
|
+
if (deps.length > 0 && deps.every((dep) => dep.status === "failed" || dep.status === "cancelled")) {
|
|
16732
|
+
const task = rowToTask3(row);
|
|
16733
|
+
issues.push({
|
|
16734
|
+
type: "zombie_blocked",
|
|
16735
|
+
task_id: task.id,
|
|
16736
|
+
task_title: task.title,
|
|
16737
|
+
severity: "high",
|
|
16738
|
+
detail: `Blocked by ${deps.length} task(s) that are all failed/cancelled \u2014 will never unblock`
|
|
16739
|
+
});
|
|
16740
|
+
}
|
|
16741
|
+
}
|
|
16742
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
16743
|
+
issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
16744
|
+
return {
|
|
16745
|
+
issues,
|
|
16746
|
+
total_issues: issues.length,
|
|
16747
|
+
scanned_at: new Date().toISOString()
|
|
16748
|
+
};
|
|
16749
|
+
}
|
|
16750
|
+
function getReviewQueue(opts, db) {
|
|
16751
|
+
const d = db || getDatabase();
|
|
16752
|
+
let sql = `SELECT * FROM tasks WHERE status = 'completed' AND (
|
|
16753
|
+
(requires_approval = 1 AND approved_by IS NULL)
|
|
16754
|
+
OR (confidence IS NOT NULL AND confidence < 0.5)
|
|
16755
|
+
)`;
|
|
16756
|
+
const params = [];
|
|
16757
|
+
if (opts?.project_id) {
|
|
16758
|
+
sql += " AND project_id = ?";
|
|
16759
|
+
params.push(opts.project_id);
|
|
16760
|
+
}
|
|
16761
|
+
sql += " ORDER BY CASE WHEN requires_approval = 1 AND approved_by IS NULL THEN 0 ELSE 1 END, confidence ASC";
|
|
16762
|
+
if (opts?.limit) {
|
|
16763
|
+
sql += " LIMIT ?";
|
|
16764
|
+
params.push(opts.limit);
|
|
16765
|
+
}
|
|
16766
|
+
return d.query(sql).all(...params).map(rowToTask3);
|
|
16767
|
+
}
|
|
16768
|
+
var init_patrol = __esm(() => {
|
|
16769
|
+
init_database();
|
|
16770
|
+
});
|
|
16771
|
+
|
|
16772
|
+
// src/db/agent-metrics.ts
|
|
16773
|
+
var exports_agent_metrics = {};
|
|
16774
|
+
__export(exports_agent_metrics, {
|
|
16775
|
+
scoreTask: () => scoreTask,
|
|
16776
|
+
getLeaderboard: () => getLeaderboard,
|
|
16777
|
+
getAgentMetrics: () => getAgentMetrics
|
|
16778
|
+
});
|
|
16779
|
+
function getAgentMetrics(agentId, opts, db) {
|
|
16780
|
+
const d = db || getDatabase();
|
|
16781
|
+
const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
|
|
16782
|
+
if (!agent)
|
|
16783
|
+
return null;
|
|
16784
|
+
let projectFilter = "";
|
|
16785
|
+
const params = [agent.id, agent.id];
|
|
16786
|
+
if (opts?.project_id) {
|
|
16787
|
+
projectFilter = " AND project_id = ?";
|
|
16788
|
+
params.push(opts.project_id);
|
|
16789
|
+
}
|
|
16790
|
+
const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}`).get(...params).count;
|
|
16791
|
+
const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'failed'${projectFilter}`).get(...params).count;
|
|
16792
|
+
const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'in_progress'${projectFilter}`).get(...params).count;
|
|
16793
|
+
const total = completed + failed;
|
|
16794
|
+
const completionRate = total > 0 ? completed / total : 0;
|
|
16795
|
+
const avgTime = d.query(`SELECT AVG(
|
|
16796
|
+
(julianday(completed_at) - julianday(created_at)) * 24 * 60
|
|
16797
|
+
) as avg_minutes
|
|
16798
|
+
FROM tasks
|
|
16799
|
+
WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
|
|
16800
|
+
const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
|
|
16801
|
+
FROM tasks
|
|
16802
|
+
WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
|
|
16803
|
+
const reviewTasks = d.query(`SELECT metadata FROM tasks
|
|
16804
|
+
WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}
|
|
16805
|
+
AND metadata LIKE '%_review_score%'`).all(...params);
|
|
16806
|
+
let reviewScoreAvg = null;
|
|
16807
|
+
if (reviewTasks.length > 0) {
|
|
16808
|
+
let total2 = 0;
|
|
16809
|
+
let count = 0;
|
|
16810
|
+
for (const row of reviewTasks) {
|
|
16811
|
+
try {
|
|
16812
|
+
const meta = JSON.parse(row.metadata);
|
|
16813
|
+
if (typeof meta._review_score === "number") {
|
|
16814
|
+
total2 += meta._review_score;
|
|
16815
|
+
count++;
|
|
16816
|
+
}
|
|
16817
|
+
} catch {}
|
|
16818
|
+
}
|
|
16819
|
+
if (count > 0)
|
|
16820
|
+
reviewScoreAvg = total2 / count;
|
|
16821
|
+
}
|
|
16822
|
+
const speedScore = avgTime?.avg_minutes != null ? Math.max(0, 1 - avgTime.avg_minutes / (60 * 24)) : 0.5;
|
|
16823
|
+
const confidenceScore = avgConf?.avg_confidence ?? 0.5;
|
|
16824
|
+
const volumeScore = Math.min(1, completed / 50);
|
|
16825
|
+
const compositeScore = completionRate * 0.3 + speedScore * 0.2 + confidenceScore * 0.3 + volumeScore * 0.2;
|
|
16826
|
+
return {
|
|
16827
|
+
agent_id: agent.id,
|
|
16828
|
+
agent_name: agent.name,
|
|
16829
|
+
tasks_completed: completed,
|
|
16830
|
+
tasks_failed: failed,
|
|
16831
|
+
tasks_in_progress: inProgress,
|
|
16832
|
+
completion_rate: Math.round(completionRate * 1000) / 1000,
|
|
16833
|
+
avg_completion_minutes: avgTime?.avg_minutes != null ? Math.round(avgTime.avg_minutes * 10) / 10 : null,
|
|
16834
|
+
avg_confidence: avgConf?.avg_confidence != null ? Math.round(avgConf.avg_confidence * 1000) / 1000 : null,
|
|
16835
|
+
review_score_avg: reviewScoreAvg != null ? Math.round(reviewScoreAvg * 1000) / 1000 : null,
|
|
16836
|
+
composite_score: Math.round(compositeScore * 1000) / 1000
|
|
16837
|
+
};
|
|
16838
|
+
}
|
|
16839
|
+
function getLeaderboard(opts, db) {
|
|
16840
|
+
const d = db || getDatabase();
|
|
16841
|
+
const agents = d.query("SELECT id FROM agents ORDER BY name").all();
|
|
16842
|
+
const entries = [];
|
|
16843
|
+
for (const agent of agents) {
|
|
16844
|
+
const metrics = getAgentMetrics(agent.id, { project_id: opts?.project_id }, d);
|
|
16845
|
+
if (metrics && (metrics.tasks_completed > 0 || metrics.tasks_failed > 0 || metrics.tasks_in_progress > 0)) {
|
|
16846
|
+
entries.push(metrics);
|
|
16847
|
+
}
|
|
16848
|
+
}
|
|
16849
|
+
entries.sort((a, b) => b.composite_score - a.composite_score);
|
|
16850
|
+
const limit = opts?.limit || 20;
|
|
16851
|
+
return entries.slice(0, limit).map((entry, idx) => ({
|
|
16852
|
+
...entry,
|
|
16853
|
+
rank: idx + 1
|
|
16854
|
+
}));
|
|
16855
|
+
}
|
|
16856
|
+
function scoreTask(taskId, score, reviewerId, db) {
|
|
16857
|
+
const d = db || getDatabase();
|
|
16858
|
+
if (score < 0 || score > 1)
|
|
16859
|
+
throw new Error("Score must be between 0 and 1");
|
|
16860
|
+
const task = d.query("SELECT metadata FROM tasks WHERE id = ?").get(taskId);
|
|
16861
|
+
if (!task)
|
|
16862
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
16863
|
+
const metadata = JSON.parse(task.metadata || "{}");
|
|
16864
|
+
metadata._review_score = score;
|
|
16865
|
+
if (reviewerId)
|
|
16866
|
+
metadata._reviewed_by = reviewerId;
|
|
16867
|
+
metadata._reviewed_at = now();
|
|
16868
|
+
d.run("UPDATE tasks SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now(), taskId]);
|
|
16869
|
+
}
|
|
16870
|
+
var init_agent_metrics = __esm(() => {
|
|
16871
|
+
init_database();
|
|
16872
|
+
});
|
|
16873
|
+
|
|
15464
16874
|
// src/lib/extract.ts
|
|
15465
16875
|
var exports_extract = {};
|
|
15466
16876
|
__export(exports_extract, {
|
|
@@ -20675,9 +22085,8 @@ init_agents();
|
|
|
20675
22085
|
init_database();
|
|
20676
22086
|
|
|
20677
22087
|
// src/lib/logger.ts
|
|
20678
|
-
import { LogsClient } from "@hasna/logs";
|
|
20679
22088
|
var client = null;
|
|
20680
|
-
function getLogger() {
|
|
22089
|
+
async function getLogger() {
|
|
20681
22090
|
if (client)
|
|
20682
22091
|
return client;
|
|
20683
22092
|
const url = process.env.LOGS_URL;
|
|
@@ -20685,14 +22094,15 @@ function getLogger() {
|
|
|
20685
22094
|
const apiKey = process.env.LOGS_API_KEY;
|
|
20686
22095
|
if (!projectId)
|
|
20687
22096
|
return null;
|
|
22097
|
+
const { LogsClient } = await import("@hasna/logs");
|
|
20688
22098
|
client = new LogsClient({ url, projectId, apiKey });
|
|
20689
22099
|
return client;
|
|
20690
22100
|
}
|
|
20691
22101
|
async function logError(message, opts) {
|
|
20692
|
-
const logger = getLogger();
|
|
20693
|
-
if (!logger)
|
|
20694
|
-
return;
|
|
20695
22102
|
try {
|
|
22103
|
+
const logger = await getLogger();
|
|
22104
|
+
if (!logger)
|
|
22105
|
+
return;
|
|
20696
22106
|
await logger.push({
|
|
20697
22107
|
level: "error",
|
|
20698
22108
|
message,
|
|
@@ -20707,7 +22117,7 @@ async function logError(message, opts) {
|
|
|
20707
22117
|
|
|
20708
22118
|
// src/mcp/index.ts
|
|
20709
22119
|
init_types2();
|
|
20710
|
-
import { readFileSync as readFileSync6 } from "fs";
|
|
22120
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
20711
22121
|
import { join as join10, dirname as dirname3 } from "path";
|
|
20712
22122
|
import { fileURLToPath } from "url";
|
|
20713
22123
|
|
|
@@ -20950,14 +22360,24 @@ ${lines.join(`
|
|
|
20950
22360
|
|
|
20951
22361
|
// src/mcp/tools/task-crud.ts
|
|
20952
22362
|
init_tasks();
|
|
22363
|
+
init_types2();
|
|
20953
22364
|
function registerTaskCrudTools(server, ctx) {
|
|
20954
22365
|
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
22366
|
+
function versionFor(taskId, version) {
|
|
22367
|
+
const current = getTask(taskId);
|
|
22368
|
+
if (!current)
|
|
22369
|
+
throw new TaskNotFoundError(taskId);
|
|
22370
|
+
if (version !== undefined && current.version !== version) {
|
|
22371
|
+
throw new VersionConflictError(taskId, version, current.version);
|
|
22372
|
+
}
|
|
22373
|
+
return current.version;
|
|
22374
|
+
}
|
|
20955
22375
|
if (shouldRegisterTool("create_task")) {
|
|
20956
22376
|
server.tool("create_task", "Create a new task in a project. Pass short_id=null to auto-generate.", {
|
|
20957
22377
|
title: exports_external.string().describe("Task title"),
|
|
20958
22378
|
description: exports_external.string().optional().describe("Task description (markdown)"),
|
|
20959
|
-
status: exports_external.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Initial status (default: pending)"),
|
|
20960
|
-
priority: exports_external.enum(["low", "medium", "high", "
|
|
22379
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional().describe("Initial status (default: pending)"),
|
|
22380
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional().describe("Priority (default: medium)"),
|
|
20961
22381
|
project_id: exports_external.string().optional().describe("Project ID"),
|
|
20962
22382
|
task_list_id: exports_external.string().optional().describe("Task list ID"),
|
|
20963
22383
|
assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
|
|
@@ -20987,9 +22407,9 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
20987
22407
|
if (confidence !== undefined)
|
|
20988
22408
|
resolved.confidence = confidence;
|
|
20989
22409
|
if (retry_count !== undefined)
|
|
20990
|
-
resolved.
|
|
22410
|
+
resolved.max_retries = retry_count;
|
|
20991
22411
|
if (deadline)
|
|
20992
|
-
resolved.
|
|
22412
|
+
resolved.due_at = deadline;
|
|
20993
22413
|
const task = createTask(resolved);
|
|
20994
22414
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
20995
22415
|
} catch (e) {
|
|
@@ -20999,8 +22419,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
20999
22419
|
}
|
|
21000
22420
|
if (shouldRegisterTool("list_tasks")) {
|
|
21001
22421
|
server.tool("list_tasks", "List tasks with optional filters. Pass empty arrays for multi-value filters (e.g. status=[] shows all).", {
|
|
21002
|
-
status: exports_external.union([exports_external.enum(["pending", "in_progress", "completed", "cancelled"]), exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "cancelled"]))]).optional().describe("Filter by status"),
|
|
21003
|
-
priority: exports_external.union([exports_external.enum(["low", "medium", "high", "
|
|
22422
|
+
status: exports_external.union([exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]), exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))]).optional().describe("Filter by status"),
|
|
22423
|
+
priority: exports_external.union([exports_external.enum(["low", "medium", "high", "critical"]), exports_external.array(exports_external.enum(["low", "medium", "high", "critical"]))]).optional().describe("Filter by priority"),
|
|
21004
22424
|
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
21005
22425
|
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
21006
22426
|
assigned_to: exports_external.string().optional().describe("Filter by assignee (agent ID or name, empty string = unassigned)"),
|
|
@@ -21035,9 +22455,9 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
21035
22455
|
}, async ({ task_id }) => {
|
|
21036
22456
|
try {
|
|
21037
22457
|
const resolvedId = resolveId(task_id);
|
|
21038
|
-
const task =
|
|
22458
|
+
const task = getTask(resolvedId);
|
|
21039
22459
|
if (!task)
|
|
21040
|
-
throw new
|
|
22460
|
+
throw new TaskNotFoundError(task_id);
|
|
21041
22461
|
const focus = ctx.getAgentFocus(task.assigned_to || "");
|
|
21042
22462
|
const lines = [
|
|
21043
22463
|
`ID: ${task.id}`,
|
|
@@ -21051,7 +22471,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
21051
22471
|
task.estimated_minutes != null ? `Estimate: ${task.estimated_minutes} min` : null,
|
|
21052
22472
|
task.actual_minutes != null ? `Actual: ${task.actual_minutes} min` : null,
|
|
21053
22473
|
task.confidence != null ? `Confidence: ${task.confidence}` : null,
|
|
21054
|
-
task.
|
|
22474
|
+
task.due_at ? `Due: ${task.due_at}` : null,
|
|
21055
22475
|
task.completed_at ? `Completed: ${task.completed_at}` : null,
|
|
21056
22476
|
focus ? `Focus: agent=${focus.agent_id} project=${focus.project_id || "(global)"}` : null,
|
|
21057
22477
|
task.created_at ? `Created: ${task.created_at}` : null,
|
|
@@ -21073,8 +22493,8 @@ ${task.description}` : null
|
|
|
21073
22493
|
task_id: exports_external.string().describe("Task ID"),
|
|
21074
22494
|
title: exports_external.string().optional(),
|
|
21075
22495
|
description: exports_external.string().optional(),
|
|
21076
|
-
status: exports_external.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
21077
|
-
priority: exports_external.enum(["low", "medium", "high", "
|
|
22496
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
22497
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21078
22498
|
assigned_to: exports_external.string().nullable().optional().describe("Agent ID or name, null to unassign"),
|
|
21079
22499
|
project_id: exports_external.string().nullable().optional(),
|
|
21080
22500
|
task_list_id: exports_external.string().nullable().optional(),
|
|
@@ -21103,9 +22523,15 @@ ${task.description}` : null
|
|
|
21103
22523
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
21104
22524
|
if (resolved.depends_on && Array.isArray(resolved.depends_on))
|
|
21105
22525
|
resolved.depends_on = resolved.depends_on.map(resolveId);
|
|
21106
|
-
if (resolved.estimate !== undefined)
|
|
22526
|
+
if (resolved.estimate !== undefined) {
|
|
21107
22527
|
resolved.estimated_minutes = resolved.estimate;
|
|
21108
|
-
|
|
22528
|
+
delete resolved.estimate;
|
|
22529
|
+
}
|
|
22530
|
+
if (resolved.deadline !== undefined) {
|
|
22531
|
+
resolved.due_at = resolved.deadline;
|
|
22532
|
+
delete resolved.deadline;
|
|
22533
|
+
}
|
|
22534
|
+
const task = updateTask(resolvedId, { ...resolved, version: versionFor(resolvedId, version) });
|
|
21109
22535
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
21110
22536
|
} catch (e) {
|
|
21111
22537
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21202,48 +22628,23 @@ function deletePlan(id, db) {
|
|
|
21202
22628
|
return result.changes > 0;
|
|
21203
22629
|
}
|
|
21204
22630
|
|
|
21205
|
-
// src/db/comments.ts
|
|
21206
|
-
init_types2();
|
|
21207
|
-
init_database();
|
|
21208
|
-
init_tasks();
|
|
21209
|
-
function addComment(input, db) {
|
|
21210
|
-
const d = db || getDatabase();
|
|
21211
|
-
if (!getTask2(input.task_id, d)) {
|
|
21212
|
-
throw new TaskNotFoundError(input.task_id);
|
|
21213
|
-
}
|
|
21214
|
-
const id = uuid();
|
|
21215
|
-
const timestamp = now();
|
|
21216
|
-
d.run(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
21217
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
21218
|
-
id,
|
|
21219
|
-
input.task_id,
|
|
21220
|
-
input.agent_id || null,
|
|
21221
|
-
input.session_id || null,
|
|
21222
|
-
input.content,
|
|
21223
|
-
input.type || "comment",
|
|
21224
|
-
input.progress_pct ?? null,
|
|
21225
|
-
timestamp
|
|
21226
|
-
]);
|
|
21227
|
-
return getComment(id, d);
|
|
21228
|
-
}
|
|
21229
|
-
function getComment(id, db) {
|
|
21230
|
-
const d = db || getDatabase();
|
|
21231
|
-
return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
21232
|
-
}
|
|
21233
|
-
function listComments(taskId, db) {
|
|
21234
|
-
const d = db || getDatabase();
|
|
21235
|
-
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
21236
|
-
}
|
|
21237
|
-
function deleteComment(id, db) {
|
|
21238
|
-
const d = db || getDatabase();
|
|
21239
|
-
const result = d.run("DELETE FROM task_comments WHERE id = ?", [id]);
|
|
21240
|
-
return result.changes > 0;
|
|
21241
|
-
}
|
|
21242
|
-
|
|
21243
22631
|
// src/mcp/tools/task-project-tools.ts
|
|
22632
|
+
init_comments();
|
|
21244
22633
|
init_types2();
|
|
21245
22634
|
function registerTaskProjectTools(server, ctx) {
|
|
21246
22635
|
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
22636
|
+
function versionFor(taskId, version) {
|
|
22637
|
+
const current = getTask(taskId);
|
|
22638
|
+
if (!current)
|
|
22639
|
+
throw new TaskNotFoundError(taskId);
|
|
22640
|
+
if (version !== undefined && current.version !== version) {
|
|
22641
|
+
throw new VersionConflictError(taskId, version, current.version);
|
|
22642
|
+
}
|
|
22643
|
+
return current.version;
|
|
22644
|
+
}
|
|
22645
|
+
function updateWithOptionalVersion(taskId, updates, version) {
|
|
22646
|
+
return updateTask(taskId, { ...updates, version: versionFor(taskId, version) });
|
|
22647
|
+
}
|
|
21247
22648
|
if (shouldRegisterTool("start_task")) {
|
|
21248
22649
|
server.tool("start_task", "Mark a task as in_progress. Uses optimistic locking via version if provided.", {
|
|
21249
22650
|
task_id: exports_external.string().describe("Task ID"),
|
|
@@ -21251,7 +22652,12 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21251
22652
|
}, async ({ task_id, version }) => {
|
|
21252
22653
|
try {
|
|
21253
22654
|
const resolvedId = resolveId(task_id);
|
|
21254
|
-
|
|
22655
|
+
if (version !== undefined)
|
|
22656
|
+
versionFor(resolvedId, version);
|
|
22657
|
+
const current = getTask(resolvedId);
|
|
22658
|
+
if (!current)
|
|
22659
|
+
throw new TaskNotFoundError(resolvedId);
|
|
22660
|
+
const task = startTask(resolvedId, current.assigned_to || current.agent_id || "mcp");
|
|
21255
22661
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
21256
22662
|
} catch (e) {
|
|
21257
22663
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21267,7 +22673,12 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21267
22673
|
}, async ({ task_id, confidence, completed_at, version }) => {
|
|
21268
22674
|
try {
|
|
21269
22675
|
const resolvedId = resolveId(task_id);
|
|
21270
|
-
|
|
22676
|
+
if (version !== undefined)
|
|
22677
|
+
versionFor(resolvedId, version);
|
|
22678
|
+
const current = getTask(resolvedId);
|
|
22679
|
+
if (!current)
|
|
22680
|
+
throw new TaskNotFoundError(resolvedId);
|
|
22681
|
+
const task = completeTask(resolvedId, current.assigned_to || current.agent_id || undefined, undefined, { confidence, completed_at });
|
|
21271
22682
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
21272
22683
|
} catch (e) {
|
|
21273
22684
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21281,7 +22692,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21281
22692
|
}, async ({ task_id, version }) => {
|
|
21282
22693
|
try {
|
|
21283
22694
|
const resolvedId = resolveId(task_id);
|
|
21284
|
-
const task =
|
|
22695
|
+
const task = version === undefined ? setTaskStatus(resolvedId, "cancelled") : updateWithOptionalVersion(resolvedId, { status: "cancelled" }, version);
|
|
21285
22696
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
21286
22697
|
} catch (e) {
|
|
21287
22698
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21297,7 +22708,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21297
22708
|
try {
|
|
21298
22709
|
const resolvedId = resolveId(task_id);
|
|
21299
22710
|
const resolvedAssignee = resolveId(new_assignee, "agents");
|
|
21300
|
-
const task =
|
|
22711
|
+
const task = updateWithOptionalVersion(resolvedId, { assigned_to: resolvedAssignee }, version);
|
|
21301
22712
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
21302
22713
|
} catch (e) {
|
|
21303
22714
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21312,7 +22723,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21312
22723
|
}, async ({ task_id, deadline, version }) => {
|
|
21313
22724
|
try {
|
|
21314
22725
|
const resolvedId = resolveId(task_id);
|
|
21315
|
-
const task =
|
|
22726
|
+
const task = updateWithOptionalVersion(resolvedId, { due_at: deadline }, version);
|
|
21316
22727
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
21317
22728
|
} catch (e) {
|
|
21318
22729
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21322,12 +22733,12 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21322
22733
|
if (shouldRegisterTool("prioritize_task")) {
|
|
21323
22734
|
server.tool("prioritize_task", "Set a task's priority.", {
|
|
21324
22735
|
task_id: exports_external.string().describe("Task ID"),
|
|
21325
|
-
priority: exports_external.enum(["low", "medium", "high", "
|
|
22736
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).describe("New priority"),
|
|
21326
22737
|
version: exports_external.number().optional().describe("Expected version for optimistic locking")
|
|
21327
22738
|
}, async ({ task_id, priority, version }) => {
|
|
21328
22739
|
try {
|
|
21329
22740
|
const resolvedId = resolveId(task_id);
|
|
21330
|
-
const task =
|
|
22741
|
+
const task = version === undefined ? setTaskPriority(resolvedId, priority) : updateWithOptionalVersion(resolvedId, { priority }, version);
|
|
21331
22742
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
21332
22743
|
} catch (e) {
|
|
21333
22744
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21383,18 +22794,18 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21383
22794
|
if (shouldRegisterTool("bulk_update_tasks")) {
|
|
21384
22795
|
server.tool("bulk_update_tasks", "Update multiple tasks at once. All tasks must pass the dependency check.", {
|
|
21385
22796
|
task_ids: exports_external.array(exports_external.string()).describe("Array of task IDs to update"),
|
|
21386
|
-
status: exports_external.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
21387
|
-
priority: exports_external.enum(["low", "medium", "high", "
|
|
22797
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
22798
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21388
22799
|
assigned_to: exports_external.string().nullable().optional().describe("Agent ID or name, null to unassign")
|
|
21389
22800
|
}, async ({ task_ids, status, priority, assigned_to }) => {
|
|
21390
22801
|
try {
|
|
21391
|
-
const { bulkUpdateTasks: bulkUpdateTasks2 } = (()
|
|
22802
|
+
const { bulkUpdateTasks: bulkUpdateTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21392
22803
|
const resolved = task_ids.map(resolveId);
|
|
21393
22804
|
let resolvedAssignee = assigned_to;
|
|
21394
22805
|
if (resolvedAssignee && typeof resolvedAssignee === "string")
|
|
21395
22806
|
resolvedAssignee = resolveId(resolvedAssignee, "agents");
|
|
21396
22807
|
const result = bulkUpdateTasks2(resolved, { status, priority, assigned_to: resolvedAssignee });
|
|
21397
|
-
return { content: [{ type: "text", text: `${result.updated} task(s) updated, ${result.
|
|
22808
|
+
return { content: [{ type: "text", text: `${result.updated} task(s) updated, ${result.failed.length} failed.` }] };
|
|
21398
22809
|
} catch (e) {
|
|
21399
22810
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21400
22811
|
}
|
|
@@ -21405,8 +22816,8 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21405
22816
|
tasks: exports_external.array(exports_external.object({
|
|
21406
22817
|
title: exports_external.string(),
|
|
21407
22818
|
description: exports_external.string().optional(),
|
|
21408
|
-
status: exports_external.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
21409
|
-
priority: exports_external.enum(["low", "medium", "high", "
|
|
22819
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
22820
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21410
22821
|
project_id: exports_external.string().optional(),
|
|
21411
22822
|
task_list_id: exports_external.string().optional(),
|
|
21412
22823
|
assigned_to: exports_external.string().optional(),
|
|
@@ -21417,7 +22828,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21417
22828
|
})).describe("Array of task objects")
|
|
21418
22829
|
}, async ({ tasks }) => {
|
|
21419
22830
|
try {
|
|
21420
|
-
const { bulkCreateTasks: bulkCreateTasks2 } = (()
|
|
22831
|
+
const { bulkCreateTasks: bulkCreateTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21421
22832
|
const resolved = tasks.map((t) => {
|
|
21422
22833
|
const r = { ...t };
|
|
21423
22834
|
if (r.project_id)
|
|
@@ -21431,7 +22842,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21431
22842
|
return r;
|
|
21432
22843
|
});
|
|
21433
22844
|
const result = bulkCreateTasks2(resolved);
|
|
21434
|
-
return { content: [{ type: "text", text: `${result.created} task(s) created
|
|
22845
|
+
return { content: [{ type: "text", text: `${result.created.length} task(s) created.` }] };
|
|
21435
22846
|
} catch (e) {
|
|
21436
22847
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21437
22848
|
}
|
|
@@ -21443,9 +22854,9 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21443
22854
|
force: exports_external.boolean().optional().describe("Skip child check for all tasks (dangerous)")
|
|
21444
22855
|
}, async ({ task_ids, force }) => {
|
|
21445
22856
|
try {
|
|
21446
|
-
const { bulkDeleteTasks } = (()
|
|
22857
|
+
const { bulkDeleteTasks: bulkDeleteTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21447
22858
|
const resolved = task_ids.map(resolveId);
|
|
21448
|
-
const result =
|
|
22859
|
+
const result = bulkDeleteTasks2(resolved, force);
|
|
21449
22860
|
return { content: [{ type: "text", text: `${result.deleted} task(s) deleted, ${result.skipped} skipped (has children).` }] };
|
|
21450
22861
|
} catch (e) {
|
|
21451
22862
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21494,7 +22905,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21494
22905
|
const project = getProject(resolvedId);
|
|
21495
22906
|
if (!project)
|
|
21496
22907
|
throw new TaskNotFoundError(`Project not found: ${project_id}`);
|
|
21497
|
-
const { listTasks: listTasks3 } = (()
|
|
22908
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21498
22909
|
const tasks = listTasks3({ project_id: resolvedId, limit: 100 }, undefined);
|
|
21499
22910
|
const lines = [
|
|
21500
22911
|
`ID: ${project.id}`,
|
|
@@ -21595,7 +23006,7 @@ function registerTaskProjectTools(server, ctx) {
|
|
|
21595
23006
|
throw new TaskNotFoundError(`Task list not found: ${task_list_id}`);
|
|
21596
23007
|
let tasks = [];
|
|
21597
23008
|
if (include_tasks) {
|
|
21598
|
-
const { listTasks: listTasks3 } = (()
|
|
23009
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21599
23010
|
tasks = listTasks3({ task_list_id: resolvedId, limit: 200 }, undefined);
|
|
21600
23011
|
}
|
|
21601
23012
|
const lines = [
|
|
@@ -21695,7 +23106,7 @@ Tasks:` : null,
|
|
|
21695
23106
|
throw new TaskNotFoundError(`Plan not found: ${plan_id}`);
|
|
21696
23107
|
let tasks = [];
|
|
21697
23108
|
if (include_tasks) {
|
|
21698
|
-
const { listTasks: listTasks3 } = (()
|
|
23109
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21699
23110
|
tasks = listTasks3({ plan_id: resolvedId, limit: 200 }, undefined);
|
|
21700
23111
|
}
|
|
21701
23112
|
const lines = [
|
|
@@ -21784,7 +23195,7 @@ Tasks:` : null,
|
|
|
21784
23195
|
const tag = getTag(tag_id);
|
|
21785
23196
|
if (!tag)
|
|
21786
23197
|
throw new TaskNotFoundError(`Tag not found: ${tag_id}`);
|
|
21787
|
-
const { listTasks: listTasks3 } = (()
|
|
23198
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21788
23199
|
const tasks = listTasks3({ tags: [tag.name], limit: 100 }, undefined);
|
|
21789
23200
|
const lines = [
|
|
21790
23201
|
`Tag: ${tag.name}${tag.color ? ` (${tag.color})` : ""}`,
|
|
@@ -21863,7 +23274,7 @@ Tasks:` : null,
|
|
|
21863
23274
|
const label = getLabel(label_id);
|
|
21864
23275
|
if (!label)
|
|
21865
23276
|
throw new TaskNotFoundError(`Label not found: ${label_id}`);
|
|
21866
|
-
const { listTasks: listTasks3 } = (()
|
|
23277
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
21867
23278
|
const tasks = listTasks3({ tags: [label.name], limit: 100 }, undefined);
|
|
21868
23279
|
const lines = [
|
|
21869
23280
|
`Label: ${label.name}${label.color ? ` (${label.color})` : ""}`,
|
|
@@ -21914,7 +23325,7 @@ Tasks:` : null,
|
|
|
21914
23325
|
try {
|
|
21915
23326
|
const resolvedId = resolveId(task_id);
|
|
21916
23327
|
const resolvedAuthor = author ? resolveId(author, "agents") : undefined;
|
|
21917
|
-
const comment = addComment({ task_id: resolvedId, body,
|
|
23328
|
+
const comment = addComment({ task_id: resolvedId, content: body, agent_id: resolvedAuthor });
|
|
21918
23329
|
return { content: [{ type: "text", text: `Comment added to ${task_id.slice(0, 8)}: ${body.slice(0, 50)}${body.length > 50 ? "..." : ""}` }] };
|
|
21919
23330
|
} catch (e) {
|
|
21920
23331
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21930,8 +23341,8 @@ Tasks:` : null,
|
|
|
21930
23341
|
const comments = listComments(resolvedId);
|
|
21931
23342
|
if (comments.length === 0)
|
|
21932
23343
|
return { content: [{ type: "text", text: "No comments." }] };
|
|
21933
|
-
const lines = comments.map((c) => `[${c.
|
|
21934
|
-
${c.
|
|
23344
|
+
const lines = comments.map((c) => `[${c.agent_id || "unknown"}] ${c.created_at?.slice(0, 16)}:
|
|
23345
|
+
${c.content}`);
|
|
21935
23346
|
return { content: [{ type: "text", text: lines.join(`
|
|
21936
23347
|
|
|
21937
23348
|
`) }] };
|
|
@@ -21946,7 +23357,7 @@ Tasks:` : null,
|
|
|
21946
23357
|
body: exports_external.string().describe("New comment body")
|
|
21947
23358
|
}, async ({ comment_id, body }) => {
|
|
21948
23359
|
try {
|
|
21949
|
-
const comment = updateComment(comment_id, { body });
|
|
23360
|
+
const comment = updateComment(comment_id, { content: body });
|
|
21950
23361
|
return { content: [{ type: "text", text: `Comment ${comment_id.slice(0, 8)} updated.` }] };
|
|
21951
23362
|
} catch (e) {
|
|
21952
23363
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -21969,17 +23380,17 @@ Tasks:` : null,
|
|
|
21969
23380
|
server.tool("search_tasks", "Full-text search across task titles and descriptions.", {
|
|
21970
23381
|
query: exports_external.string().describe("Search query"),
|
|
21971
23382
|
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
21972
|
-
status: exports_external.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
23383
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
21973
23384
|
limit: exports_external.number().optional().describe("Max results (default: 20)")
|
|
21974
23385
|
}, async ({ query, project_id, status, limit }) => {
|
|
21975
23386
|
try {
|
|
21976
|
-
const { searchTasks } = (()
|
|
23387
|
+
const { searchTasks: searchTasks2 } = (init_search(), __toCommonJS(exports_search));
|
|
21977
23388
|
const resolved = { query, limit };
|
|
21978
23389
|
if (project_id)
|
|
21979
23390
|
resolved.project_id = resolveId(project_id, "projects");
|
|
21980
23391
|
if (status)
|
|
21981
23392
|
resolved.status = status;
|
|
21982
|
-
const results =
|
|
23393
|
+
const results = searchTasks2(resolved);
|
|
21983
23394
|
if (results.length === 0)
|
|
21984
23395
|
return { content: [{ type: "text", text: `No results for: ${query}` }] };
|
|
21985
23396
|
const lines = results.map((t) => `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.title}`);
|
|
@@ -21991,34 +23402,22 @@ ${lines.join(`
|
|
|
21991
23402
|
}
|
|
21992
23403
|
});
|
|
21993
23404
|
}
|
|
21994
|
-
if (shouldRegisterTool("sync")) {
|
|
21995
|
-
server.tool("sync", "Sync tasks from a GitHub PR or external source into the project.", {
|
|
21996
|
-
source: exports_external.enum(["github_pr", "linear", "asana"]).describe("Source type"),
|
|
21997
|
-
source_id: exports_external.string().describe("PR number, Linear issue ID, or Asana task ID"),
|
|
21998
|
-
project_id: exports_external.string().optional().describe("Project ID to import into"),
|
|
21999
|
-
options: exports_external.record(exports_external.unknown()).optional().describe("Source-specific options")
|
|
22000
|
-
}, async ({ source, source_id, project_id, options }) => {
|
|
22001
|
-
try {
|
|
22002
|
-
const { syncFromGithubPR, syncFromLinear, syncFromAsana } = (()=>{throw new Error("Cannot require module "+"../lib/sync.js");})();
|
|
22003
|
-
let result;
|
|
22004
|
-
if (source === "github_pr") {
|
|
22005
|
-
result = await syncFromGithubPR({ prNumber: parseInt(source_id), project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
|
|
22006
|
-
} else if (source === "linear") {
|
|
22007
|
-
result = await syncFromLinear({ issueId: source_id, project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
|
|
22008
|
-
} else {
|
|
22009
|
-
result = await syncFromAsana({ taskId: source_id, project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
|
|
22010
|
-
}
|
|
22011
|
-
return { content: [{ type: "text", text: `Synced from ${source}: ${result.task?.title || source_id}` }] };
|
|
22012
|
-
} catch (e) {
|
|
22013
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22014
|
-
}
|
|
22015
|
-
});
|
|
22016
|
-
}
|
|
22017
23405
|
}
|
|
22018
23406
|
|
|
22019
23407
|
// src/mcp/tools/task-workflow-tools.ts
|
|
23408
|
+
init_types2();
|
|
22020
23409
|
function registerTaskWorkflowTools(server, ctx) {
|
|
22021
23410
|
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
23411
|
+
function versionFor(taskId, version) {
|
|
23412
|
+
const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23413
|
+
const current = getTask2(taskId);
|
|
23414
|
+
if (!current)
|
|
23415
|
+
throw new TaskNotFoundError(taskId);
|
|
23416
|
+
if (version !== undefined && current.version !== version) {
|
|
23417
|
+
throw new VersionConflictError(taskId, version, current.version);
|
|
23418
|
+
}
|
|
23419
|
+
return current.version;
|
|
23420
|
+
}
|
|
22022
23421
|
if (shouldRegisterTool("approve_task")) {
|
|
22023
23422
|
server.tool("approve_task", "Approve a task that requires_approval. Records who approved it.", {
|
|
22024
23423
|
task_id: exports_external.string().describe("Task ID"),
|
|
@@ -22027,13 +23426,14 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
22027
23426
|
version: exports_external.number().optional().describe("Expected version for optimistic locking")
|
|
22028
23427
|
}, async ({ task_id, approved_by, notes, version }) => {
|
|
22029
23428
|
try {
|
|
22030
|
-
const { updateTask: updateTask2 } = (()
|
|
23429
|
+
const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22031
23430
|
const resolvedId = resolveId(task_id);
|
|
22032
23431
|
const resolvedApprover = approved_by ? resolveId(approved_by, "agents") : undefined;
|
|
22033
23432
|
const task = updateTask2(resolvedId, {
|
|
22034
23433
|
approved_by: resolvedApprover,
|
|
22035
|
-
metadata: notes ? { approval_notes: notes } : {}
|
|
22036
|
-
|
|
23434
|
+
metadata: notes ? { approval_notes: notes } : {},
|
|
23435
|
+
version: versionFor(resolvedId, version)
|
|
23436
|
+
});
|
|
22037
23437
|
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} approved by ${resolvedApprover || "unknown"}` }] };
|
|
22038
23438
|
} catch (e) {
|
|
22039
23439
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -22048,15 +23448,11 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
22048
23448
|
version: exports_external.number().optional().describe("Expected version for optimistic locking")
|
|
22049
23449
|
}, async ({ task_id, reason, agent_id, version }) => {
|
|
22050
23450
|
try {
|
|
22051
|
-
const {
|
|
23451
|
+
const { failTask: failTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22052
23452
|
const resolvedId = resolveId(task_id);
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
back_on_board: false,
|
|
22057
|
-
metadata: reason ? { failure_reason: reason } : {}
|
|
22058
|
-
}, version);
|
|
22059
|
-
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} marked failed. Retry count: ${task.retry_count}` }] };
|
|
23453
|
+
versionFor(resolvedId, version);
|
|
23454
|
+
const result = failTask2(resolvedId, agent_id, reason);
|
|
23455
|
+
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} marked failed. Retry count: ${result.task.retry_count}` }] };
|
|
22060
23456
|
} catch (e) {
|
|
22061
23457
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22062
23458
|
}
|
|
@@ -22065,12 +23461,12 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
22065
23461
|
if (shouldRegisterTool("get_my_tasks")) {
|
|
22066
23462
|
server.tool("get_my_tasks", "Get tasks assigned to the calling agent. Supports focus mode scoping.", {
|
|
22067
23463
|
agent_id: exports_external.string().optional().describe("Agent ID (defaults to context agent)"),
|
|
22068
|
-
status: exports_external.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Filter by status"),
|
|
23464
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional().describe("Filter by status"),
|
|
22069
23465
|
project_id: exports_external.string().optional().describe("Filter by project (respects focus mode)"),
|
|
22070
23466
|
limit: exports_external.number().optional().describe("Max results (default: 50)")
|
|
22071
23467
|
}, async ({ agent_id, status, project_id, limit }) => {
|
|
22072
23468
|
try {
|
|
22073
|
-
const { listTasks: listTasks3 } = (()
|
|
23469
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22074
23470
|
const focus = ctx.getAgentFocus(agent_id || "");
|
|
22075
23471
|
const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
|
|
22076
23472
|
const effectiveProjectId = focus?.project_id || project_id;
|
|
@@ -22090,6 +23486,122 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
22090
23486
|
}
|
|
22091
23487
|
});
|
|
22092
23488
|
}
|
|
23489
|
+
if (shouldRegisterTool("get_next_task")) {
|
|
23490
|
+
server.tool("get_next_task", "Get the best available pending task without claiming it.", {
|
|
23491
|
+
agent_id: exports_external.string().optional().describe("Agent ID or name for assignment affinity"),
|
|
23492
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
23493
|
+
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
23494
|
+
plan_id: exports_external.string().optional().describe("Filter by plan"),
|
|
23495
|
+
tags: exports_external.array(exports_external.string()).optional().describe("Filter by tags")
|
|
23496
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
23497
|
+
try {
|
|
23498
|
+
const { getNextTask: getNextTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23499
|
+
const filters = {};
|
|
23500
|
+
if (project_id)
|
|
23501
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
23502
|
+
if (task_list_id)
|
|
23503
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
23504
|
+
if (plan_id)
|
|
23505
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
23506
|
+
if (tags)
|
|
23507
|
+
filters.tags = tags;
|
|
23508
|
+
const task = getNextTask2(agent_id, filters);
|
|
23509
|
+
return { content: [{ type: "text", text: task ? formatTask(task) : "No available task." }] };
|
|
23510
|
+
} catch (e) {
|
|
23511
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23512
|
+
}
|
|
23513
|
+
});
|
|
23514
|
+
}
|
|
23515
|
+
if (shouldRegisterTool("claim_next_task")) {
|
|
23516
|
+
server.tool("claim_next_task", "Atomically claim and start the best available pending task for an agent.", {
|
|
23517
|
+
agent_id: exports_external.string().describe("Agent ID or name claiming the task"),
|
|
23518
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
23519
|
+
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
23520
|
+
plan_id: exports_external.string().optional().describe("Filter by plan"),
|
|
23521
|
+
tags: exports_external.array(exports_external.string()).optional().describe("Filter by tags")
|
|
23522
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
|
|
23523
|
+
try {
|
|
23524
|
+
const { claimNextTask: claimNextTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23525
|
+
const filters = {};
|
|
23526
|
+
if (project_id)
|
|
23527
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
23528
|
+
if (task_list_id)
|
|
23529
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
23530
|
+
if (plan_id)
|
|
23531
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
23532
|
+
if (tags)
|
|
23533
|
+
filters.tags = tags;
|
|
23534
|
+
const task = claimNextTask2(agent_id, filters);
|
|
23535
|
+
return { content: [{ type: "text", text: task ? formatTask(task) : "No available task to claim." }] };
|
|
23536
|
+
} catch (e) {
|
|
23537
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23538
|
+
}
|
|
23539
|
+
});
|
|
23540
|
+
}
|
|
23541
|
+
if (shouldRegisterTool("get_tasks_changed_since")) {
|
|
23542
|
+
server.tool("get_tasks_changed_since", "List tasks changed since an ISO timestamp.", {
|
|
23543
|
+
since: exports_external.string().describe("ISO timestamp"),
|
|
23544
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
23545
|
+
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
23546
|
+
limit: exports_external.number().optional().describe("Maximum tasks to return")
|
|
23547
|
+
}, async ({ since, project_id, task_list_id, limit }) => {
|
|
23548
|
+
try {
|
|
23549
|
+
const { getTasksChangedSince: getTasksChangedSince2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23550
|
+
const filters = {};
|
|
23551
|
+
if (project_id)
|
|
23552
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
23553
|
+
if (task_list_id)
|
|
23554
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
23555
|
+
const tasks = getTasksChangedSince2(since, filters).slice(0, limit || 50);
|
|
23556
|
+
if (tasks.length === 0)
|
|
23557
|
+
return { content: [{ type: "text", text: "No changed tasks." }] };
|
|
23558
|
+
return { content: [{ type: "text", text: tasks.map(formatTask).join(`
|
|
23559
|
+
`) }] };
|
|
23560
|
+
} catch (e) {
|
|
23561
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23562
|
+
}
|
|
23563
|
+
});
|
|
23564
|
+
}
|
|
23565
|
+
function registerContextTool(name, description) {
|
|
23566
|
+
if (!shouldRegisterTool(name))
|
|
23567
|
+
return;
|
|
23568
|
+
server.tool(name, description, {
|
|
23569
|
+
agent_id: exports_external.string().optional().describe("Agent ID or name"),
|
|
23570
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
23571
|
+
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
23572
|
+
explain_blocked: exports_external.boolean().optional().describe("Include blocked task details")
|
|
23573
|
+
}, async ({ agent_id, project_id, task_list_id, explain_blocked }) => {
|
|
23574
|
+
try {
|
|
23575
|
+
const { getStatus: getStatus2, getNextTask: getNextTask2, getOverdueTasks: getOverdueTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23576
|
+
const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
23577
|
+
const filters = {};
|
|
23578
|
+
if (project_id)
|
|
23579
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
23580
|
+
if (task_list_id)
|
|
23581
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
23582
|
+
const status = getStatus2(filters, agent_id, { explain_blocked });
|
|
23583
|
+
const next_task = getNextTask2(agent_id, filters);
|
|
23584
|
+
const overdue = getOverdueTasks2(filters.project_id);
|
|
23585
|
+
const latest_handoff = getLatestHandoff2(agent_id, filters.project_id);
|
|
23586
|
+
return {
|
|
23587
|
+
content: [{
|
|
23588
|
+
type: "text",
|
|
23589
|
+
text: JSON.stringify({
|
|
23590
|
+
status,
|
|
23591
|
+
next_task,
|
|
23592
|
+
overdue_count: overdue.length,
|
|
23593
|
+
latest_handoff,
|
|
23594
|
+
as_of: new Date().toISOString()
|
|
23595
|
+
}, null, 2)
|
|
23596
|
+
}]
|
|
23597
|
+
};
|
|
23598
|
+
} catch (e) {
|
|
23599
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
23600
|
+
}
|
|
23601
|
+
});
|
|
23602
|
+
}
|
|
23603
|
+
registerContextTool("get_context", "Get session start context: queue status, next task, overdue count, and latest handoff.");
|
|
23604
|
+
registerContextTool("bootstrap", "Bootstrap an agent session with queue context and the next available task.");
|
|
22093
23605
|
if (shouldRegisterTool("get_org_chart")) {
|
|
22094
23606
|
server.tool("get_org_chart", "Get the global org chart (agent hierarchy + titles).", {
|
|
22095
23607
|
format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)")
|
|
@@ -22110,7 +23622,7 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
22110
23622
|
}).join(`
|
|
22111
23623
|
`);
|
|
22112
23624
|
};
|
|
22113
|
-
const { getOrgChart: getOrgChart2 } = (()
|
|
23625
|
+
const { getOrgChart: getOrgChart2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
22114
23626
|
const tree = getOrgChart2();
|
|
22115
23627
|
if (format === "json") {
|
|
22116
23628
|
return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
|
|
@@ -22130,10 +23642,10 @@ function registerTaskWorkflowTools(server, ctx) {
|
|
|
22130
23642
|
reports_to: exports_external.string().describe("Manager agent ID or name")
|
|
22131
23643
|
}, async ({ agent_id, reports_to }) => {
|
|
22132
23644
|
try {
|
|
22133
|
-
const {
|
|
23645
|
+
const { updateAgent: updateAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
22134
23646
|
const resolvedAgent = resolveId(agent_id, "agents");
|
|
22135
23647
|
const resolvedManager = resolveId(reports_to, "agents");
|
|
22136
|
-
|
|
23648
|
+
updateAgent2(resolvedAgent, { reports_to: resolvedManager });
|
|
22137
23649
|
return { content: [{ type: "text", text: `${agent_id} now reports to ${reports_to}` }] };
|
|
22138
23650
|
} catch (e) {
|
|
22139
23651
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -22151,9 +23663,9 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
22151
23663
|
project_id: exports_external.string().optional().describe("Scope to a project")
|
|
22152
23664
|
}, async ({ days = 7, project_id }) => {
|
|
22153
23665
|
try {
|
|
22154
|
-
const { archiveCompletedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23666
|
+
const { archiveCompletedTasks: archiveCompletedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22155
23667
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
22156
|
-
const count =
|
|
23668
|
+
const count = archiveCompletedTasks2(days, resolvedProjectId);
|
|
22157
23669
|
return { content: [{ type: "text", text: `Archived ${count} completed task(s) older than ${days} days.` }] };
|
|
22158
23670
|
} catch (e) {
|
|
22159
23671
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -22179,9 +23691,9 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
22179
23691
|
limit: exports_external.number().optional().describe("Max results (default: 50)")
|
|
22180
23692
|
}, async ({ project_id, limit }) => {
|
|
22181
23693
|
try {
|
|
22182
|
-
const { getArchivedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23694
|
+
const { getArchivedTasks: getArchivedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22183
23695
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
22184
|
-
const tasks =
|
|
23696
|
+
const tasks = getArchivedTasks2({ project_id: resolvedProjectId, limit: limit || 50 });
|
|
22185
23697
|
if (tasks.length === 0)
|
|
22186
23698
|
return { content: [{ type: "text", text: "No archived tasks." }] };
|
|
22187
23699
|
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} archived ${t.archived_at}`);
|
|
@@ -22197,14 +23709,13 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
22197
23709
|
task_id: exports_external.string().describe("Task ID")
|
|
22198
23710
|
}, async ({ task_id }) => {
|
|
22199
23711
|
try {
|
|
22200
|
-
const { autoAssign } = (init_agents(), __toCommonJS(exports_agents));
|
|
22201
23712
|
const resolvedId = resolveId(task_id);
|
|
22202
|
-
const
|
|
22203
|
-
|
|
23713
|
+
const { autoAssignTask: autoAssignTask2 } = (init_auto_assign(), __toCommonJS(exports_auto_assign));
|
|
23714
|
+
const assignment = await autoAssignTask2(resolvedId);
|
|
23715
|
+
if (!assignment.assigned_to)
|
|
22204
23716
|
return { content: [{ type: "text", text: "No suitable agent found for this task." }] };
|
|
22205
|
-
const
|
|
22206
|
-
|
|
22207
|
-
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} assigned to ${assignment.agent_name} (score: ${assignment.score.toFixed(2)})` }] };
|
|
23717
|
+
const reason = assignment.reason ? ` \u2014 ${assignment.reason}` : "";
|
|
23718
|
+
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} assigned to ${assignment.agent_name || assignment.assigned_to} via ${assignment.method}${reason}` }] };
|
|
22208
23719
|
} catch (e) {
|
|
22209
23720
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22210
23721
|
}
|
|
@@ -22215,10 +23726,23 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
22215
23726
|
agent_id: exports_external.string().optional().describe("Agent ID (defaults to context agent)")
|
|
22216
23727
|
}, async ({ agent_id }) => {
|
|
22217
23728
|
try {
|
|
22218
|
-
const {
|
|
23729
|
+
const { listTasks: listTasks3, getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22219
23730
|
const focus = ctx.getAgentFocus(agent_id || "");
|
|
22220
23731
|
const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
|
|
22221
|
-
|
|
23732
|
+
if (!effectiveAgentId) {
|
|
23733
|
+
return { content: [{ type: "text", text: "No agent_id provided and no agent focus is active." }], isError: true };
|
|
23734
|
+
}
|
|
23735
|
+
const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
|
|
23736
|
+
const now2 = Date.now();
|
|
23737
|
+
const dueSoonCutoff = now2 + 24 * 60 * 60 * 1000;
|
|
23738
|
+
const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
|
|
23739
|
+
const workload = {
|
|
23740
|
+
in_progress: assigned.filter((t) => t.status === "in_progress").length,
|
|
23741
|
+
pending: assigned.filter((t) => t.status === "pending").length,
|
|
23742
|
+
completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now2 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
|
|
23743
|
+
due_soon: assigned.filter((t) => t.due_at && new Date(t.due_at).getTime() <= dueSoonCutoff && new Date(t.due_at).getTime() >= now2 && !["completed", "cancelled", "failed"].includes(t.status)).length,
|
|
23744
|
+
blocked: blocked.length
|
|
23745
|
+
};
|
|
22222
23746
|
const lines = [
|
|
22223
23747
|
`Agent: ${effectiveAgentId}`,
|
|
22224
23748
|
`In Progress: ${workload.in_progress}`,
|
|
@@ -22240,10 +23764,29 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
22240
23764
|
max_per_agent: exports_external.number().optional().describe("Max tasks per agent (default: 5)")
|
|
22241
23765
|
}, async ({ project_id, max_per_agent }) => {
|
|
22242
23766
|
try {
|
|
22243
|
-
const {
|
|
23767
|
+
const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
23768
|
+
const { listTasks: listTasks3, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22244
23769
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
22245
|
-
const
|
|
22246
|
-
|
|
23770
|
+
const limit = max_per_agent || 5;
|
|
23771
|
+
const agents = listAgents2().filter((agent) => agent.status === "active");
|
|
23772
|
+
if (agents.length === 0)
|
|
23773
|
+
return { content: [{ type: "text", text: "No active agents available for rebalancing." }] };
|
|
23774
|
+
const activeTasks = listTasks3({ project_id: resolvedProjectId, status: ["pending", "in_progress"], limit: 1000 }, undefined);
|
|
23775
|
+
const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => t.assigned_to === agent.id).length]));
|
|
23776
|
+
let moved = 0;
|
|
23777
|
+
let skipped = 0;
|
|
23778
|
+
for (const task of activeTasks.filter((t) => t.status === "pending" && t.assigned_to && (load.get(t.assigned_to) ?? 0) > limit)) {
|
|
23779
|
+
const target = agents.filter((agent) => agent.id !== task.assigned_to).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
|
|
23780
|
+
if (!target || (load.get(target.id) ?? 0) >= limit) {
|
|
23781
|
+
skipped++;
|
|
23782
|
+
continue;
|
|
23783
|
+
}
|
|
23784
|
+
updateTask2(task.id, { assigned_to: target.id, version: task.version });
|
|
23785
|
+
load.set(task.assigned_to, (load.get(task.assigned_to) ?? 1) - 1);
|
|
23786
|
+
load.set(target.id, (load.get(target.id) ?? 0) + 1);
|
|
23787
|
+
moved++;
|
|
23788
|
+
}
|
|
23789
|
+
return { content: [{ type: "text", text: `Rebalanced: moved ${moved} task(s), ${skipped} skipped.` }] };
|
|
22247
23790
|
} catch (e) {
|
|
22248
23791
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22249
23792
|
}
|
|
@@ -22256,13 +23799,13 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
22256
23799
|
agent_id: exports_external.string().optional().describe("Filter by assignee")
|
|
22257
23800
|
}, async ({ hours = 24, project_id, agent_id }) => {
|
|
22258
23801
|
try {
|
|
22259
|
-
const { notifyUpcomingDeadlines } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23802
|
+
const { notifyUpcomingDeadlines: notifyUpcomingDeadlines2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22260
23803
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
22261
23804
|
const resolvedAgentId = agent_id ? resolveId(agent_id, "agents") : undefined;
|
|
22262
|
-
const tasks =
|
|
23805
|
+
const tasks = notifyUpcomingDeadlines2({ hours, project_id: resolvedProjectId, agent_id: resolvedAgentId });
|
|
22263
23806
|
if (tasks.length === 0)
|
|
22264
23807
|
return { content: [{ type: "text", text: "No deadlines approaching." }] };
|
|
22265
|
-
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} due ${t.
|
|
23808
|
+
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} due ${t.due_at}`);
|
|
22266
23809
|
return { content: [{ type: "text", text: `${tasks.length} task(s) due within ${hours}h:
|
|
22267
23810
|
${lines.join(`
|
|
22268
23811
|
`)}` }] };
|
|
@@ -22296,9 +23839,9 @@ ${lines.join(`
|
|
|
22296
23839
|
project_id: exports_external.string().optional().describe("Filter by project")
|
|
22297
23840
|
}, async ({ project_id }) => {
|
|
22298
23841
|
try {
|
|
22299
|
-
const { getBlockedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23842
|
+
const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22300
23843
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
22301
|
-
const tasks =
|
|
23844
|
+
const tasks = getBlockedTasks2(resolvedProjectId);
|
|
22302
23845
|
if (tasks.length === 0)
|
|
22303
23846
|
return { content: [{ type: "text", text: "No blocked tasks." }] };
|
|
22304
23847
|
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.title} \u2014 blocked by ${(t.blocked_by || []).map((b) => b.slice(0, 8)).join(", ")}`);
|
|
@@ -22315,9 +23858,9 @@ ${lines.join(`
|
|
|
22315
23858
|
project_id: exports_external.string().optional().describe("Filter by project")
|
|
22316
23859
|
}, async ({ project_id }) => {
|
|
22317
23860
|
try {
|
|
22318
|
-
const { getBlockingTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23861
|
+
const { getBlockingTasks: getBlockingTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22319
23862
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
22320
|
-
const tasks =
|
|
23863
|
+
const tasks = getBlockingTasks2(resolvedProjectId);
|
|
22321
23864
|
if (tasks.length === 0)
|
|
22322
23865
|
return { content: [{ type: "text", text: "No tasks blocking others." }] };
|
|
22323
23866
|
const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.title} \u2014 blocking ${t.blocking_count} task(s)`);
|
|
@@ -22332,20 +23875,18 @@ ${lines.join(`
|
|
|
22332
23875
|
if (shouldRegisterTool("get_health")) {
|
|
22333
23876
|
server.tool("get_health", "Get system health: task counts by status, active agents, project summary.", async () => {
|
|
22334
23877
|
try {
|
|
22335
|
-
const {
|
|
22336
|
-
const { listProjects: listProjects2 } = (
|
|
23878
|
+
const { countTasks: countTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23879
|
+
const { listProjects: listProjects2 } = (init_projects(), __toCommonJS(exports_projects));
|
|
22337
23880
|
const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
22338
|
-
const
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
|
|
22342
|
-
|
|
22343
|
-
|
|
22344
|
-
const projects = listProjects2({ limit: 100 });
|
|
22345
|
-
const agents = listAgents2({ limit: 100 });
|
|
23881
|
+
const pending = countTasks2({ status: "pending" });
|
|
23882
|
+
const inProgress = countTasks2({ status: "in_progress" });
|
|
23883
|
+
const completed = countTasks2({ status: "completed" });
|
|
23884
|
+
const cancelled = countTasks2({ status: "cancelled" });
|
|
23885
|
+
const projects = listProjects2();
|
|
23886
|
+
const agents = listAgents2();
|
|
22346
23887
|
const lines = [
|
|
22347
23888
|
`=== System Health ===`,
|
|
22348
|
-
`Tasks: ${pending
|
|
23889
|
+
`Tasks: ${pending} pending | ${inProgress} in progress | ${completed} completed | ${cancelled} cancelled`,
|
|
22349
23890
|
`Projects: ${projects.length} total`,
|
|
22350
23891
|
`Agents: ${agents.length} registered`
|
|
22351
23892
|
];
|
|
@@ -22362,17 +23903,31 @@ ${lines.join(`
|
|
|
22362
23903
|
function registerTaskAdvTools(server, ctx) {
|
|
22363
23904
|
const { shouldRegisterTool, resolveId, formatError, formatTask, formatTaskDetail } = ctx;
|
|
22364
23905
|
if (shouldRegisterTool("get_status")) {
|
|
22365
|
-
server.tool("get_status", "Get
|
|
22366
|
-
task_id: exports_external.string().describe("Task ID")
|
|
22367
|
-
|
|
23906
|
+
server.tool("get_status", "Get queue status summary, or pass task_id for a task's detailed status.", {
|
|
23907
|
+
task_id: exports_external.string().optional().describe("Task ID for task-specific status"),
|
|
23908
|
+
project_id: exports_external.string().optional().describe("Filter summary by project"),
|
|
23909
|
+
task_list_id: exports_external.string().optional().describe("Filter summary by task list"),
|
|
23910
|
+
agent_id: exports_external.string().optional().describe("Agent for next-task affinity"),
|
|
23911
|
+
explain_blocked: exports_external.boolean().optional().describe("Include blocked task explanations in summary")
|
|
23912
|
+
}, async ({ task_id, project_id, task_list_id, agent_id, explain_blocked }) => {
|
|
22368
23913
|
try {
|
|
23914
|
+
if (!task_id) {
|
|
23915
|
+
const { getStatus: getStatus2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23916
|
+
const filters = {};
|
|
23917
|
+
if (project_id)
|
|
23918
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
23919
|
+
if (task_list_id)
|
|
23920
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
23921
|
+
const status = getStatus2(filters, agent_id, { explain_blocked });
|
|
23922
|
+
return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
|
|
23923
|
+
}
|
|
22369
23924
|
const resolvedId = resolveId(task_id);
|
|
22370
|
-
const { getTask:
|
|
22371
|
-
const task =
|
|
23925
|
+
const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23926
|
+
const task = getTask2(resolvedId);
|
|
22372
23927
|
if (!task)
|
|
22373
23928
|
throw new Error(`Task not found: ${task_id}`);
|
|
22374
23929
|
const { getTaskDependencies: getTaskDependencies3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22375
|
-
const { listComments: listComments2 } = (
|
|
23930
|
+
const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
22376
23931
|
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
22377
23932
|
const [deps, comments, files] = await Promise.all([
|
|
22378
23933
|
Promise.resolve(getTaskDependencies3(resolvedId, "both")),
|
|
@@ -22382,14 +23937,14 @@ function registerTaskAdvTools(server, ctx) {
|
|
|
22382
23937
|
const lines = [
|
|
22383
23938
|
`Status: ${task.status} | Priority: ${task.priority}`,
|
|
22384
23939
|
task.assigned_to ? `Assigned: ${task.assigned_to}` : "Unassigned",
|
|
22385
|
-
task.
|
|
23940
|
+
task.due_at ? `Due: ${task.due_at}` : null,
|
|
22386
23941
|
task.confidence != null ? `Confidence: ${task.confidence}` : null,
|
|
22387
23942
|
deps.length > 0 ? `
|
|
22388
23943
|
Dependencies (${deps.length}):` : null,
|
|
22389
23944
|
...deps.map((d) => ` [${d.direction}] ${d.task_id.slice(0, 8)} (${d.status})`),
|
|
22390
23945
|
comments.length > 0 ? `
|
|
22391
23946
|
Comments (${comments.length}):` : null,
|
|
22392
|
-
...comments.map((c) => ` [${c.
|
|
23947
|
+
...comments.map((c) => ` [${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content.slice(0, 80)}`),
|
|
22393
23948
|
files.length > 0 ? `
|
|
22394
23949
|
Files (${files.length}):` : null,
|
|
22395
23950
|
...files.map((f) => ` ${f.status} ${f.path}`)
|
|
@@ -22407,22 +23962,22 @@ Files (${files.length}):` : null,
|
|
|
22407
23962
|
}, async ({ task_id }) => {
|
|
22408
23963
|
try {
|
|
22409
23964
|
const resolvedId = resolveId(task_id);
|
|
22410
|
-
const { getTask:
|
|
22411
|
-
const task =
|
|
23965
|
+
const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23966
|
+
const task = getTask2(resolvedId);
|
|
22412
23967
|
if (!task)
|
|
22413
23968
|
throw new Error(`Task not found: ${task_id}`);
|
|
22414
23969
|
const { getTaskDependencies: getTaskDependencies3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22415
23970
|
const { getTaskRelationships: getTaskRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
22416
|
-
const { listComments: listComments2 } = (
|
|
23971
|
+
const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
22417
23972
|
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
22418
|
-
const { getTaskCommits } = (
|
|
23973
|
+
const { getTaskCommits: getTaskCommits2 } = (init_task_commits(), __toCommonJS(exports_task_commits));
|
|
22419
23974
|
const { getTaskWatchers: getTaskWatchers2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22420
23975
|
const [deps, rels, comments, files, commits, watchers] = await Promise.all([
|
|
22421
23976
|
Promise.resolve(getTaskDependencies3(resolvedId, "both")),
|
|
22422
23977
|
Promise.resolve(getTaskRelationships2(resolvedId)),
|
|
22423
23978
|
Promise.resolve(listComments2(resolvedId)),
|
|
22424
23979
|
Promise.resolve(listTaskFiles2(resolvedId)),
|
|
22425
|
-
Promise.resolve(
|
|
23980
|
+
Promise.resolve(getTaskCommits2(resolvedId)),
|
|
22426
23981
|
Promise.resolve(getTaskWatchers2(resolvedId))
|
|
22427
23982
|
]);
|
|
22428
23983
|
const lines = [
|
|
@@ -22436,7 +23991,7 @@ Files (${files.length}):` : null,
|
|
|
22436
23991
|
task.tags?.length ? `Tags: ${task.tags.join(", ")}` : null,
|
|
22437
23992
|
task.created_at ? `Created: ${task.created_at}` : null,
|
|
22438
23993
|
task.updated_at ? `Updated: ${task.updated_at}` : null,
|
|
22439
|
-
task.
|
|
23994
|
+
task.due_at ? `Due: ${task.due_at}` : null,
|
|
22440
23995
|
task.completed_at ? `Completed: ${task.completed_at}` : null,
|
|
22441
23996
|
deps.length > 0 ? `
|
|
22442
23997
|
--- Dependencies (${deps.length}) ---` : null,
|
|
@@ -22446,7 +24001,7 @@ Files (${files.length}):` : null,
|
|
|
22446
24001
|
...rels.map((r) => ` ${r.source_task_id.slice(0, 8)} --[${r.relationship_type}]--> ${r.target_task_id.slice(0, 8)}`),
|
|
22447
24002
|
comments.length > 0 ? `
|
|
22448
24003
|
--- Comments (${comments.length}) ---` : null,
|
|
22449
|
-
...comments.map((c) => ` [${c.
|
|
24004
|
+
...comments.map((c) => ` [${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content.slice(0, 120)}`),
|
|
22450
24005
|
files.length > 0 ? `
|
|
22451
24006
|
--- Files (${files.length}) ---` : null,
|
|
22452
24007
|
...files.map((f) => ` [${f.status}] ${f.path}`),
|
|
@@ -22495,8 +24050,8 @@ ${JSON.stringify(task.metadata, null, 2)}` : null
|
|
|
22495
24050
|
limit: 20
|
|
22496
24051
|
}, undefined);
|
|
22497
24052
|
const completedYesterday = completed.filter((t) => t.completed_at && t.completed_at.startsWith(yesterdayStr));
|
|
22498
|
-
const { getBlockedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22499
|
-
const blocked =
|
|
24053
|
+
const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
24054
|
+
const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => t.assigned_to === effectiveAgentId);
|
|
22500
24055
|
const lines = [
|
|
22501
24056
|
`Standup for ${effectiveAgentId} (${effectiveProjectId ? `project: ${effectiveProjectId.slice(0, 8)}` : "all projects"})`,
|
|
22502
24057
|
inProgress.length > 0 ? `
|
|
@@ -22525,11 +24080,11 @@ No blocked tasks.`,
|
|
|
22525
24080
|
agent_id: exports_external.string().optional().describe("Agent claiming (defaults to context)")
|
|
22526
24081
|
}, async ({ task_id, agent_id }) => {
|
|
22527
24082
|
try {
|
|
22528
|
-
const {
|
|
24083
|
+
const { startTask: startTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22529
24084
|
const resolvedId = resolveId(task_id);
|
|
22530
24085
|
const focus = ctx.getAgentFocus(agent_id || "");
|
|
22531
|
-
const effectiveAgent = focus ? focus.agent_id : agent_id || "";
|
|
22532
|
-
const task =
|
|
24086
|
+
const effectiveAgent = focus ? focus.agent_id : agent_id || "mcp";
|
|
24087
|
+
const task = startTask2(resolvedId, effectiveAgent);
|
|
22533
24088
|
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
22534
24089
|
} catch (e) {
|
|
22535
24090
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -22541,211 +24096,95 @@ No blocked tasks.`,
|
|
|
22541
24096
|
task_id: exports_external.string().describe("Task ID")
|
|
22542
24097
|
}, async ({ task_id }) => {
|
|
22543
24098
|
try {
|
|
22544
|
-
const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22545
|
-
const task = updateTask2(resolveId(task_id), { status: "pending", assigned_to: null });
|
|
22546
|
-
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
22547
|
-
} catch (e) {
|
|
22548
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22549
|
-
}
|
|
22550
|
-
});
|
|
22551
|
-
}
|
|
22552
|
-
if (shouldRegisterTool("extend_task")) {
|
|
22553
|
-
server.tool("extend_task", "Add more time to a task's estimate.", {
|
|
22554
|
-
task_id: exports_external.string().describe("Task ID"),
|
|
22555
|
-
minutes: exports_external.number().describe("Additional minutes to add to estimate")
|
|
22556
|
-
}, async ({ task_id, minutes }) => {
|
|
22557
|
-
try {
|
|
22558
|
-
const { getTask: getTask4, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
24099
|
+
const { getTask: getTask2, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22559
24100
|
const resolvedId = resolveId(task_id);
|
|
22560
|
-
const
|
|
22561
|
-
if (!
|
|
24101
|
+
const current = getTask2(resolvedId);
|
|
24102
|
+
if (!current)
|
|
22562
24103
|
throw new Error(`Task not found: ${task_id}`);
|
|
22563
|
-
const
|
|
22564
|
-
|
|
22565
|
-
return { content: [{ type: "text", text: `Estimate updated: ${currentEstimate} \u2192 ${updated.estimated_minutes} min (+${minutes})` }] };
|
|
22566
|
-
} catch (e) {
|
|
22567
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22568
|
-
}
|
|
22569
|
-
});
|
|
22570
|
-
}
|
|
22571
|
-
if (shouldRegisterTool("add_comment")) {
|
|
22572
|
-
server.tool("add_comment", "Alias for create_comment.", {
|
|
22573
|
-
task_id: exports_external.string().describe("Task ID"),
|
|
22574
|
-
body: exports_external.string().describe("Comment body"),
|
|
22575
|
-
author: exports_external.string().optional().describe("Author agent ID or name")
|
|
22576
|
-
}, async ({ task_id, body, author }) => {
|
|
22577
|
-
try {
|
|
22578
|
-
const { createComment } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22579
|
-
const resolvedId = resolveId(task_id);
|
|
22580
|
-
const resolvedAuthor = author ? resolveId(author, "agents") : undefined;
|
|
22581
|
-
createComment({ task_id: resolvedId, body, author: resolvedAuthor });
|
|
22582
|
-
return { content: [{ type: "text", text: `Comment added to ${task_id.slice(0, 8)}` }] };
|
|
22583
|
-
} catch (e) {
|
|
22584
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22585
|
-
}
|
|
22586
|
-
});
|
|
22587
|
-
}
|
|
22588
|
-
if (shouldRegisterTool("get_comments")) {
|
|
22589
|
-
server.tool("get_comments", "Alias for list_comments.", {
|
|
22590
|
-
task_id: exports_external.string().describe("Task ID")
|
|
22591
|
-
}, async ({ task_id }) => {
|
|
22592
|
-
try {
|
|
22593
|
-
const { listComments: listComments2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22594
|
-
const comments = listComments2(resolveId(task_id));
|
|
22595
|
-
if (comments.length === 0)
|
|
22596
|
-
return { content: [{ type: "text", text: "No comments." }] };
|
|
22597
|
-
const lines = comments.map((c) => `[${c.author || "?"}] ${c.created_at?.slice(0, 16)}: ${c.body}`);
|
|
22598
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
22599
|
-
|
|
22600
|
-
`) }] };
|
|
22601
|
-
} catch (e) {
|
|
22602
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22603
|
-
}
|
|
22604
|
-
});
|
|
22605
|
-
}
|
|
22606
|
-
if (shouldRegisterTool("list_my_tasks")) {
|
|
22607
|
-
server.tool("list_my_tasks", "Alias for get_my_tasks.", {
|
|
22608
|
-
agent_id: exports_external.string().optional().describe("Agent ID (defaults to context agent)"),
|
|
22609
|
-
status: exports_external.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
|
|
22610
|
-
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
22611
|
-
limit: exports_external.number().optional()
|
|
22612
|
-
}, async ({ agent_id, status, project_id, limit }) => {
|
|
22613
|
-
try {
|
|
22614
|
-
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
22615
|
-
const focus = ctx.getAgentFocus(agent_id || "");
|
|
22616
|
-
const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
|
|
22617
|
-
const effectiveProjectId = focus?.project_id || project_id;
|
|
22618
|
-
const tasks = listTasks3({
|
|
22619
|
-
assigned_to: effectiveAgentId,
|
|
22620
|
-
status,
|
|
22621
|
-
project_id: effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined,
|
|
22622
|
-
limit: limit || 50
|
|
22623
|
-
}, undefined);
|
|
22624
|
-
if (tasks.length === 0)
|
|
22625
|
-
return { content: [{ type: "text", text: "No tasks found." }] };
|
|
22626
|
-
const lines = tasks.map(formatTask);
|
|
22627
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
22628
|
-
`) }] };
|
|
22629
|
-
} catch (e) {
|
|
22630
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22631
|
-
}
|
|
22632
|
-
});
|
|
22633
|
-
}
|
|
22634
|
-
if (shouldRegisterTool("list_agents")) {
|
|
22635
|
-
server.tool("list_agents", "List all registered agents.", {
|
|
22636
|
-
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
22637
|
-
role: exports_external.string().optional().describe("Filter by global role"),
|
|
22638
|
-
capabilities: exports_external.array(exports_external.string()).optional().describe("Filter by capabilities")
|
|
22639
|
-
}, async ({ project_id, role, capabilities }) => {
|
|
22640
|
-
try {
|
|
22641
|
-
const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
22642
|
-
const resolved = {};
|
|
22643
|
-
if (project_id)
|
|
22644
|
-
resolved.project_id = resolveId(project_id, "projects");
|
|
22645
|
-
if (role)
|
|
22646
|
-
resolved.role = role;
|
|
22647
|
-
if (capabilities)
|
|
22648
|
-
resolved.capabilities = capabilities;
|
|
22649
|
-
const agents = listAgents2(resolved);
|
|
22650
|
-
if (agents.length === 0)
|
|
22651
|
-
return { content: [{ type: "text", text: "No agents found." }] };
|
|
22652
|
-
const lines = agents.map((a) => `\u25CF ${a.name} [${a.role || "no role"}]${a.capabilities?.length ? ` caps:[${a.capabilities.join(",")}]` : ""}`);
|
|
22653
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
22654
|
-
`) }] };
|
|
22655
|
-
} catch (e) {
|
|
22656
|
-
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22657
|
-
}
|
|
22658
|
-
});
|
|
22659
|
-
}
|
|
22660
|
-
if (shouldRegisterTool("get_agent")) {
|
|
22661
|
-
server.tool("get_agent", "Get full details for an agent.", {
|
|
22662
|
-
agent_id: exports_external.string().describe("Agent ID or name")
|
|
22663
|
-
}, async ({ agent_id }) => {
|
|
22664
|
-
try {
|
|
22665
|
-
const { getAgentByName: getAgentByName2, getAgent: getAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
22666
|
-
const resolvedId = resolveId(agent_id, "agents");
|
|
22667
|
-
const agent = agent_id.includes("@") || !resolvedId.startsWith("agent_") ? getAgentByName2(agent_id) : getAgent2(resolvedId);
|
|
22668
|
-
if (!agent)
|
|
22669
|
-
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
22670
|
-
const lines = [
|
|
22671
|
-
`ID: ${agent.id}`,
|
|
22672
|
-
`Name: ${agent.name}`,
|
|
22673
|
-
agent.email ? `Email: ${agent.email}` : null,
|
|
22674
|
-
agent.title ? `Title: ${agent.title}` : null,
|
|
22675
|
-
agent.role ? `Role: ${agent.role}` : null,
|
|
22676
|
-
agent.capabilities?.length ? `Capabilities: ${agent.capabilities.join(", ")}` : null,
|
|
22677
|
-
agent.last_seen_at ? `Last Seen: ${agent.last_seen_at}` : null
|
|
22678
|
-
].filter(Boolean);
|
|
22679
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
22680
|
-
`) }] };
|
|
24104
|
+
const task = updateTask2(resolvedId, { status: "pending", assigned_to: null, version: current.version });
|
|
24105
|
+
return { content: [{ type: "text", text: formatTask(task) }] };
|
|
22681
24106
|
} catch (e) {
|
|
22682
24107
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22683
24108
|
}
|
|
22684
|
-
});
|
|
22685
|
-
}
|
|
22686
|
-
if (shouldRegisterTool("
|
|
22687
|
-
server.tool("
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
title: exports_external.string().optional(),
|
|
22692
|
-
role: exports_external.string().optional(),
|
|
22693
|
-
capabilities: exports_external.array(exports_external.string()).optional()
|
|
22694
|
-
}, async ({ agent_id, ...updates }) => {
|
|
24109
|
+
});
|
|
24110
|
+
}
|
|
24111
|
+
if (shouldRegisterTool("extend_task")) {
|
|
24112
|
+
server.tool("extend_task", "Add more time to a task's estimate.", {
|
|
24113
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
24114
|
+
minutes: exports_external.number().describe("Additional minutes to add to estimate")
|
|
24115
|
+
}, async ({ task_id, minutes }) => {
|
|
22695
24116
|
try {
|
|
22696
|
-
const {
|
|
22697
|
-
const resolvedId = resolveId(
|
|
22698
|
-
const
|
|
22699
|
-
|
|
22700
|
-
|
|
24117
|
+
const { getTask: getTask2, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
24118
|
+
const resolvedId = resolveId(task_id);
|
|
24119
|
+
const task = getTask2(resolvedId);
|
|
24120
|
+
if (!task)
|
|
24121
|
+
throw new Error(`Task not found: ${task_id}`);
|
|
24122
|
+
const currentEstimate = task.estimated_minutes || 0;
|
|
24123
|
+
const updated = updateTask2(resolvedId, { estimated_minutes: currentEstimate + minutes, version: task.version });
|
|
24124
|
+
return { content: [{ type: "text", text: `Estimate updated: ${currentEstimate} \u2192 ${updated.estimated_minutes} min (+${minutes})` }] };
|
|
22701
24125
|
} catch (e) {
|
|
22702
24126
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22703
24127
|
}
|
|
22704
24128
|
});
|
|
22705
24129
|
}
|
|
22706
|
-
if (shouldRegisterTool("
|
|
22707
|
-
server.tool("
|
|
22708
|
-
|
|
22709
|
-
|
|
24130
|
+
if (shouldRegisterTool("add_comment")) {
|
|
24131
|
+
server.tool("add_comment", "Alias for create_comment.", {
|
|
24132
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
24133
|
+
body: exports_external.string().describe("Comment body"),
|
|
24134
|
+
author: exports_external.string().optional().describe("Author agent ID or name")
|
|
24135
|
+
}, async ({ task_id, body, author }) => {
|
|
22710
24136
|
try {
|
|
22711
|
-
const {
|
|
22712
|
-
const resolvedId = resolveId(
|
|
22713
|
-
const
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
deleteAgent2(agent.id);
|
|
22717
|
-
return { content: [{ type: "text", text: `Agent ${agent_id} deleted.` }] };
|
|
24137
|
+
const { addComment: addComment2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
24138
|
+
const resolvedId = resolveId(task_id);
|
|
24139
|
+
const resolvedAuthor = author ? resolveId(author, "agents") : undefined;
|
|
24140
|
+
addComment2({ task_id: resolvedId, content: body, agent_id: resolvedAuthor });
|
|
24141
|
+
return { content: [{ type: "text", text: `Comment added to ${task_id.slice(0, 8)}` }] };
|
|
22718
24142
|
} catch (e) {
|
|
22719
24143
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22720
24144
|
}
|
|
22721
24145
|
});
|
|
22722
24146
|
}
|
|
22723
|
-
if (shouldRegisterTool("
|
|
22724
|
-
server.tool("
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
title: exports_external.string().optional(),
|
|
22728
|
-
role: exports_external.string().optional(),
|
|
22729
|
-
capabilities: exports_external.array(exports_external.string()).optional()
|
|
22730
|
-
}, async ({ name, email, title, role, capabilities }) => {
|
|
24147
|
+
if (shouldRegisterTool("get_comments")) {
|
|
24148
|
+
server.tool("get_comments", "Alias for list_comments.", {
|
|
24149
|
+
task_id: exports_external.string().describe("Task ID")
|
|
24150
|
+
}, async ({ task_id }) => {
|
|
22731
24151
|
try {
|
|
22732
|
-
const {
|
|
22733
|
-
const
|
|
22734
|
-
|
|
24152
|
+
const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
|
|
24153
|
+
const comments = listComments2(resolveId(task_id));
|
|
24154
|
+
if (comments.length === 0)
|
|
24155
|
+
return { content: [{ type: "text", text: "No comments." }] };
|
|
24156
|
+
const lines = comments.map((c) => `[${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content}`);
|
|
24157
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
24158
|
+
|
|
24159
|
+
`) }] };
|
|
22735
24160
|
} catch (e) {
|
|
22736
24161
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22737
24162
|
}
|
|
22738
24163
|
});
|
|
22739
24164
|
}
|
|
22740
|
-
if (shouldRegisterTool("
|
|
22741
|
-
server.tool("
|
|
22742
|
-
agent_id: exports_external.string().describe("Agent ID"),
|
|
22743
|
-
status: exports_external.
|
|
22744
|
-
|
|
24165
|
+
if (shouldRegisterTool("list_my_tasks")) {
|
|
24166
|
+
server.tool("list_my_tasks", "Alias for get_my_tasks.", {
|
|
24167
|
+
agent_id: exports_external.string().optional().describe("Agent ID (defaults to context agent)"),
|
|
24168
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
24169
|
+
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
24170
|
+
limit: exports_external.number().optional()
|
|
24171
|
+
}, async ({ agent_id, status, project_id, limit }) => {
|
|
22745
24172
|
try {
|
|
22746
|
-
const {
|
|
22747
|
-
|
|
22748
|
-
|
|
24173
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
24174
|
+
const focus = ctx.getAgentFocus(agent_id || "");
|
|
24175
|
+
const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
|
|
24176
|
+
const effectiveProjectId = focus?.project_id || project_id;
|
|
24177
|
+
const tasks = listTasks3({
|
|
24178
|
+
assigned_to: effectiveAgentId,
|
|
24179
|
+
status,
|
|
24180
|
+
project_id: effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined,
|
|
24181
|
+
limit: limit || 50
|
|
24182
|
+
}, undefined);
|
|
24183
|
+
if (tasks.length === 0)
|
|
24184
|
+
return { content: [{ type: "text", text: "No tasks found." }] };
|
|
24185
|
+
const lines = tasks.map(formatTask);
|
|
24186
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
24187
|
+
`) }] };
|
|
22749
24188
|
} catch (e) {
|
|
22750
24189
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22751
24190
|
}
|
|
@@ -22792,6 +24231,11 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
22792
24231
|
prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
|
|
22793
24232
|
search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
|
|
22794
24233
|
get_my_tasks: "get_my_tasks \u2014 Get tasks for calling agent. Params: agent_id, status, project_id, limit",
|
|
24234
|
+
get_next_task: "get_next_task \u2014 Get the next available task without claiming it. Params: agent_id, project_id, task_list_id, plan_id, tags",
|
|
24235
|
+
claim_next_task: "claim_next_task \u2014 Atomically claim and start the next available task. Params: agent_id, project_id, task_list_id, plan_id, tags",
|
|
24236
|
+
get_tasks_changed_since: "get_tasks_changed_since \u2014 List tasks changed since an ISO timestamp. Params: since, project_id, task_list_id, limit",
|
|
24237
|
+
get_context: "get_context \u2014 Get session start context. Params: agent_id, project_id, task_list_id, explain_blocked",
|
|
24238
|
+
bootstrap: "bootstrap \u2014 Bootstrap an agent session with queue context. Params: agent_id, project_id, task_list_id, explain_blocked",
|
|
22795
24239
|
standup: "standup \u2014 Get standup report. Params: agent_id, project_id",
|
|
22796
24240
|
patrol_tasks: "patrol_tasks \u2014 Scan for task issues. Params: stuck_minutes, confidence_threshold, project_id",
|
|
22797
24241
|
get_review_queue: "get_review_queue \u2014 Get tasks needing review. Params: project_id, limit",
|
|
@@ -22849,9 +24293,20 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
22849
24293
|
get_health: "get_health \u2014 Get system health stats",
|
|
22850
24294
|
approve_task: "approve_task \u2014 Approve a task. Params: task_id, approved_by, notes, version",
|
|
22851
24295
|
fail_task: "fail_task \u2014 Mark task failed. Params: task_id, reason, agent_id, version",
|
|
24296
|
+
register_agent: "register_agent \u2014 Register an agent. Params: name, description, role, title, capabilities, session_id, working_dir, force",
|
|
24297
|
+
list_agents: "list_agents \u2014 List registered agents. Params: include_archived",
|
|
24298
|
+
get_agent: "get_agent \u2014 Get agent details. Params: agent_id, id, name",
|
|
24299
|
+
update_agent: "update_agent \u2014 Update an agent. Params: agent_id, id, name, description, role, title, level, capabilities, permissions, metadata",
|
|
24300
|
+
delete_agent: "delete_agent \u2014 Archive an agent. Params: agent_id, id, name",
|
|
24301
|
+
unarchive_agent: "unarchive_agent \u2014 Restore an archived agent. Params: agent_id, id, name",
|
|
24302
|
+
heartbeat: "heartbeat \u2014 Update an agent heartbeat. Params: agent_id",
|
|
24303
|
+
release_agent: "release_agent \u2014 Release an agent session/name. Params: agent_id, session_id",
|
|
24304
|
+
set_focus: "set_focus \u2014 Focus an agent on a project. Params: agent_id, project_id, task_list_id",
|
|
24305
|
+
get_focus: "get_focus \u2014 Get current agent focus. Params: agent_id",
|
|
24306
|
+
unfocus: "unfocus \u2014 Clear agent focus. Params: agent_id",
|
|
24307
|
+
suggest_agent_name: "suggest_agent_name \u2014 Suggest available agent names. Params: working_dir",
|
|
22852
24308
|
get_org_chart: "get_org_chart \u2014 Get global org chart. Params: format",
|
|
22853
24309
|
set_reports_to: "set_reports_to \u2014 Set org hierarchy. Params: agent_id, reports_to",
|
|
22854
|
-
sync: "sync \u2014 Sync from external source. Params: source, source_id, project_id, options",
|
|
22855
24310
|
extract_todos: "extract_todos \u2014 Scan code for TODO comments. Params: path, project_id, task_list_id, patterns, tags, assigned_to, agent_id, dry_run, extensions",
|
|
22856
24311
|
migrate_pg: "migrate_pg \u2014 Apply PostgreSQL migrations. Params: connection_string"
|
|
22857
24312
|
};
|
|
@@ -22870,39 +24325,7 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
22870
24325
|
init_tasks();
|
|
22871
24326
|
init_projects();
|
|
22872
24327
|
init_agents();
|
|
22873
|
-
|
|
22874
|
-
// src/db/task-commits.ts
|
|
22875
|
-
init_database();
|
|
22876
|
-
function rowToCommit(row) {
|
|
22877
|
-
return {
|
|
22878
|
-
...row,
|
|
22879
|
-
files_changed: row.files_changed ? JSON.parse(row.files_changed) : null
|
|
22880
|
-
};
|
|
22881
|
-
}
|
|
22882
|
-
function linkTaskToCommit(input, db) {
|
|
22883
|
-
const d = db || getDatabase();
|
|
22884
|
-
const existing = d.query("SELECT * FROM task_commits WHERE task_id = ? AND sha = ?").get(input.task_id, input.sha);
|
|
22885
|
-
if (existing) {
|
|
22886
|
-
d.run("UPDATE task_commits SET message = COALESCE(?, message), author = COALESCE(?, author), files_changed = COALESCE(?, files_changed), committed_at = COALESCE(?, committed_at) WHERE id = ?", [input.message ?? null, input.author ?? null, input.files_changed ? JSON.stringify(input.files_changed) : null, input.committed_at ?? null, existing.id]);
|
|
22887
|
-
return rowToCommit(d.query("SELECT * FROM task_commits WHERE id = ?").get(existing.id));
|
|
22888
|
-
}
|
|
22889
|
-
const id = uuid();
|
|
22890
|
-
d.run("INSERT INTO task_commits (id, task_id, sha, message, author, files_changed, committed_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, input.task_id, input.sha, input.message ?? null, input.author ?? null, input.files_changed ? JSON.stringify(input.files_changed) : null, input.committed_at ?? null, now()]);
|
|
22891
|
-
return rowToCommit(d.query("SELECT * FROM task_commits WHERE id = ?").get(id));
|
|
22892
|
-
}
|
|
22893
|
-
function getTaskCommits(taskId, db) {
|
|
22894
|
-
const d = db || getDatabase();
|
|
22895
|
-
return d.query("SELECT * FROM task_commits WHERE task_id = ? ORDER BY committed_at DESC, created_at DESC").all(taskId).map(rowToCommit);
|
|
22896
|
-
}
|
|
22897
|
-
function findTaskByCommit(sha, db) {
|
|
22898
|
-
const d = db || getDatabase();
|
|
22899
|
-
const row = d.query("SELECT * FROM task_commits WHERE sha = ? OR sha LIKE ? LIMIT 1").get(sha, `${sha}%`);
|
|
22900
|
-
if (!row)
|
|
22901
|
-
return null;
|
|
22902
|
-
return { task_id: row.task_id, commit: rowToCommit(row) };
|
|
22903
|
-
}
|
|
22904
|
-
|
|
22905
|
-
// src/mcp/tools/task-resources.ts
|
|
24328
|
+
init_task_commits();
|
|
22906
24329
|
function registerTaskResources(server, ctx) {
|
|
22907
24330
|
const { shouldRegisterTool, resolveId, formatError } = ctx;
|
|
22908
24331
|
server.resource("tasks", "todos://tasks", { description: "All active tasks", mimeType: "application/json" }, async () => {
|
|
@@ -22927,7 +24350,7 @@ function registerTaskResources(server, ctx) {
|
|
|
22927
24350
|
note: exports_external.string().optional().describe("Note about why this file is linked")
|
|
22928
24351
|
}, async ({ task_id, path, paths: multiplePaths, status, agent_id, note }) => {
|
|
22929
24352
|
try {
|
|
22930
|
-
const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (()
|
|
24353
|
+
const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
22931
24354
|
const resolvedId = resolveId(task_id);
|
|
22932
24355
|
let addedFiles;
|
|
22933
24356
|
if (multiplePaths && multiplePaths.length > 0) {
|
|
@@ -22971,7 +24394,7 @@ function registerTaskResources(server, ctx) {
|
|
|
22971
24394
|
if (shouldRegisterTool("list_task_files")) {
|
|
22972
24395
|
server.tool("list_task_files", "List all files linked to a task.", { task_id: exports_external.string().describe("Task ID") }, async ({ task_id }) => {
|
|
22973
24396
|
try {
|
|
22974
|
-
const { listTaskFiles: listTaskFiles2 } = (()
|
|
24397
|
+
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
22975
24398
|
const resolvedId = resolveId(task_id);
|
|
22976
24399
|
const files = listTaskFiles2(resolvedId);
|
|
22977
24400
|
if (files.length === 0)
|
|
@@ -22988,7 +24411,7 @@ ${lines.join(`
|
|
|
22988
24411
|
if (shouldRegisterTool("find_tasks_by_file")) {
|
|
22989
24412
|
server.tool("find_tasks_by_file", "Find which tasks are linked to a specific file path. Shows who's working on what files.", { path: exports_external.string().describe("File path to search for") }, async ({ path }) => {
|
|
22990
24413
|
try {
|
|
22991
|
-
const { findTasksByFile: findTasksByFile2 } = (()
|
|
24414
|
+
const { findTasksByFile: findTasksByFile2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
22992
24415
|
const files = findTasksByFile2(path);
|
|
22993
24416
|
if (files.length === 0)
|
|
22994
24417
|
return { content: [{ type: "text", text: `No tasks linked to ${path}` }] };
|
|
@@ -23008,7 +24431,7 @@ ${lines.join(`
|
|
|
23008
24431
|
min_edits: exports_external.number().optional().describe("Minimum edit count to include (default: 1)")
|
|
23009
24432
|
}, async ({ limit, project_id, min_edits }) => {
|
|
23010
24433
|
try {
|
|
23011
|
-
const { getFileHeatMap: getFileHeatMap2 } = (()
|
|
24434
|
+
const { getFileHeatMap: getFileHeatMap2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
23012
24435
|
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
23013
24436
|
const results = getFileHeatMap2({ limit, project_id: resolvedProjectId, min_edits });
|
|
23014
24437
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
@@ -23022,7 +24445,7 @@ ${lines.join(`
|
|
|
23022
24445
|
paths: exports_external.array(exports_external.string()).describe("Array of file paths to check")
|
|
23023
24446
|
}, async ({ paths }) => {
|
|
23024
24447
|
try {
|
|
23025
|
-
const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (()
|
|
24448
|
+
const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
23026
24449
|
const results = bulkFindTasksByFiles2(paths);
|
|
23027
24450
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
23028
24451
|
} catch (e) {
|
|
@@ -23035,11 +24458,11 @@ ${lines.join(`
|
|
|
23035
24458
|
project_id: exports_external.string().optional().describe("Filter by project")
|
|
23036
24459
|
}, async ({ project_id }) => {
|
|
23037
24460
|
try {
|
|
23038
|
-
const { listActiveFiles: listActiveFiles2 } = (()
|
|
24461
|
+
const { listActiveFiles: listActiveFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
23039
24462
|
let files = listActiveFiles2();
|
|
23040
24463
|
if (project_id) {
|
|
23041
24464
|
const pid = resolveId(project_id, "projects");
|
|
23042
|
-
const db = (()
|
|
24465
|
+
const db = (init_database(), __toCommonJS(exports_database)).getDatabase();
|
|
23043
24466
|
files = db.query(`
|
|
23044
24467
|
SELECT
|
|
23045
24468
|
tf.path,
|
|
@@ -23121,8 +24544,8 @@ ${lines.join(`
|
|
|
23121
24544
|
ttl_seconds: exports_external.number().optional().describe("Lock TTL in seconds (default: 1800 = 30 min)")
|
|
23122
24545
|
}, async ({ path, agent_id, task_id, ttl_seconds }) => {
|
|
23123
24546
|
try {
|
|
23124
|
-
const { lockFile } = (()
|
|
23125
|
-
const lock =
|
|
24547
|
+
const { lockFile: lockFile2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
24548
|
+
const lock = lockFile2({ path, agent_id, task_id, ttl_seconds });
|
|
23126
24549
|
return { content: [{ type: "text", text: JSON.stringify(lock, null, 2) }] };
|
|
23127
24550
|
} catch (e) {
|
|
23128
24551
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -23135,8 +24558,8 @@ ${lines.join(`
|
|
|
23135
24558
|
agent_id: exports_external.string().describe("Agent releasing the lock (must be the lock holder)")
|
|
23136
24559
|
}, async ({ path, agent_id }) => {
|
|
23137
24560
|
try {
|
|
23138
|
-
const { unlockFile } = (()
|
|
23139
|
-
const released =
|
|
24561
|
+
const { unlockFile: unlockFile2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
24562
|
+
const released = unlockFile2(path, agent_id);
|
|
23140
24563
|
return { content: [{ type: "text", text: JSON.stringify({ released, path }) }] };
|
|
23141
24564
|
} catch (e) {
|
|
23142
24565
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -23148,8 +24571,8 @@ ${lines.join(`
|
|
|
23148
24571
|
path: exports_external.string().describe("File path to check")
|
|
23149
24572
|
}, async ({ path }) => {
|
|
23150
24573
|
try {
|
|
23151
|
-
const { checkFileLock } = (()
|
|
23152
|
-
const lock =
|
|
24574
|
+
const { checkFileLock: checkFileLock2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
24575
|
+
const lock = checkFileLock2(path);
|
|
23153
24576
|
if (!lock)
|
|
23154
24577
|
return { content: [{ type: "text", text: JSON.stringify({ path, locked: false }) }] };
|
|
23155
24578
|
return { content: [{ type: "text", text: JSON.stringify({ path, locked: true, ...lock }) }] };
|
|
@@ -23163,8 +24586,8 @@ ${lines.join(`
|
|
|
23163
24586
|
agent_id: exports_external.string().optional().describe("Filter locks by agent")
|
|
23164
24587
|
}, async ({ agent_id }) => {
|
|
23165
24588
|
try {
|
|
23166
|
-
const { listFileLocks } = (()
|
|
23167
|
-
const locks =
|
|
24589
|
+
const { listFileLocks: listFileLocks2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
|
|
24590
|
+
const locks = listFileLocks2(agent_id);
|
|
23168
24591
|
return { content: [{ type: "text", text: JSON.stringify(locks, null, 2) }] };
|
|
23169
24592
|
} catch (e) {
|
|
23170
24593
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -23187,8 +24610,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23187
24610
|
next_steps: exports_external.array(exports_external.string()).optional().describe("Recommended next actions")
|
|
23188
24611
|
}, async ({ agent_id, project_id, summary, completed, in_progress, blockers, next_steps }) => {
|
|
23189
24612
|
try {
|
|
23190
|
-
const { createHandoff } = (()
|
|
23191
|
-
const handoff =
|
|
24613
|
+
const { createHandoff: createHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
24614
|
+
const handoff = createHandoff2({
|
|
23192
24615
|
agent_id,
|
|
23193
24616
|
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
23194
24617
|
summary,
|
|
@@ -23209,8 +24632,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23209
24632
|
project_id: exports_external.string().optional().describe("Filter by project")
|
|
23210
24633
|
}, async ({ agent_id, project_id }) => {
|
|
23211
24634
|
try {
|
|
23212
|
-
const { getLatestHandoff } = (()
|
|
23213
|
-
const handoff =
|
|
24635
|
+
const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
24636
|
+
const handoff = getLatestHandoff2(agent_id, project_id ? resolveId(project_id, "projects") : undefined);
|
|
23214
24637
|
if (!handoff)
|
|
23215
24638
|
return { content: [{ type: "text", text: "No handoffs found." }] };
|
|
23216
24639
|
const lines = [
|
|
@@ -23240,7 +24663,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23240
24663
|
created_by: exports_external.string().optional().describe("Agent ID who created this relationship")
|
|
23241
24664
|
}, async ({ source_task_id, target_task_id, relationship_type, created_by }) => {
|
|
23242
24665
|
try {
|
|
23243
|
-
const { addTaskRelationship: addTaskRelationship2 } = (()
|
|
24666
|
+
const { addTaskRelationship: addTaskRelationship2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
23244
24667
|
const rel = addTaskRelationship2({
|
|
23245
24668
|
source_task_id: resolveId(source_task_id),
|
|
23246
24669
|
target_task_id: resolveId(target_task_id),
|
|
@@ -23261,7 +24684,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23261
24684
|
relationship_type: exports_external.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
|
|
23262
24685
|
}, async ({ id, source_task_id, target_task_id, relationship_type }) => {
|
|
23263
24686
|
try {
|
|
23264
|
-
const { removeTaskRelationship: removeTaskRelationship2, removeTaskRelationshipByPair: removeTaskRelationshipByPair2 } = (()
|
|
24687
|
+
const { removeTaskRelationship: removeTaskRelationship2, removeTaskRelationshipByPair: removeTaskRelationshipByPair2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
23265
24688
|
let removed = false;
|
|
23266
24689
|
if (id) {
|
|
23267
24690
|
removed = removeTaskRelationship2(id);
|
|
@@ -23282,7 +24705,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23282
24705
|
relationship_type: exports_external.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
|
|
23283
24706
|
}, async ({ task_id, relationship_type }) => {
|
|
23284
24707
|
try {
|
|
23285
|
-
const { getTaskRelationships: getTaskRelationships2 } = (()
|
|
24708
|
+
const { getTaskRelationships: getTaskRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
23286
24709
|
const rels = getTaskRelationships2(resolveId(task_id), relationship_type);
|
|
23287
24710
|
if (rels.length === 0)
|
|
23288
24711
|
return { content: [{ type: "text", text: "No relationships found." }] };
|
|
@@ -23299,7 +24722,7 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23299
24722
|
task_id: exports_external.string().describe("Task ID to detect file relationships for")
|
|
23300
24723
|
}, async ({ task_id }) => {
|
|
23301
24724
|
try {
|
|
23302
|
-
const { autoDetectFileRelationships: autoDetectFileRelationships2 } = (()
|
|
24725
|
+
const { autoDetectFileRelationships: autoDetectFileRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
|
|
23303
24726
|
const created = autoDetectFileRelationships2(resolveId(task_id));
|
|
23304
24727
|
return { content: [{ type: "text", text: created.length > 0 ? `Created ${created.length} file relationship(s).` : "No file overlaps detected." }] };
|
|
23305
24728
|
} catch (e) {
|
|
@@ -23310,8 +24733,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23310
24733
|
if (shouldRegisterTool("sync_kg")) {
|
|
23311
24734
|
server.tool("sync_kg", "Sync all existing relationships into the knowledge graph edges table. Idempotent.", {}, async () => {
|
|
23312
24735
|
try {
|
|
23313
|
-
const { syncKgEdges } = (()
|
|
23314
|
-
const result =
|
|
24736
|
+
const { syncKgEdges: syncKgEdges2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
24737
|
+
const result = syncKgEdges2();
|
|
23315
24738
|
return { content: [{ type: "text", text: `Knowledge graph synced: ${result.synced} edge(s) processed.` }] };
|
|
23316
24739
|
} catch (e) {
|
|
23317
24740
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -23327,8 +24750,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23327
24750
|
limit: exports_external.number().optional().describe("Max results")
|
|
23328
24751
|
}, async ({ entity_id, relation_type, entity_type, direction, limit }) => {
|
|
23329
24752
|
try {
|
|
23330
|
-
const { getRelated } = (()
|
|
23331
|
-
const edges =
|
|
24753
|
+
const { getRelated: getRelated2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
24754
|
+
const edges = getRelated2(entity_id, { relation_type, entity_type, direction, limit });
|
|
23332
24755
|
if (edges.length === 0)
|
|
23333
24756
|
return { content: [{ type: "text", text: "No related entities found." }] };
|
|
23334
24757
|
const lines = edges.map((e) => `${e.source_id.slice(0, 12)}(${e.source_type}) --[${e.relation_type}]--> ${e.target_id.slice(0, 12)}(${e.target_type})`);
|
|
@@ -23347,8 +24770,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23347
24770
|
relation_types: exports_external.array(exports_external.string()).optional().describe("Filter by relation types")
|
|
23348
24771
|
}, async ({ source_id, target_id, max_depth, relation_types }) => {
|
|
23349
24772
|
try {
|
|
23350
|
-
const { findPath } = (()
|
|
23351
|
-
const paths =
|
|
24773
|
+
const { findPath: findPath2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
24774
|
+
const paths = findPath2(source_id, target_id, { max_depth, relation_types });
|
|
23352
24775
|
if (paths.length === 0)
|
|
23353
24776
|
return { content: [{ type: "text", text: "No path found." }] };
|
|
23354
24777
|
const lines = paths.map((path, i) => {
|
|
@@ -23372,8 +24795,8 @@ function registerTaskRelTools(server, ctx) {
|
|
|
23372
24795
|
relation_types: exports_external.array(exports_external.string()).optional().describe("Filter by relation types")
|
|
23373
24796
|
}, async ({ entity_id, max_depth, relation_types }) => {
|
|
23374
24797
|
try {
|
|
23375
|
-
const { getImpactAnalysis } = (()
|
|
23376
|
-
const impact =
|
|
24798
|
+
const { getImpactAnalysis: getImpactAnalysis2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
24799
|
+
const impact = getImpactAnalysis2(entity_id, { max_depth, relation_types });
|
|
23377
24800
|
if (impact.length === 0)
|
|
23378
24801
|
return { content: [{ type: "text", text: "No downstream impact detected." }] };
|
|
23379
24802
|
const byDepth = new Map;
|
|
@@ -23403,8 +24826,8 @@ Depth ${depth}:`);
|
|
|
23403
24826
|
limit: exports_external.number().optional().describe("Max results (default: 20)")
|
|
23404
24827
|
}, async ({ project_id, limit }) => {
|
|
23405
24828
|
try {
|
|
23406
|
-
const { getCriticalPath } = (()
|
|
23407
|
-
const result =
|
|
24829
|
+
const { getCriticalPath: getCriticalPath2 } = (init_kg(), __toCommonJS(exports_kg));
|
|
24830
|
+
const result = getCriticalPath2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, limit });
|
|
23408
24831
|
if (result.length === 0)
|
|
23409
24832
|
return { content: [{ type: "text", text: "No critical path data. Run sync_kg first to populate the knowledge graph." }] };
|
|
23410
24833
|
const lines = result.map((r, i) => `${i + 1}. ${r.task_id.slice(0, 8)} blocks ${r.blocking_count} task(s), max depth ${r.depth}`);
|
|
@@ -23424,13 +24847,13 @@ ${lines.join(`
|
|
|
23424
24847
|
is_lead: exports_external.coerce.boolean().optional().describe("Whether this agent is the project lead for this role")
|
|
23425
24848
|
}, async ({ project_id, agent_name, role, is_lead }) => {
|
|
23426
24849
|
try {
|
|
23427
|
-
const { setProjectAgentRole } = (()
|
|
23428
|
-
const { getAgentByName: getAgentByName2 } = (()
|
|
24850
|
+
const { setProjectAgentRole: setProjectAgentRole2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
|
|
24851
|
+
const { getAgentByName: getAgentByName2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
23429
24852
|
const agent = getAgentByName2(agent_name);
|
|
23430
24853
|
if (!agent)
|
|
23431
24854
|
return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
|
|
23432
24855
|
const pid = resolveId(project_id, "projects");
|
|
23433
|
-
const result =
|
|
24856
|
+
const result = setProjectAgentRole2(pid, agent.id, role, is_lead ?? false);
|
|
23434
24857
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23435
24858
|
} catch (e) {
|
|
23436
24859
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -23460,9 +24883,9 @@ ${lines.join(`
|
|
|
23460
24883
|
}).join(`
|
|
23461
24884
|
`);
|
|
23462
24885
|
};
|
|
23463
|
-
const { getProjectOrgChart } = (()
|
|
24886
|
+
const { getProjectOrgChart: getProjectOrgChart2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
|
|
23464
24887
|
const pid = resolveId(project_id, "projects");
|
|
23465
|
-
const tree =
|
|
24888
|
+
const tree = getProjectOrgChart2(pid, { filter_to_project });
|
|
23466
24889
|
if (format === "json") {
|
|
23467
24890
|
return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
|
|
23468
24891
|
}
|
|
@@ -23480,9 +24903,9 @@ ${lines.join(`
|
|
|
23480
24903
|
project_id: exports_external.string().describe("Project ID")
|
|
23481
24904
|
}, async ({ project_id }) => {
|
|
23482
24905
|
try {
|
|
23483
|
-
const { listProjectAgentRoles } = (()
|
|
24906
|
+
const { listProjectAgentRoles: listProjectAgentRoles2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
|
|
23484
24907
|
const pid = resolveId(project_id, "projects");
|
|
23485
|
-
const roles =
|
|
24908
|
+
const roles = listProjectAgentRoles2(pid);
|
|
23486
24909
|
return { content: [{ type: "text", text: JSON.stringify(roles, null, 2) }] };
|
|
23487
24910
|
} catch (e) {
|
|
23488
24911
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -23496,7 +24919,7 @@ ${lines.join(`
|
|
|
23496
24919
|
limit: exports_external.number().optional().describe("Max results")
|
|
23497
24920
|
}, async ({ capabilities, min_score, limit }) => {
|
|
23498
24921
|
try {
|
|
23499
|
-
const { getCapableAgents: getCapableAgents2 } = (()
|
|
24922
|
+
const { getCapableAgents: getCapableAgents2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
23500
24923
|
const results = getCapableAgents2(capabilities, { min_score, limit });
|
|
23501
24924
|
if (results.length === 0)
|
|
23502
24925
|
return { content: [{ type: "text", text: "No agents match the given capabilities." }] };
|
|
@@ -23515,8 +24938,8 @@ ${lines.join(`
|
|
|
23515
24938
|
project_id: exports_external.string().optional().describe("Filter by project")
|
|
23516
24939
|
}, async ({ stuck_minutes, confidence_threshold, project_id }) => {
|
|
23517
24940
|
try {
|
|
23518
|
-
const { patrolTasks } = (()
|
|
23519
|
-
const result =
|
|
24941
|
+
const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
|
|
24942
|
+
const result = patrolTasks2({
|
|
23520
24943
|
stuck_minutes,
|
|
23521
24944
|
confidence_threshold,
|
|
23522
24945
|
project_id: project_id ? resolveId(project_id, "projects") : undefined
|
|
@@ -23542,8 +24965,8 @@ ${lines.join(`
|
|
|
23542
24965
|
limit: exports_external.number().optional().describe("Max results (default: all)")
|
|
23543
24966
|
}, async ({ project_id, limit }) => {
|
|
23544
24967
|
try {
|
|
23545
|
-
const { getReviewQueue } = (()
|
|
23546
|
-
const tasks =
|
|
24968
|
+
const { getReviewQueue: getReviewQueue2 } = (init_patrol(), __toCommonJS(exports_patrol));
|
|
24969
|
+
const tasks = getReviewQueue2({
|
|
23547
24970
|
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
23548
24971
|
limit
|
|
23549
24972
|
});
|
|
@@ -23569,8 +24992,8 @@ ${lines.join(`
|
|
|
23569
24992
|
reviewer_id: exports_external.string().optional().describe("Agent ID of reviewer")
|
|
23570
24993
|
}, async ({ task_id, score, reviewer_id }) => {
|
|
23571
24994
|
try {
|
|
23572
|
-
const { scoreTask } = (()
|
|
23573
|
-
|
|
24995
|
+
const { scoreTask: scoreTask2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
|
|
24996
|
+
scoreTask2(resolveId(task_id), score, reviewer_id);
|
|
23574
24997
|
return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} scored: ${score}` }] };
|
|
23575
24998
|
} catch (e) {
|
|
23576
24999
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -23587,7 +25010,7 @@ ${lines.join(`
|
|
|
23587
25010
|
notes: exports_external.string().optional().describe("Notes about what was done")
|
|
23588
25011
|
}, async ({ task_id, minutes, agent_id, started_at, ended_at, notes }) => {
|
|
23589
25012
|
try {
|
|
23590
|
-
const { logTime: logTime2 } = (()
|
|
25013
|
+
const { logTime: logTime2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23591
25014
|
logTime2({ task_id: resolveId(task_id), minutes, agent_id, started_at, ended_at, notes });
|
|
23592
25015
|
return { content: [{ type: "text", text: `Logged ${minutes} min on task ${task_id.slice(0, 8)}` }] };
|
|
23593
25016
|
} catch (e) {
|
|
@@ -23602,7 +25025,7 @@ ${lines.join(`
|
|
|
23602
25025
|
since: exports_external.string().optional().describe("ISO date \u2014 only tasks completed after this date")
|
|
23603
25026
|
}, async ({ project_id, agent_id, since }) => {
|
|
23604
25027
|
try {
|
|
23605
|
-
const { getTimeReport: getTimeReport2 } = (()
|
|
25028
|
+
const { getTimeReport: getTimeReport2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23606
25029
|
const report = getTimeReport2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, agent_id, since });
|
|
23607
25030
|
if (report.length === 0)
|
|
23608
25031
|
return { content: [{ type: "text", text: "No completed tasks found." }] };
|
|
@@ -23626,7 +25049,7 @@ ${lines.join(`
|
|
|
23626
25049
|
agent_id: exports_external.string().optional().describe("Agent subscribing (defaults to context agent)")
|
|
23627
25050
|
}, async ({ task_id, agent_id }) => {
|
|
23628
25051
|
try {
|
|
23629
|
-
const { watchTask: watchTask2 } = (()
|
|
25052
|
+
const { watchTask: watchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23630
25053
|
watchTask2(resolveId(task_id), agent_id || "");
|
|
23631
25054
|
return { content: [{ type: "text", text: `Now watching task ${task_id.slice(0, 8)}` }] };
|
|
23632
25055
|
} catch (e) {
|
|
@@ -23640,7 +25063,7 @@ ${lines.join(`
|
|
|
23640
25063
|
agent_id: exports_external.string().optional().describe("Agent unsubscribing (defaults to context agent)")
|
|
23641
25064
|
}, async ({ task_id, agent_id }) => {
|
|
23642
25065
|
try {
|
|
23643
|
-
const { unwatchTask: unwatchTask2 } = (()
|
|
25066
|
+
const { unwatchTask: unwatchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23644
25067
|
unwatchTask2(resolveId(task_id), agent_id || "");
|
|
23645
25068
|
return { content: [{ type: "text", text: `Stopped watching task ${task_id.slice(0, 8)}` }] };
|
|
23646
25069
|
} catch (e) {
|
|
@@ -23653,7 +25076,7 @@ ${lines.join(`
|
|
|
23653
25076
|
task_id: exports_external.string().describe("Task ID")
|
|
23654
25077
|
}, async ({ task_id }) => {
|
|
23655
25078
|
try {
|
|
23656
|
-
const { getTaskWatchers: getTaskWatchers2 } = (()
|
|
25079
|
+
const { getTaskWatchers: getTaskWatchers2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23657
25080
|
const watchers = getTaskWatchers2(resolveId(task_id));
|
|
23658
25081
|
if (watchers.length === 0)
|
|
23659
25082
|
return { content: [{ type: "text", text: "No watchers." }] };
|
|
@@ -23672,15 +25095,15 @@ ${lines.join(`
|
|
|
23672
25095
|
agent_id: exports_external.string().optional().describe("Filter by assignee")
|
|
23673
25096
|
}, async ({ project_id, plan_id, task_list_id, since, agent_id }) => {
|
|
23674
25097
|
try {
|
|
23675
|
-
const { listTasks: listTasks3 } = (()
|
|
23676
|
-
const { patrolTasks } = (()
|
|
25098
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
25099
|
+
const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
|
|
23677
25100
|
const completed = listTasks3({ status: "completed", project_id, plan_id, task_list_id, assigned_to: agent_id, limit: 500 }, undefined);
|
|
23678
25101
|
const filtered = since ? completed.filter((t) => t.completed_at && t.completed_at >= since) : completed;
|
|
23679
25102
|
const total = filtered.length;
|
|
23680
25103
|
const lowConf = filtered.filter((t) => t.confidence != null && t.confidence < 0.7).length;
|
|
23681
25104
|
const withEstimate = filtered.filter((t) => t.estimated_minutes != null && t.actual_minutes != null);
|
|
23682
25105
|
const avgDiff = withEstimate.length > 0 ? withEstimate.reduce((acc, t) => acc + (t.actual_minutes - t.estimated_minutes), 0) / withEstimate.length : 0;
|
|
23683
|
-
const patrolResult =
|
|
25106
|
+
const patrolResult = patrolTasks2({ project_id: project_id ? resolveId(project_id, "projects") : undefined });
|
|
23684
25107
|
const stuck = patrolResult.issues.filter((i) => i.type === "stuck").length;
|
|
23685
25108
|
const lines = [
|
|
23686
25109
|
`Retro (${total} completed tasks${since ? ` since ${since}` : ""})`,
|
|
@@ -23701,7 +25124,7 @@ ${lines.join(`
|
|
|
23701
25124
|
limit: exports_external.number().optional().describe("Max results (default: 20)")
|
|
23702
25125
|
}, async ({ project_id, limit }) => {
|
|
23703
25126
|
try {
|
|
23704
|
-
const { listTasks: listTasks3 } = (()
|
|
25127
|
+
const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
23705
25128
|
const tasks = listTasks3({ status: "pending", project_id, assigned_to: "", limit: limit || 20 }, undefined);
|
|
23706
25129
|
if (tasks.length === 0)
|
|
23707
25130
|
return { content: [{ type: "text", text: "Inbox is empty." }] };
|
|
@@ -23720,8 +25143,8 @@ ${lines.join(`
|
|
|
23720
25143
|
project_id: exports_external.string().optional().describe("Filter by project")
|
|
23721
25144
|
}, async ({ agent_id, project_id }) => {
|
|
23722
25145
|
try {
|
|
23723
|
-
const { getAgentMetrics } = (()
|
|
23724
|
-
const metrics =
|
|
25146
|
+
const { getAgentMetrics: getAgentMetrics2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
|
|
25147
|
+
const metrics = getAgentMetrics2(agent_id, {
|
|
23725
25148
|
project_id: project_id ? resolveId(project_id, "projects") : undefined
|
|
23726
25149
|
});
|
|
23727
25150
|
if (!metrics)
|
|
@@ -23748,8 +25171,8 @@ ${lines.join(`
|
|
|
23748
25171
|
limit: exports_external.number().optional().describe("Max entries (default: 20)")
|
|
23749
25172
|
}, async ({ project_id, limit }) => {
|
|
23750
25173
|
try {
|
|
23751
|
-
const { getLeaderboard } = (()
|
|
23752
|
-
const entries =
|
|
25174
|
+
const { getLeaderboard: getLeaderboard2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
|
|
25175
|
+
const entries = getLeaderboard2({
|
|
23753
25176
|
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
23754
25177
|
limit
|
|
23755
25178
|
});
|
|
@@ -23999,15 +25422,373 @@ ${lines.join(`
|
|
|
23999
25422
|
}
|
|
24000
25423
|
}
|
|
24001
25424
|
|
|
25425
|
+
// src/mcp/tools/agents.ts
|
|
25426
|
+
init_agents();
|
|
25427
|
+
init_config2();
|
|
25428
|
+
init_database();
|
|
25429
|
+
function registerAgentTools(server, { shouldRegisterTool, resolveId, formatError, agentFocusMap, getAgentFocus }) {
|
|
25430
|
+
if (shouldRegisterTool("set_focus")) {
|
|
25431
|
+
server.tool("set_focus", "Focus this agent on a project. All list/search/status tools will default to this project.", {
|
|
25432
|
+
agent_id: exports_external.string().describe("Agent ID or name"),
|
|
25433
|
+
project_id: exports_external.string().optional().describe("Project to focus on. Omit to clear."),
|
|
25434
|
+
task_list_id: exports_external.string().optional().describe("Task list to focus on")
|
|
25435
|
+
}, async ({ agent_id, project_id, task_list_id }) => {
|
|
25436
|
+
try {
|
|
25437
|
+
const resolvedProject = project_id ? resolveId(project_id, "projects") : undefined;
|
|
25438
|
+
const focus = { agent_id, project_id: resolvedProject, task_list_id };
|
|
25439
|
+
agentFocusMap.set(agent_id, focus);
|
|
25440
|
+
try {
|
|
25441
|
+
const agent = getAgentByName(agent_id) || getAgent(agent_id);
|
|
25442
|
+
if (agent) {
|
|
25443
|
+
const db = getDatabase();
|
|
25444
|
+
db.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [resolvedProject || null, agent.id]);
|
|
25445
|
+
}
|
|
25446
|
+
} catch {}
|
|
25447
|
+
const projectName = resolvedProject ? ` (${resolvedProject.slice(0, 8)})` : "";
|
|
25448
|
+
return { content: [{ type: "text", text: `Focused on project${projectName}. Read tools will default to this scope. Pass explicit project_id to override.` }] };
|
|
25449
|
+
} catch (e) {
|
|
25450
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25451
|
+
}
|
|
25452
|
+
});
|
|
25453
|
+
}
|
|
25454
|
+
if (shouldRegisterTool("get_focus")) {
|
|
25455
|
+
server.tool("get_focus", "Get the current focus for an agent.", { agent_id: exports_external.string().describe("Agent ID or name") }, async ({ agent_id }) => {
|
|
25456
|
+
const focus = getAgentFocus(agent_id);
|
|
25457
|
+
if (!focus?.project_id) {
|
|
25458
|
+
return { content: [{ type: "text", text: "No focus set. Showing all projects." }] };
|
|
25459
|
+
}
|
|
25460
|
+
return { content: [{ type: "text", text: `Focused on project: ${focus.project_id}${focus.task_list_id ? `, task list: ${focus.task_list_id}` : ""}` }] };
|
|
25461
|
+
});
|
|
25462
|
+
}
|
|
25463
|
+
if (shouldRegisterTool("unfocus")) {
|
|
25464
|
+
server.tool("unfocus", "Clear focus \u2014 show all projects and tasks.", { agent_id: exports_external.string().describe("Agent ID or name") }, async ({ agent_id }) => {
|
|
25465
|
+
agentFocusMap.delete(agent_id);
|
|
25466
|
+
try {
|
|
25467
|
+
const agent = getAgentByName(agent_id) || getAgent(agent_id);
|
|
25468
|
+
if (agent) {
|
|
25469
|
+
const db = getDatabase();
|
|
25470
|
+
db.run("UPDATE agents SET active_project_id = NULL WHERE id = ?", [agent.id]);
|
|
25471
|
+
}
|
|
25472
|
+
} catch {}
|
|
25473
|
+
return { content: [{ type: "text", text: "Focus cleared. Showing all projects." }] };
|
|
25474
|
+
});
|
|
25475
|
+
}
|
|
25476
|
+
if (shouldRegisterTool("register_agent")) {
|
|
25477
|
+
server.tool("register_agent", "Register an agent. Any name is allowed \u2014 the configured pool is advisory, not enforced. Returns a conflict error if the name is held by a recently-active agent.", {
|
|
25478
|
+
name: exports_external.string().describe("Agent name \u2014 any name is allowed. Use suggest_agent_name to see pool suggestions and avoid conflicts."),
|
|
25479
|
+
description: exports_external.string().optional(),
|
|
25480
|
+
role: exports_external.string().optional(),
|
|
25481
|
+
title: exports_external.string().optional(),
|
|
25482
|
+
level: exports_external.string().optional(),
|
|
25483
|
+
permissions: exports_external.array(exports_external.string()).optional(),
|
|
25484
|
+
capabilities: exports_external.array(exports_external.string()).optional().describe("Agent capabilities/skills for task routing (e.g. ['typescript', 'testing', 'devops'])"),
|
|
25485
|
+
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."),
|
|
25486
|
+
working_dir: exports_external.string().optional().describe("Working directory of this session \u2014 used to look up the project's agent pool and identify who holds the name in a conflict"),
|
|
25487
|
+
force: exports_external.boolean().optional().describe("Force takeover of an active agent's name. Use with caution \u2014 only when you know the previous session is dead.")
|
|
25488
|
+
}, async ({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force }) => {
|
|
25489
|
+
try {
|
|
25490
|
+
const pool = getAgentPoolForProject(working_dir);
|
|
25491
|
+
const result = registerAgent({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force, pool: pool || undefined });
|
|
25492
|
+
if (isAgentConflict(result)) {
|
|
25493
|
+
const suggestLine = result.suggestions && result.suggestions.length > 0 ? `
|
|
25494
|
+
Available names: ${result.suggestions.join(", ")}` : "";
|
|
25495
|
+
const hint = `CONFLICT: ${result.message}${suggestLine}`;
|
|
25496
|
+
return {
|
|
25497
|
+
content: [{ type: "text", text: hint }],
|
|
25498
|
+
isError: true
|
|
25499
|
+
};
|
|
25500
|
+
}
|
|
25501
|
+
const agent = result;
|
|
25502
|
+
const poolLine = pool ? `
|
|
25503
|
+
Pool: [${pool.join(", ")}]` : "";
|
|
25504
|
+
return {
|
|
25505
|
+
content: [{
|
|
25506
|
+
type: "text",
|
|
25507
|
+
text: `Agent registered:
|
|
25508
|
+
ID: ${agent.id}
|
|
25509
|
+
Name: ${agent.name}${agent.description ? `
|
|
25510
|
+
Description: ${agent.description}` : ""}
|
|
25511
|
+
Session: ${agent.session_id ?? "unbound"}${poolLine}
|
|
25512
|
+
Created: ${agent.created_at}
|
|
25513
|
+
Last seen: ${agent.last_seen_at}`
|
|
25514
|
+
}]
|
|
25515
|
+
};
|
|
25516
|
+
} catch (e) {
|
|
25517
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25518
|
+
}
|
|
25519
|
+
});
|
|
25520
|
+
}
|
|
25521
|
+
if (shouldRegisterTool("suggest_agent_name")) {
|
|
25522
|
+
server.tool("suggest_agent_name", "Get available agent names for a project. Shows configured pool, active agents, and suggestions. If no pool is configured, any name is allowed.", {
|
|
25523
|
+
working_dir: exports_external.string().optional().describe("Your working directory \u2014 used to look up the project's allowed name pool from config")
|
|
25524
|
+
}, async ({ working_dir }) => {
|
|
25525
|
+
try {
|
|
25526
|
+
const pool = getAgentPoolForProject(working_dir);
|
|
25527
|
+
const cutoff = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
25528
|
+
const allActive = listAgents().filter((a) => a.last_seen_at > cutoff);
|
|
25529
|
+
if (!pool) {
|
|
25530
|
+
const lines2 = [
|
|
25531
|
+
"No agent pool configured \u2014 any name is allowed.",
|
|
25532
|
+
allActive.length > 0 ? `Active agents (avoid these names): ${allActive.map((a) => `${a.name} (seen ${Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000)}m ago)`).join(", ")}` : "No active agents.",
|
|
25533
|
+
`
|
|
25534
|
+
To restrict names, configure agent_pool or project_pools in ~/.hasna/todos/config.json`
|
|
25535
|
+
];
|
|
25536
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
25537
|
+
`) }] };
|
|
25538
|
+
}
|
|
25539
|
+
const available = getAvailableNamesFromPool(pool, getDatabase());
|
|
25540
|
+
const activeInPool = allActive.filter((a) => pool.map((n) => n.toLowerCase()).includes(a.name));
|
|
25541
|
+
const lines = [
|
|
25542
|
+
`Project pool: ${pool.join(", ")}`,
|
|
25543
|
+
`Available now (${available.length}): ${available.length > 0 ? available.join(", ") : "none \u2014 all names in use"}`,
|
|
25544
|
+
activeInPool.length > 0 ? `Active agents: ${activeInPool.map((a) => `${a.name} (seen ${Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000)}m ago)`).join(", ")}` : "Active agents: none",
|
|
25545
|
+
available.length > 0 ? `
|
|
25546
|
+
Suggested: ${available[0]}` : `
|
|
25547
|
+
No names available. Wait for an active agent to go stale (30min timeout).`
|
|
25548
|
+
];
|
|
25549
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
25550
|
+
`) }] };
|
|
25551
|
+
} catch (e) {
|
|
25552
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25553
|
+
}
|
|
25554
|
+
});
|
|
25555
|
+
}
|
|
25556
|
+
if (shouldRegisterTool("list_agents")) {
|
|
25557
|
+
server.tool("list_agents", "List all registered agents. By default shows only active agents \u2014 set include_archived to see archived ones too.", {
|
|
25558
|
+
include_archived: exports_external.boolean().optional().describe("Include archived agents in the list (default: false)")
|
|
25559
|
+
}, async ({ include_archived }) => {
|
|
25560
|
+
try {
|
|
25561
|
+
const agents = listAgents({ include_archived: include_archived ?? false });
|
|
25562
|
+
if (agents.length === 0) {
|
|
25563
|
+
return { content: [{ type: "text", text: "No agents registered." }] };
|
|
25564
|
+
}
|
|
25565
|
+
const text = agents.map((a) => {
|
|
25566
|
+
const statusTag = a.status === "archived" ? " [archived]" : "";
|
|
25567
|
+
return `${a.id} | ${a.name}${statusTag}${a.description ? ` - ${a.description}` : ""} (last seen: ${a.last_seen_at})`;
|
|
25568
|
+
}).join(`
|
|
25569
|
+
`);
|
|
25570
|
+
return { content: [{ type: "text", text: `${agents.length} agent(s):
|
|
25571
|
+
${text}` }] };
|
|
25572
|
+
} catch (e) {
|
|
25573
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25574
|
+
}
|
|
25575
|
+
});
|
|
25576
|
+
}
|
|
25577
|
+
if (shouldRegisterTool("get_agent")) {
|
|
25578
|
+
server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
|
|
25579
|
+
agent_id: exports_external.string().optional(),
|
|
25580
|
+
id: exports_external.string().optional(),
|
|
25581
|
+
name: exports_external.string().optional()
|
|
25582
|
+
}, async ({ agent_id, id, name }) => {
|
|
25583
|
+
try {
|
|
25584
|
+
const identifier = agent_id || id || name;
|
|
25585
|
+
if (!identifier) {
|
|
25586
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
25587
|
+
}
|
|
25588
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
25589
|
+
if (!agent) {
|
|
25590
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
25591
|
+
}
|
|
25592
|
+
const parts = [
|
|
25593
|
+
`ID: ${agent.id}`,
|
|
25594
|
+
`Name: ${agent.name}`
|
|
25595
|
+
];
|
|
25596
|
+
if (agent.description)
|
|
25597
|
+
parts.push(`Description: ${agent.description}`);
|
|
25598
|
+
if (Object.keys(agent.metadata).length > 0)
|
|
25599
|
+
parts.push(`Metadata: ${JSON.stringify(agent.metadata)}`);
|
|
25600
|
+
parts.push(`Created: ${agent.created_at}`);
|
|
25601
|
+
parts.push(`Last seen: ${agent.last_seen_at}`);
|
|
25602
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
25603
|
+
`) }] };
|
|
25604
|
+
} catch (e) {
|
|
25605
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25606
|
+
}
|
|
25607
|
+
});
|
|
25608
|
+
}
|
|
25609
|
+
if (shouldRegisterTool("rename_agent")) {
|
|
25610
|
+
server.tool("rename_agent", "Rename an agent. Resolve by id or current name.", {
|
|
25611
|
+
id: exports_external.string().optional(),
|
|
25612
|
+
name: exports_external.string().optional(),
|
|
25613
|
+
new_name: exports_external.string()
|
|
25614
|
+
}, async ({ id, name, new_name }) => {
|
|
25615
|
+
try {
|
|
25616
|
+
if (!id && !name) {
|
|
25617
|
+
return { content: [{ type: "text", text: "Provide either id or name." }], isError: true };
|
|
25618
|
+
}
|
|
25619
|
+
const agent = id ? getAgent(id) : getAgentByName(name);
|
|
25620
|
+
if (!agent) {
|
|
25621
|
+
return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
|
|
25622
|
+
}
|
|
25623
|
+
const oldName = agent.name;
|
|
25624
|
+
const updated = updateAgent(agent.id, { name: new_name });
|
|
25625
|
+
const db = getDatabase();
|
|
25626
|
+
const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
|
|
25627
|
+
const taskNote = tasksResult.changes > 0 ? `
|
|
25628
|
+
Updated assigned_to on ${tasksResult.changes} task(s).` : "";
|
|
25629
|
+
return {
|
|
25630
|
+
content: [{
|
|
25631
|
+
type: "text",
|
|
25632
|
+
text: `Agent renamed: ${oldName} -> ${updated.name}
|
|
25633
|
+
ID: ${updated.id}${taskNote}`
|
|
25634
|
+
}]
|
|
25635
|
+
};
|
|
25636
|
+
} catch (e) {
|
|
25637
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25638
|
+
}
|
|
25639
|
+
});
|
|
25640
|
+
}
|
|
25641
|
+
if (shouldRegisterTool("update_agent")) {
|
|
25642
|
+
server.tool("update_agent", "Update an agent's description, role, title, or other metadata. Resolve by id or name.", {
|
|
25643
|
+
agent_id: exports_external.string().optional(),
|
|
25644
|
+
id: exports_external.string().optional(),
|
|
25645
|
+
name: exports_external.string().optional(),
|
|
25646
|
+
description: exports_external.string().optional(),
|
|
25647
|
+
role: exports_external.string().optional(),
|
|
25648
|
+
title: exports_external.string().optional(),
|
|
25649
|
+
level: exports_external.string().optional(),
|
|
25650
|
+
capabilities: exports_external.array(exports_external.string()).optional(),
|
|
25651
|
+
permissions: exports_external.array(exports_external.string()).optional(),
|
|
25652
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
25653
|
+
}, async ({ agent_id, id, name, ...updates }) => {
|
|
25654
|
+
try {
|
|
25655
|
+
const identifier = agent_id || id || name;
|
|
25656
|
+
if (!identifier) {
|
|
25657
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
25658
|
+
}
|
|
25659
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
25660
|
+
if (!agent) {
|
|
25661
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
25662
|
+
}
|
|
25663
|
+
const updated = updateAgent(agent.id, updates);
|
|
25664
|
+
return { content: [{ type: "text", text: `Agent updated: ${updated.name} (${updated.id.slice(0, 8)})` }] };
|
|
25665
|
+
} catch (e) {
|
|
25666
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25667
|
+
}
|
|
25668
|
+
});
|
|
25669
|
+
}
|
|
25670
|
+
if (shouldRegisterTool("delete_agent")) {
|
|
25671
|
+
server.tool("delete_agent", "Archive an agent (soft delete). The agent is hidden from list_agents but preserved for task history. Use unarchive_agent to restore. Resolve by id or name.", {
|
|
25672
|
+
agent_id: exports_external.string().optional(),
|
|
25673
|
+
id: exports_external.string().optional(),
|
|
25674
|
+
name: exports_external.string().optional()
|
|
25675
|
+
}, async ({ agent_id, id, name }) => {
|
|
25676
|
+
try {
|
|
25677
|
+
const identifier = agent_id || id || name;
|
|
25678
|
+
if (!identifier) {
|
|
25679
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
25680
|
+
}
|
|
25681
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
25682
|
+
if (!agent) {
|
|
25683
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
25684
|
+
}
|
|
25685
|
+
const archived = archiveAgent(agent.id);
|
|
25686
|
+
return {
|
|
25687
|
+
content: [{
|
|
25688
|
+
type: "text",
|
|
25689
|
+
text: archived ? `Agent archived: ${agent.name} (${agent.id}). Use unarchive_agent to restore.` : `Failed to archive agent: ${agent.name}`
|
|
25690
|
+
}],
|
|
25691
|
+
isError: !archived
|
|
25692
|
+
};
|
|
25693
|
+
} catch (e) {
|
|
25694
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25695
|
+
}
|
|
25696
|
+
});
|
|
25697
|
+
}
|
|
25698
|
+
if (shouldRegisterTool("unarchive_agent")) {
|
|
25699
|
+
server.tool("unarchive_agent", "Restore an archived agent back to active status. Resolve by id or name.", {
|
|
25700
|
+
agent_id: exports_external.string().optional(),
|
|
25701
|
+
id: exports_external.string().optional(),
|
|
25702
|
+
name: exports_external.string().optional()
|
|
25703
|
+
}, async ({ agent_id, id, name }) => {
|
|
25704
|
+
try {
|
|
25705
|
+
const identifier = agent_id || id || name;
|
|
25706
|
+
if (!identifier) {
|
|
25707
|
+
return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
|
|
25708
|
+
}
|
|
25709
|
+
const agent = getAgent(identifier) || getAgentByName(identifier);
|
|
25710
|
+
if (!agent) {
|
|
25711
|
+
return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
|
|
25712
|
+
}
|
|
25713
|
+
if (agent.status === "active") {
|
|
25714
|
+
return { content: [{ type: "text", text: `Agent ${agent.name} is already active.` }] };
|
|
25715
|
+
}
|
|
25716
|
+
const restored = unarchiveAgent(agent.id);
|
|
25717
|
+
return {
|
|
25718
|
+
content: [{
|
|
25719
|
+
type: "text",
|
|
25720
|
+
text: restored ? `Agent restored: ${agent.name} (${agent.id}) is now active.` : `Failed to restore agent: ${agent.name}`
|
|
25721
|
+
}],
|
|
25722
|
+
isError: !restored
|
|
25723
|
+
};
|
|
25724
|
+
} catch (e) {
|
|
25725
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25726
|
+
}
|
|
25727
|
+
});
|
|
25728
|
+
}
|
|
25729
|
+
if (shouldRegisterTool("heartbeat")) {
|
|
25730
|
+
server.tool("heartbeat", "Update your last_seen_at timestamp to signal you're still active. Call periodically during long tasks to prevent being marked stale.", {
|
|
25731
|
+
agent_id: exports_external.string().describe("Your agent ID or name.")
|
|
25732
|
+
}, async ({ agent_id }) => {
|
|
25733
|
+
try {
|
|
25734
|
+
const agent = getAgent(agent_id) || getAgentByName(agent_id);
|
|
25735
|
+
if (!agent) {
|
|
25736
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
25737
|
+
}
|
|
25738
|
+
updateAgentActivity(agent.id);
|
|
25739
|
+
return {
|
|
25740
|
+
content: [{
|
|
25741
|
+
type: "text",
|
|
25742
|
+
text: `Heartbeat: ${agent.name} (${agent.id}) \u2014 last_seen_at updated to ${new Date().toISOString()}`
|
|
25743
|
+
}]
|
|
25744
|
+
};
|
|
25745
|
+
} catch (e) {
|
|
25746
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25747
|
+
}
|
|
25748
|
+
});
|
|
25749
|
+
}
|
|
25750
|
+
if (shouldRegisterTool("release_agent")) {
|
|
25751
|
+
server.tool("release_agent", "Explicitly release/logout an agent \u2014 clears session binding and makes the name immediately available. Call this when your session ends instead of waiting for the 30-minute stale timeout.", {
|
|
25752
|
+
agent_id: exports_external.string().describe("Your agent ID or name."),
|
|
25753
|
+
session_id: exports_external.string().optional().describe("Your session ID \u2014 if provided, release only succeeds if it matches (prevents other sessions from releasing your agent).")
|
|
25754
|
+
}, async ({ agent_id, session_id }) => {
|
|
25755
|
+
try {
|
|
25756
|
+
const agent = getAgent(agent_id) || getAgentByName(agent_id);
|
|
25757
|
+
if (!agent) {
|
|
25758
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
25759
|
+
}
|
|
25760
|
+
const released = releaseAgent(agent.id, session_id);
|
|
25761
|
+
if (!released) {
|
|
25762
|
+
return { content: [{ type: "text", text: `Release denied: session_id does not match agent's current session.` }], isError: true };
|
|
25763
|
+
}
|
|
25764
|
+
return {
|
|
25765
|
+
content: [{
|
|
25766
|
+
type: "text",
|
|
25767
|
+
text: `Agent released: ${agent.name} (${agent.id}) \u2014 session cleared, name is now available.`
|
|
25768
|
+
}]
|
|
25769
|
+
};
|
|
25770
|
+
} catch (e) {
|
|
25771
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25772
|
+
}
|
|
25773
|
+
});
|
|
25774
|
+
}
|
|
25775
|
+
}
|
|
25776
|
+
|
|
24002
25777
|
// src/mcp/index.ts
|
|
24003
25778
|
function getMcpVersion() {
|
|
24004
25779
|
try {
|
|
24005
|
-
|
|
24006
|
-
|
|
24007
|
-
|
|
25780
|
+
let dir = dirname3(fileURLToPath(import.meta.url));
|
|
25781
|
+
for (let i = 0;i < 4; i++) {
|
|
25782
|
+
const pkgPath = join10(dir, "package.json");
|
|
25783
|
+
if (existsSync10(pkgPath)) {
|
|
25784
|
+
return JSON.parse(readFileSync6(pkgPath, "utf-8")).version || "0.0.0";
|
|
25785
|
+
}
|
|
25786
|
+
dir = dirname3(dir);
|
|
25787
|
+
}
|
|
24008
25788
|
} catch {
|
|
24009
25789
|
return "0.0.0";
|
|
24010
25790
|
}
|
|
25791
|
+
return "0.0.0";
|
|
24011
25792
|
}
|
|
24012
25793
|
var server = new McpServer({
|
|
24013
25794
|
name: "todos",
|
|
@@ -24026,6 +25807,7 @@ var MINIMAL_TOOLS = new Set([
|
|
|
24026
25807
|
"get_next_task",
|
|
24027
25808
|
"bootstrap",
|
|
24028
25809
|
"get_tasks_changed_since",
|
|
25810
|
+
"get_health",
|
|
24029
25811
|
"heartbeat",
|
|
24030
25812
|
"release_agent"
|
|
24031
25813
|
]);
|
|
@@ -24204,6 +25986,7 @@ registerTaskMetaTools(server, toolContext);
|
|
|
24204
25986
|
registerTaskResources(server, toolContext);
|
|
24205
25987
|
registerTaskRelTools(server, toolContext);
|
|
24206
25988
|
registerCodeTools(server, toolContext);
|
|
25989
|
+
registerAgentTools(server, { ...toolContext, agentFocusMap });
|
|
24207
25990
|
registerMachineTools(server, { shouldRegisterTool, formatError });
|
|
24208
25991
|
registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
|
|
24209
25992
|
registerCloudSyncTools(server, { shouldRegisterTool, formatError });
|