@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 CHANGED
@@ -2888,6 +2888,26 @@ var init_migrations = __esm(() => {
2888
2888
  ALTER TABLE tasks ADD COLUMN current_step TEXT;
2889
2889
  ALTER TABLE tasks ADD COLUMN total_steps INTEGER;
2890
2890
  INSERT OR IGNORE INTO _migrations (id) VALUES (48);
2891
+ `,
2892
+ `
2893
+ CREATE TABLE IF NOT EXISTS cycles (
2894
+ id TEXT PRIMARY KEY,
2895
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
2896
+ number INTEGER NOT NULL,
2897
+ start_date TEXT NOT NULL,
2898
+ end_date TEXT NOT NULL,
2899
+ duration_weeks INTEGER NOT NULL DEFAULT 1,
2900
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
2901
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2902
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2903
+ );
2904
+ CREATE INDEX IF NOT EXISTS idx_cycles_project ON cycles(project_id);
2905
+ CREATE INDEX IF NOT EXISTS idx_cycles_number ON cycles(number);
2906
+ CREATE INDEX IF NOT EXISTS idx_cycles_status ON cycles(status);
2907
+ CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date);
2908
+ ALTER TABLE tasks ADD COLUMN cycle_id TEXT REFERENCES cycles(id) ON DELETE SET NULL;
2909
+ CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL;
2910
+ INSERT OR IGNORE INTO _migrations (id) VALUES (49);
2891
2911
  `
2892
2912
  ];
2893
2913
  });
@@ -4675,8 +4695,8 @@ function createTask(input, db) {
4675
4695
  let id = uuid();
4676
4696
  for (let attempt = 0;attempt < 3; attempt++) {
4677
4697
  try {
4678
- 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)
4679
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
4698
+ 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)
4699
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
4680
4700
  id,
4681
4701
  null,
4682
4702
  input.project_id || null,
@@ -4698,6 +4718,10 @@ function createTask(input, db) {
4698
4718
  timestamp,
4699
4719
  input.due_at || null,
4700
4720
  input.estimated_minutes || null,
4721
+ input.confidence ?? null,
4722
+ input.retry_count ?? 0,
4723
+ input.max_retries ?? 3,
4724
+ input.retry_after ?? null,
4701
4725
  input.requires_approval ? 1 : 0,
4702
4726
  null,
4703
4727
  null,
@@ -4722,11 +4746,11 @@ function createTask(input, db) {
4722
4746
  if (tags.length > 0) {
4723
4747
  insertTaskTags(id, tags, d);
4724
4748
  }
4725
- const task = getTask2(id, d);
4749
+ const task = getTask(id, d);
4726
4750
  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(() => {});
4727
4751
  return task;
4728
4752
  }
4729
- function getTask2(id, db) {
4753
+ function getTask(id, db) {
4730
4754
  const d = db || getDatabase();
4731
4755
  const row = d.query("SELECT * FROM tasks WHERE id = ?").get(id);
4732
4756
  if (!row)
@@ -4735,7 +4759,7 @@ function getTask2(id, db) {
4735
4759
  }
4736
4760
  function getTaskWithRelations(id, db) {
4737
4761
  const d = db || getDatabase();
4738
- const task = getTask2(id, d);
4762
+ const task = getTask(id, d);
4739
4763
  if (!task)
4740
4764
  return null;
4741
4765
  const subtaskRows = d.query("SELECT * FROM tasks WHERE parent_id = ? ORDER BY created_at").all(id);
@@ -4749,7 +4773,7 @@ function getTaskWithRelations(id, db) {
4749
4773
  WHERE td.depends_on = ?`).all(id);
4750
4774
  const blocked_by = blockedByRows.map(rowToTask);
4751
4775
  const comments = d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(id);
4752
- const parent = task.parent_id ? getTask2(task.parent_id, d) : null;
4776
+ const parent = task.parent_id ? getTask(task.parent_id, d) : null;
4753
4777
  const checklist = getChecklist(id, d);
4754
4778
  return {
4755
4779
  ...task,
@@ -4936,14 +4960,16 @@ function countTasks(filter = {}, db) {
4936
4960
  }
4937
4961
  function updateTask(id, input, db) {
4938
4962
  const d = db || getDatabase();
4939
- const task = getTask2(id, d);
4963
+ const task = getTask(id, d);
4940
4964
  if (!task)
4941
4965
  throw new TaskNotFoundError(id);
4942
4966
  if (task.version !== input.version) {
4943
4967
  throw new VersionConflictError(id, input.version, task.version);
4944
4968
  }
4969
+ const timestamp = now();
4970
+ const completionTimestamp = input.completed_at ?? timestamp;
4945
4971
  const sets = ["version = version + 1", "updated_at = ?"];
4946
- const params = [now()];
4972
+ const params = [timestamp];
4947
4973
  if (input.title !== undefined) {
4948
4974
  sets.push("title = ?");
4949
4975
  params.push(input.title);
@@ -4960,13 +4986,17 @@ function updateTask(id, input, db) {
4960
4986
  params.push(input.status);
4961
4987
  if (input.status === "completed") {
4962
4988
  sets.push("completed_at = ?");
4963
- params.push(now());
4989
+ params.push(completionTimestamp);
4964
4990
  }
4965
4991
  }
4966
4992
  if (input.priority !== undefined) {
4967
4993
  sets.push("priority = ?");
4968
4994
  params.push(input.priority);
4969
4995
  }
4996
+ if (input.project_id !== undefined) {
4997
+ sets.push("project_id = ?");
4998
+ params.push(input.project_id);
4999
+ }
4970
5000
  if (input.assigned_to !== undefined) {
4971
5001
  sets.push("assigned_to = ?");
4972
5002
  params.push(input.assigned_to);
@@ -4995,6 +5025,30 @@ function updateTask(id, input, db) {
4995
5025
  sets.push("estimated_minutes = ?");
4996
5026
  params.push(input.estimated_minutes);
4997
5027
  }
5028
+ if (input.actual_minutes !== undefined) {
5029
+ sets.push("actual_minutes = ?");
5030
+ params.push(input.actual_minutes);
5031
+ }
5032
+ if (input.completed_at !== undefined && input.status !== "completed") {
5033
+ sets.push("completed_at = ?");
5034
+ params.push(input.completed_at);
5035
+ }
5036
+ if (input.confidence !== undefined) {
5037
+ sets.push("confidence = ?");
5038
+ params.push(input.confidence);
5039
+ }
5040
+ if (input.retry_count !== undefined) {
5041
+ sets.push("retry_count = ?");
5042
+ params.push(input.retry_count);
5043
+ }
5044
+ if (input.max_retries !== undefined) {
5045
+ sets.push("max_retries = ?");
5046
+ params.push(input.max_retries);
5047
+ }
5048
+ if (input.retry_after !== undefined) {
5049
+ sets.push("retry_after = ?");
5050
+ params.push(input.retry_after);
5051
+ }
4998
5052
  if (input.requires_approval !== undefined) {
4999
5053
  sets.push("requires_approval = ?");
5000
5054
  params.push(input.requires_approval ? 1 : 0);
@@ -5016,7 +5070,7 @@ function updateTask(id, input, db) {
5016
5070
  params.push(id, input.version);
5017
5071
  const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
5018
5072
  if (result.changes === 0) {
5019
- const current = getTask2(id, d);
5073
+ const current = getTask(id, d);
5020
5074
  throw new VersionConflictError(id, input.version, current?.version ?? -1);
5021
5075
  }
5022
5076
  if (input.tags !== undefined) {
@@ -5045,11 +5099,16 @@ function updateTask(id, input, db) {
5045
5099
  tags: input.tags ?? task.tags,
5046
5100
  metadata: input.metadata ?? task.metadata,
5047
5101
  version: task.version + 1,
5048
- updated_at: now(),
5049
- completed_at: input.status === "completed" ? now() : task.completed_at,
5102
+ updated_at: timestamp,
5103
+ completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : task.completed_at,
5104
+ actual_minutes: input.actual_minutes ?? task.actual_minutes,
5105
+ confidence: input.confidence !== undefined ? input.confidence : task.confidence,
5106
+ retry_count: input.retry_count ?? task.retry_count,
5107
+ max_retries: input.max_retries ?? task.max_retries,
5108
+ retry_after: input.retry_after !== undefined ? input.retry_after : task.retry_after,
5050
5109
  requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
5051
5110
  approved_by: input.approved_by ?? task.approved_by,
5052
- approved_at: input.approved_by ? now() : task.approved_at
5111
+ approved_at: input.approved_by ? timestamp : task.approved_at
5053
5112
  };
5054
5113
  }
5055
5114
  function deleteTask(id, db) {
@@ -5622,9 +5681,9 @@ var init_templates = __esm(() => {
5622
5681
  // src/db/task-graph.ts
5623
5682
  function addDependency(taskId, dependsOn, db) {
5624
5683
  const d = db || getDatabase();
5625
- if (!getTask2(taskId, d))
5684
+ if (!getTask(taskId, d))
5626
5685
  throw new TaskNotFoundError(taskId);
5627
- if (!getTask2(dependsOn, d))
5686
+ if (!getTask(dependsOn, d))
5628
5687
  throw new TaskNotFoundError(dependsOn);
5629
5688
  if (wouldCreateCycle(taskId, dependsOn, d)) {
5630
5689
  throw new DependencyCycleError(taskId, dependsOn);
@@ -5646,7 +5705,7 @@ function getTaskDependents(taskId, db) {
5646
5705
  }
5647
5706
  function cloneTask(taskId, overrides, db) {
5648
5707
  const d = db || getDatabase();
5649
- const source = getTask2(taskId, d);
5708
+ const source = getTask(taskId, d);
5650
5709
  if (!source)
5651
5710
  throw new TaskNotFoundError(taskId);
5652
5711
  const input = {
@@ -5669,13 +5728,13 @@ function cloneTask(taskId, overrides, db) {
5669
5728
  }
5670
5729
  function getTaskGraph(taskId, direction = "both", db) {
5671
5730
  const d = db || getDatabase();
5672
- const task = getTask2(taskId, d);
5731
+ const task = getTask(taskId, d);
5673
5732
  if (!task)
5674
5733
  throw new TaskNotFoundError(taskId);
5675
5734
  function toNode(t) {
5676
5735
  const deps = getTaskDependencies(t.id, d);
5677
5736
  const hasUnfinishedDeps = deps.some((dep) => {
5678
- const depTask = getTask2(dep.depends_on, d);
5737
+ const depTask = getTask(dep.depends_on, d);
5679
5738
  return depTask && depTask.status !== "completed";
5680
5739
  });
5681
5740
  return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
@@ -5686,7 +5745,7 @@ function getTaskGraph(taskId, direction = "both", db) {
5686
5745
  visited.add(id);
5687
5746
  const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
5688
5747
  return deps.map((dep) => {
5689
- const depTask = getTask2(dep.depends_on, d);
5748
+ const depTask = getTask(dep.depends_on, d);
5690
5749
  if (!depTask)
5691
5750
  return null;
5692
5751
  return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
@@ -5698,7 +5757,7 @@ function getTaskGraph(taskId, direction = "both", db) {
5698
5757
  visited.add(id);
5699
5758
  const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
5700
5759
  return dependents.map((dep) => {
5701
- const depTask = getTask2(dep.task_id, d);
5760
+ const depTask = getTask(dep.task_id, d);
5702
5761
  if (!depTask)
5703
5762
  return null;
5704
5763
  return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
@@ -5711,7 +5770,7 @@ function getTaskGraph(taskId, direction = "both", db) {
5711
5770
  }
5712
5771
  function moveTask(taskId, target, db) {
5713
5772
  const d = db || getDatabase();
5714
- const task = getTask2(taskId, d);
5773
+ const task = getTask(taskId, d);
5715
5774
  if (!task)
5716
5775
  throw new TaskNotFoundError(taskId);
5717
5776
  const sets = ["updated_at = ?", "version = version + 1"];
@@ -5730,7 +5789,7 @@ function moveTask(taskId, target, db) {
5730
5789
  }
5731
5790
  params.push(taskId);
5732
5791
  d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
5733
- return getTask2(taskId, d);
5792
+ return getTask(taskId, d);
5734
5793
  }
5735
5794
  function wouldCreateCycle(taskId, dependsOn, db) {
5736
5795
  const visited = new Set;
@@ -5763,7 +5822,7 @@ function getBlockingDeps(id, db) {
5763
5822
  return [];
5764
5823
  const blocking = [];
5765
5824
  for (const dep of deps) {
5766
- const task = getTask2(dep.depends_on, d);
5825
+ const task = getTask(dep.depends_on, d);
5767
5826
  if (task && task.status !== "completed")
5768
5827
  blocking.push(task);
5769
5828
  }
@@ -5771,7 +5830,7 @@ function getBlockingDeps(id, db) {
5771
5830
  }
5772
5831
  function startTask(id, agentId, db) {
5773
5832
  const d = db || getDatabase();
5774
- const task = getTask2(id, d);
5833
+ const task = getTask(id, d);
5775
5834
  if (!task)
5776
5835
  throw new TaskNotFoundError(id);
5777
5836
  const blocking = getBlockingDeps(id, d);
@@ -5794,7 +5853,7 @@ function startTask(id, agentId, db) {
5794
5853
  }
5795
5854
  function completeTask(id, agentId, db, options) {
5796
5855
  const d = db || getDatabase();
5797
- const task = getTask2(id, d);
5856
+ const task = getTask(id, d);
5798
5857
  if (!task)
5799
5858
  throw new TaskNotFoundError(id);
5800
5859
  if (agentId && task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
@@ -5810,14 +5869,14 @@ function completeTask(id, agentId, db, options) {
5810
5869
  completionMeta._completion = { confidence: options.confidence };
5811
5870
  }
5812
5871
  const hasMeta = Object.keys(completionMeta).length > 0;
5813
- const timestamp = now();
5872
+ const timestamp = options?.completed_at || now();
5814
5873
  const confidence = options?.confidence !== undefined ? options.confidence : null;
5815
5874
  const tx = d.transaction(() => {
5816
5875
  if (hasMeta) {
5817
5876
  const meta2 = { ...task.metadata, ...completionMeta };
5818
5877
  const metaResult = d.run("UPDATE tasks SET metadata = ?, version = version + 1, updated_at = ? WHERE id = ? AND version = ?", [JSON.stringify(meta2), timestamp, id, task.version]);
5819
5878
  if (metaResult.changes === 0) {
5820
- const current = getTask2(id, d);
5879
+ const current = getTask(id, d);
5821
5880
  throw new VersionConflictError(id, task.version, current?.version ?? -1);
5822
5881
  }
5823
5882
  }
@@ -5874,7 +5933,7 @@ function completeTask(id, agentId, db, options) {
5874
5933
  }
5875
5934
  function lockTask(id, agentId, db) {
5876
5935
  const d = db || getDatabase();
5877
- const task = getTask2(id, d);
5936
+ const task = getTask(id, d);
5878
5937
  if (!task)
5879
5938
  throw new TaskNotFoundError(id);
5880
5939
  if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
@@ -5885,7 +5944,7 @@ function lockTask(id, agentId, db) {
5885
5944
  const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
5886
5945
  WHERE id = ? AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp, timestamp, id, agentId, cutoff]);
5887
5946
  if (result.changes === 0) {
5888
- const current = getTask2(id, d);
5947
+ const current = getTask(id, d);
5889
5948
  if (!current)
5890
5949
  throw new TaskNotFoundError(id);
5891
5950
  if (current.locked_by && !isLockExpired(current.locked_at)) {
@@ -5901,7 +5960,7 @@ function lockTask(id, agentId, db) {
5901
5960
  }
5902
5961
  function unlockTask(id, agentId, db) {
5903
5962
  const d = db || getDatabase();
5904
- const task = getTask2(id, d);
5963
+ const task = getTask(id, d);
5905
5964
  if (!task)
5906
5965
  throw new TaskNotFoundError(id);
5907
5966
  if (agentId && task.locked_by && task.locked_by !== agentId) {
@@ -6002,7 +6061,7 @@ function getTasksChangedSince(since, filters, db) {
6002
6061
  }
6003
6062
  function failTask(id, agentId, reason, options, db) {
6004
6063
  const d = db || getDatabase();
6005
- const task = getTask2(id, d);
6064
+ const task = getTask(id, d);
6006
6065
  if (!task)
6007
6066
  throw new TaskNotFoundError(id);
6008
6067
  const meta = {
@@ -6201,7 +6260,7 @@ function getStatus(filters, agentId, options, db) {
6201
6260
  }
6202
6261
  function decomposeTasks(parentId, subtasks, options, db) {
6203
6262
  const d = db || getDatabase();
6204
- const parent = getTask2(parentId, d);
6263
+ const parent = getTask(parentId, d);
6205
6264
  if (!parent)
6206
6265
  throw new TaskNotFoundError(parentId);
6207
6266
  const created = [];
@@ -6232,7 +6291,7 @@ function decomposeTasks(parentId, subtasks, options, db) {
6232
6291
  function setTaskStatus(id, status, _agentId, db) {
6233
6292
  const d = db || getDatabase();
6234
6293
  for (let attempt = 0;attempt < 3; attempt++) {
6235
- const task = getTask2(id, d);
6294
+ const task = getTask(id, d);
6236
6295
  if (!task)
6237
6296
  throw new TaskNotFoundError(id);
6238
6297
  if (task.status === status)
@@ -6250,7 +6309,7 @@ function setTaskStatus(id, status, _agentId, db) {
6250
6309
  function setTaskPriority(id, priority, _agentId, db) {
6251
6310
  const d = db || getDatabase();
6252
6311
  for (let attempt = 0;attempt < 3; attempt++) {
6253
- const task = getTask2(id, d);
6312
+ const task = getTask(id, d);
6254
6313
  if (!task)
6255
6314
  throw new TaskNotFoundError(id);
6256
6315
  if (task.priority === priority)
@@ -6358,7 +6417,7 @@ function bulkUpdateTasks(taskIds, updates, db) {
6358
6417
  const tx = d.transaction(() => {
6359
6418
  for (const id of taskIds) {
6360
6419
  try {
6361
- const task = getTask2(id, d);
6420
+ const task = getTask(id, d);
6362
6421
  if (!task) {
6363
6422
  failed.push({ id, error: "Task not found" });
6364
6423
  continue;
@@ -6373,6 +6432,30 @@ function bulkUpdateTasks(taskIds, updates, db) {
6373
6432
  tx();
6374
6433
  return { updated, failed };
6375
6434
  }
6435
+ function bulkDeleteTasks(taskIds, force = false, db) {
6436
+ const d = db || getDatabase();
6437
+ let deleted = 0;
6438
+ let skipped = 0;
6439
+ const failed = [];
6440
+ const tx = d.transaction(() => {
6441
+ for (const id of taskIds) {
6442
+ try {
6443
+ const childCount = d.query("SELECT COUNT(*) as count FROM tasks WHERE parent_id = ?").get(id);
6444
+ if (!force && childCount.count > 0) {
6445
+ skipped++;
6446
+ continue;
6447
+ }
6448
+ const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
6449
+ if (result.changes > 0)
6450
+ deleted++;
6451
+ } catch (e) {
6452
+ failed.push({ id, error: e instanceof Error ? e.message : String(e) });
6453
+ }
6454
+ }
6455
+ });
6456
+ tx();
6457
+ return { deleted, skipped, failed };
6458
+ }
6376
6459
  function archiveTasks(options, db) {
6377
6460
  const d = db || getDatabase();
6378
6461
  const conditions = ["archived_at IS NULL"];
@@ -6397,10 +6480,33 @@ function archiveTasks(options, db) {
6397
6480
  const result = d.run(`UPDATE tasks SET archived_at = ? WHERE ${conditions.join(" AND ")}`, [ts, ...params]);
6398
6481
  return { archived: result.changes };
6399
6482
  }
6483
+ function archiveCompletedTasks(days = 7, projectId, db) {
6484
+ return archiveTasks({
6485
+ project_id: projectId,
6486
+ older_than_days: days,
6487
+ status: ["completed"]
6488
+ }, db).archived;
6489
+ }
6490
+ function getArchivedTasks(opts = {}, db) {
6491
+ const d = db || getDatabase();
6492
+ const conditions = ["archived_at IS NOT NULL"];
6493
+ const params = [];
6494
+ if (opts.project_id) {
6495
+ conditions.push("project_id = ?");
6496
+ params.push(opts.project_id);
6497
+ }
6498
+ let limitClause = "";
6499
+ if (opts.limit) {
6500
+ limitClause = " LIMIT ?";
6501
+ params.push(opts.limit);
6502
+ }
6503
+ const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY archived_at DESC${limitClause}`).all(...params);
6504
+ return rows.map(rowToTask);
6505
+ }
6400
6506
  function unarchiveTask(id, db) {
6401
6507
  const d = db || getDatabase();
6402
6508
  d.run("UPDATE tasks SET archived_at = NULL WHERE id = ?", [id]);
6403
- return getTask2(id, d);
6509
+ return getTask(id, d);
6404
6510
  }
6405
6511
  function getOverdueTasks(projectId, db) {
6406
6512
  const d = db || getDatabase();
@@ -6415,6 +6521,80 @@ function getOverdueTasks(projectId, db) {
6415
6521
  const rows = d.query(query).all(...params);
6416
6522
  return rows.map(rowToTask);
6417
6523
  }
6524
+ function notifyUpcomingDeadlines(opts = {}, db) {
6525
+ const d = db || getDatabase();
6526
+ const hours = opts.hours ?? 24;
6527
+ const start = new Date().toISOString();
6528
+ const end = new Date(Date.now() + hours * 60 * 60 * 1000).toISOString();
6529
+ const conditions = [
6530
+ "archived_at IS NULL",
6531
+ "due_at IS NOT NULL",
6532
+ "due_at >= ?",
6533
+ "due_at <= ?",
6534
+ "status NOT IN ('completed', 'cancelled', 'failed')"
6535
+ ];
6536
+ const params = [start, end];
6537
+ if (opts.project_id) {
6538
+ conditions.push("project_id = ?");
6539
+ params.push(opts.project_id);
6540
+ }
6541
+ if (opts.agent_id) {
6542
+ conditions.push("assigned_to = ?");
6543
+ params.push(opts.agent_id);
6544
+ }
6545
+ const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC`).all(...params);
6546
+ return rows.map(rowToTask);
6547
+ }
6548
+ function getBlockedTasks(projectId, db) {
6549
+ const d = db || getDatabase();
6550
+ const params = [];
6551
+ let projectClause = "";
6552
+ if (projectId) {
6553
+ projectClause = "AND t.project_id = ?";
6554
+ params.push(projectId);
6555
+ }
6556
+ const rows = d.query(`
6557
+ SELECT t.*, GROUP_CONCAT(dep.id) AS blocked_by_ids
6558
+ FROM tasks t
6559
+ JOIN task_dependencies td ON td.task_id = t.id
6560
+ JOIN tasks dep ON dep.id = td.depends_on
6561
+ WHERE t.archived_at IS NULL
6562
+ AND t.status NOT IN ('completed', 'cancelled', 'failed')
6563
+ AND dep.status NOT IN ('completed', 'cancelled')
6564
+ ${projectClause}
6565
+ GROUP BY t.id
6566
+ ORDER BY t.priority DESC, t.created_at DESC
6567
+ `).all(...params);
6568
+ return rows.map((row) => ({
6569
+ ...rowToTask(row),
6570
+ blocked_by: row.blocked_by_ids ? row.blocked_by_ids.split(",") : []
6571
+ }));
6572
+ }
6573
+ function getBlockingTasks(projectId, db) {
6574
+ const d = db || getDatabase();
6575
+ const params = [];
6576
+ let projectClause = "";
6577
+ if (projectId) {
6578
+ projectClause = "AND blocker.project_id = ?";
6579
+ params.push(projectId);
6580
+ }
6581
+ const rows = d.query(`
6582
+ SELECT blocker.*, COUNT(DISTINCT blocked.id) AS blocking_count
6583
+ FROM tasks blocker
6584
+ JOIN task_dependencies td ON td.depends_on = blocker.id
6585
+ JOIN tasks blocked ON blocked.id = td.task_id
6586
+ WHERE blocker.archived_at IS NULL
6587
+ AND blocker.status NOT IN ('completed', 'cancelled', 'failed')
6588
+ AND blocked.status NOT IN ('completed', 'cancelled', 'failed')
6589
+ ${projectClause}
6590
+ GROUP BY blocker.id
6591
+ ORDER BY blocking_count DESC, blocker.created_at DESC
6592
+ `).all(...params);
6593
+ return rows.map((row) => ({
6594
+ ...rowToTask(row),
6595
+ blocking_count: row.blocking_count
6596
+ }));
6597
+ }
6418
6598
  function logTime(input, db) {
6419
6599
  const d = db || getDatabase();
6420
6600
  const id = uuid();
@@ -6495,6 +6675,7 @@ __export(exports_tasks, {
6495
6675
  removeDependency: () => removeDependency,
6496
6676
  redistributeStaleTasks: () => redistributeStaleTasks,
6497
6677
  notifyWatchers: () => notifyWatchers,
6678
+ notifyUpcomingDeadlines: () => notifyUpcomingDeadlines,
6498
6679
  moveTask: () => moveTask,
6499
6680
  logTime: () => logTime,
6500
6681
  logCost: () => logCost,
@@ -6510,12 +6691,15 @@ __export(exports_tasks, {
6510
6691
  getTaskGraph: () => getTaskGraph,
6511
6692
  getTaskDependents: () => getTaskDependents,
6512
6693
  getTaskDependencies: () => getTaskDependencies,
6513
- getTask: () => getTask2,
6694
+ getTask: () => getTask,
6514
6695
  getStatus: () => getStatus,
6515
6696
  getStaleTasks: () => getStaleTasks,
6516
6697
  getOverdueTasks: () => getOverdueTasks,
6517
6698
  getNextTask: () => getNextTask,
6699
+ getBlockingTasks: () => getBlockingTasks,
6518
6700
  getBlockingDeps: () => getBlockingDeps,
6701
+ getBlockedTasks: () => getBlockedTasks,
6702
+ getArchivedTasks: () => getArchivedTasks,
6519
6703
  getActiveWork: () => getActiveWork,
6520
6704
  failTask: () => failTask,
6521
6705
  deleteTask: () => deleteTask,
@@ -6527,8 +6711,10 @@ __export(exports_tasks, {
6527
6711
  claimOrSteal: () => claimOrSteal,
6528
6712
  claimNextTask: () => claimNextTask,
6529
6713
  bulkUpdateTasks: () => bulkUpdateTasks,
6714
+ bulkDeleteTasks: () => bulkDeleteTasks,
6530
6715
  bulkCreateTasks: () => bulkCreateTasks,
6531
6716
  archiveTasks: () => archiveTasks,
6717
+ archiveCompletedTasks: () => archiveCompletedTasks,
6532
6718
  addDependency: () => addDependency
6533
6719
  });
6534
6720
  var init_tasks = __esm(() => {
@@ -6934,9 +7120,18 @@ var init_builtin_templates = __esm(() => {
6934
7120
  });
6935
7121
 
6936
7122
  // src/db/comments.ts
7123
+ var exports_comments = {};
7124
+ __export(exports_comments, {
7125
+ updateComment: () => updateComment,
7126
+ logProgress: () => logProgress,
7127
+ listComments: () => listComments,
7128
+ getComment: () => getComment,
7129
+ deleteComment: () => deleteComment,
7130
+ addComment: () => addComment
7131
+ });
6937
7132
  function addComment(input, db) {
6938
7133
  const d = db || getDatabase();
6939
- if (!getTask2(input.task_id, d)) {
7134
+ if (!getTask(input.task_id, d)) {
6940
7135
  throw new TaskNotFoundError(input.task_id);
6941
7136
  }
6942
7137
  const id = uuid();
@@ -6965,6 +7160,15 @@ function listComments(taskId, db) {
6965
7160
  const d = db || getDatabase();
6966
7161
  return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
6967
7162
  }
7163
+ function updateComment(id, input, db) {
7164
+ const d = db || getDatabase();
7165
+ d.run("UPDATE task_comments SET content = ? WHERE id = ?", [input.content, id]);
7166
+ const comment = getComment(id, d);
7167
+ if (!comment) {
7168
+ throw new Error(`Comment not found: ${id}`);
7169
+ }
7170
+ return comment;
7171
+ }
6968
7172
  function deleteComment(id, db) {
6969
7173
  const d = db || getDatabase();
6970
7174
  const result = d.run("DELETE FROM task_comments WHERE id = ?", [id]);
@@ -6977,6 +7181,10 @@ var init_comments = __esm(() => {
6977
7181
  });
6978
7182
 
6979
7183
  // src/lib/search.ts
7184
+ var exports_search = {};
7185
+ __export(exports_search, {
7186
+ searchTasks: () => searchTasks
7187
+ });
6980
7188
  function rowToTask2(row) {
6981
7189
  return {
6982
7190
  ...row,
@@ -7994,7 +8202,7 @@ function handleTasksContext(_req, url, _ctx, json2, taskToSummary2) {
7994
8202
  return new Response(text, { headers: { "Content-Type": "text/plain" } });
7995
8203
  }
7996
8204
  function handleTaskAttachments(id, _ctx, json2) {
7997
- const task = getTask2(id);
8205
+ const task = getTask(id);
7998
8206
  if (!task)
7999
8207
  return json2({ error: "Task not found" }, 404);
8000
8208
  const evidence = task.metadata?._evidence || {};
@@ -8002,7 +8210,7 @@ function handleTaskAttachments(id, _ctx, json2) {
8002
8210
  return json2({ task_id: id, short_id: task.short_id, attachment_ids: attachmentIds, count: attachmentIds.length, files_changed: evidence.files_changed, commit_hash: evidence.commit_hash, notes: evidence.notes });
8003
8211
  }
8004
8212
  async function handleTaskProgress(id, req, method, _ctx, json2) {
8005
- const task = getTask2(id);
8213
+ const task = getTask(id);
8006
8214
  if (!task)
8007
8215
  return json2({ error: "Task not found" }, 404);
8008
8216
  if (method === "GET") {
@@ -8025,7 +8233,7 @@ async function handleTaskProgress(id, req, method, _ctx, json2) {
8025
8233
  return null;
8026
8234
  }
8027
8235
  function handleGetTask(id, _ctx, json2, taskToSummary2) {
8028
- const task = getTask2(id);
8236
+ const task = getTask(id);
8029
8237
  if (!task)
8030
8238
  return json2({ error: "Task not found" }, 404);
8031
8239
  return json2(taskToSummary2(task));
@@ -8033,10 +8241,10 @@ function handleGetTask(id, _ctx, json2, taskToSummary2) {
8033
8241
  async function handlePatchTask(id, req, _ctx, json2, taskToSummary2) {
8034
8242
  try {
8035
8243
  const body = await req.json();
8036
- const task = getTask2(id);
8244
+ const task = getTask(id);
8037
8245
  if (!task)
8038
8246
  return json2({ error: "Task not found" }, 404);
8039
- const ALLOWED = new Set(["title", "description", "status", "priority", "assigned_to", "plan_id", "task_list_id", "tags", "metadata", "due_at", "estimated_minutes", "task_type"]);
8247
+ const ALLOWED = new Set(["title", "description", "status", "priority", "assigned_to", "plan_id", "task_list_id", "tags", "metadata", "due_at", "estimated_minutes", "actual_minutes", "confidence", "retry_count", "max_retries", "retry_after", "task_type"]);
8040
8248
  const safeBody = {};
8041
8249
  for (const [key, value] of Object.entries(body)) {
8042
8250
  if (ALLOWED.has(key))
@@ -10199,6 +10407,77 @@ var init_Dashboard = __esm(() => {
10199
10407
  init_audit();
10200
10408
  });
10201
10409
 
10410
+ // src/db/handoffs.ts
10411
+ var exports_handoffs = {};
10412
+ __export(exports_handoffs, {
10413
+ listHandoffs: () => listHandoffs,
10414
+ getLatestHandoff: () => getLatestHandoff,
10415
+ createHandoff: () => createHandoff
10416
+ });
10417
+ function createHandoff(input, db) {
10418
+ const d = db || getDatabase();
10419
+ const id = uuid();
10420
+ const timestamp = now();
10421
+ d.run(`INSERT INTO handoffs (id, agent_id, project_id, summary, completed, in_progress, blockers, next_steps, created_at)
10422
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10423
+ id,
10424
+ input.agent_id || null,
10425
+ input.project_id || null,
10426
+ input.summary,
10427
+ input.completed ? JSON.stringify(input.completed) : null,
10428
+ input.in_progress ? JSON.stringify(input.in_progress) : null,
10429
+ input.blockers ? JSON.stringify(input.blockers) : null,
10430
+ input.next_steps ? JSON.stringify(input.next_steps) : null,
10431
+ timestamp
10432
+ ]);
10433
+ return {
10434
+ id,
10435
+ agent_id: input.agent_id || null,
10436
+ project_id: input.project_id || null,
10437
+ summary: input.summary,
10438
+ completed: input.completed || null,
10439
+ in_progress: input.in_progress || null,
10440
+ blockers: input.blockers || null,
10441
+ next_steps: input.next_steps || null,
10442
+ created_at: timestamp
10443
+ };
10444
+ }
10445
+ function rowToHandoff(row) {
10446
+ return {
10447
+ ...row,
10448
+ completed: row.completed ? JSON.parse(row.completed) : null,
10449
+ in_progress: row.in_progress ? JSON.parse(row.in_progress) : null,
10450
+ blockers: row.blockers ? JSON.parse(row.blockers) : null,
10451
+ next_steps: row.next_steps ? JSON.parse(row.next_steps) : null
10452
+ };
10453
+ }
10454
+ function listHandoffs(projectId, limit = 10, db) {
10455
+ const d = db || getDatabase();
10456
+ if (projectId) {
10457
+ return d.query("SELECT * FROM handoffs WHERE project_id = ? ORDER BY rowid DESC LIMIT ?").all(projectId, limit).map(rowToHandoff);
10458
+ }
10459
+ return d.query("SELECT * FROM handoffs ORDER BY rowid DESC LIMIT ?").all(limit).map(rowToHandoff);
10460
+ }
10461
+ function getLatestHandoff(agentId, projectId, db) {
10462
+ const d = db || getDatabase();
10463
+ let query = "SELECT * FROM handoffs WHERE 1=1";
10464
+ const params = [];
10465
+ if (agentId) {
10466
+ query += " AND agent_id = ?";
10467
+ params.push(agentId);
10468
+ }
10469
+ if (projectId) {
10470
+ query += " AND project_id = ?";
10471
+ params.push(projectId);
10472
+ }
10473
+ query += " ORDER BY rowid DESC LIMIT 1";
10474
+ const row = d.query(query).get(...params);
10475
+ return row ? rowToHandoff(row) : null;
10476
+ }
10477
+ var init_handoffs = __esm(() => {
10478
+ init_database();
10479
+ });
10480
+
10202
10481
  // src/lib/auto-assign.ts
10203
10482
  var exports_auto_assign = {};
10204
10483
  __export(exports_auto_assign, {
@@ -10289,7 +10568,7 @@ async function callCerebras(prompt, apiKey) {
10289
10568
  }
10290
10569
  async function autoAssignTask(taskId, db) {
10291
10570
  const d = db || getDatabase();
10292
- const task = getTask2(taskId, d);
10571
+ const task = getTask(taskId, d);
10293
10572
  if (!task)
10294
10573
  throw new Error(`Task ${taskId} not found`);
10295
10574
  const agents = listAgents(d).filter((a) => a.status === "active");
@@ -25736,8 +26015,7 @@ var init_cloud = __esm(() => {
25736
26015
  });
25737
26016
 
25738
26017
  // src/lib/logger.ts
25739
- import { LogsClient } from "@hasna/logs";
25740
- function getLogger() {
26018
+ async function getLogger() {
25741
26019
  if (client)
25742
26020
  return client;
25743
26021
  const url = process.env.LOGS_URL;
@@ -25745,14 +26023,15 @@ function getLogger() {
25745
26023
  const apiKey = process.env.LOGS_API_KEY;
25746
26024
  if (!projectId)
25747
26025
  return null;
26026
+ const { LogsClient } = await import("@hasna/logs");
25748
26027
  client = new LogsClient({ url, projectId, apiKey });
25749
26028
  return client;
25750
26029
  }
25751
26030
  async function logError(message, opts) {
25752
- const logger = getLogger();
25753
- if (!logger)
25754
- return;
25755
26031
  try {
26032
+ const logger = await getLogger();
26033
+ if (!logger)
26034
+ return;
25756
26035
  await logger.push({
25757
26036
  level: "error",
25758
26037
  message,
@@ -25765,7 +26044,6 @@ async function logError(message, opts) {
25765
26044
  } catch {}
25766
26045
  }
25767
26046
  var client = null;
25768
- var init_logger = () => {};
25769
26047
 
25770
26048
  // src/db/dispatches.ts
25771
26049
  var exports_dispatches = {};
@@ -26216,12 +26494,21 @@ var init_dispatch2 = __esm(() => {
26216
26494
  // src/mcp/tools/task-crud.ts
26217
26495
  function registerTaskCrudTools(server, ctx) {
26218
26496
  const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
26497
+ function versionFor(taskId, version) {
26498
+ const current = getTask(taskId);
26499
+ if (!current)
26500
+ throw new TaskNotFoundError(taskId);
26501
+ if (version !== undefined && current.version !== version) {
26502
+ throw new VersionConflictError(taskId, version, current.version);
26503
+ }
26504
+ return current.version;
26505
+ }
26219
26506
  if (shouldRegisterTool("create_task")) {
26220
26507
  server.tool("create_task", "Create a new task in a project. Pass short_id=null to auto-generate.", {
26221
26508
  title: exports_external2.string().describe("Task title"),
26222
26509
  description: exports_external2.string().optional().describe("Task description (markdown)"),
26223
- status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Initial status (default: pending)"),
26224
- priority: exports_external2.enum(["low", "medium", "high", "urgent"]).optional().describe("Priority (default: medium)"),
26510
+ status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional().describe("Initial status (default: pending)"),
26511
+ priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional().describe("Priority (default: medium)"),
26225
26512
  project_id: exports_external2.string().optional().describe("Project ID"),
26226
26513
  task_list_id: exports_external2.string().optional().describe("Task list ID"),
26227
26514
  assigned_to: exports_external2.string().optional().describe("Agent ID or name to assign to"),
@@ -26251,9 +26538,9 @@ function registerTaskCrudTools(server, ctx) {
26251
26538
  if (confidence !== undefined)
26252
26539
  resolved.confidence = confidence;
26253
26540
  if (retry_count !== undefined)
26254
- resolved.retry_count = retry_count;
26541
+ resolved.max_retries = retry_count;
26255
26542
  if (deadline)
26256
- resolved.deadline = deadline;
26543
+ resolved.due_at = deadline;
26257
26544
  const task = createTask(resolved);
26258
26545
  return { content: [{ type: "text", text: formatTask(task) }] };
26259
26546
  } catch (e) {
@@ -26263,8 +26550,8 @@ function registerTaskCrudTools(server, ctx) {
26263
26550
  }
26264
26551
  if (shouldRegisterTool("list_tasks")) {
26265
26552
  server.tool("list_tasks", "List tasks with optional filters. Pass empty arrays for multi-value filters (e.g. status=[] shows all).", {
26266
- status: exports_external2.union([exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]), exports_external2.array(exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]))]).optional().describe("Filter by status"),
26267
- priority: exports_external2.union([exports_external2.enum(["low", "medium", "high", "urgent"]), exports_external2.array(exports_external2.enum(["low", "medium", "high", "urgent"]))]).optional().describe("Filter by priority"),
26553
+ status: exports_external2.union([exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]), exports_external2.array(exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))]).optional().describe("Filter by status"),
26554
+ priority: exports_external2.union([exports_external2.enum(["low", "medium", "high", "critical"]), exports_external2.array(exports_external2.enum(["low", "medium", "high", "critical"]))]).optional().describe("Filter by priority"),
26268
26555
  project_id: exports_external2.string().optional().describe("Filter by project"),
26269
26556
  task_list_id: exports_external2.string().optional().describe("Filter by task list"),
26270
26557
  assigned_to: exports_external2.string().optional().describe("Filter by assignee (agent ID or name, empty string = unassigned)"),
@@ -26299,9 +26586,9 @@ function registerTaskCrudTools(server, ctx) {
26299
26586
  }, async ({ task_id }) => {
26300
26587
  try {
26301
26588
  const resolvedId = resolveId(task_id);
26302
- const task = getTask2(resolvedId);
26589
+ const task = getTask(resolvedId);
26303
26590
  if (!task)
26304
- throw new NotFoundError(`Task not found: ${task_id}`);
26591
+ throw new TaskNotFoundError(task_id);
26305
26592
  const focus = ctx.getAgentFocus(task.assigned_to || "");
26306
26593
  const lines = [
26307
26594
  `ID: ${task.id}`,
@@ -26315,7 +26602,7 @@ function registerTaskCrudTools(server, ctx) {
26315
26602
  task.estimated_minutes != null ? `Estimate: ${task.estimated_minutes} min` : null,
26316
26603
  task.actual_minutes != null ? `Actual: ${task.actual_minutes} min` : null,
26317
26604
  task.confidence != null ? `Confidence: ${task.confidence}` : null,
26318
- task.deadline ? `Deadline: ${task.deadline}` : null,
26605
+ task.due_at ? `Due: ${task.due_at}` : null,
26319
26606
  task.completed_at ? `Completed: ${task.completed_at}` : null,
26320
26607
  focus ? `Focus: agent=${focus.agent_id} project=${focus.project_id || "(global)"}` : null,
26321
26608
  task.created_at ? `Created: ${task.created_at}` : null,
@@ -26337,8 +26624,8 @@ ${task.description}` : null
26337
26624
  task_id: exports_external2.string().describe("Task ID"),
26338
26625
  title: exports_external2.string().optional(),
26339
26626
  description: exports_external2.string().optional(),
26340
- status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
26341
- priority: exports_external2.enum(["low", "medium", "high", "urgent"]).optional(),
26627
+ status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
26628
+ priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
26342
26629
  assigned_to: exports_external2.string().nullable().optional().describe("Agent ID or name, null to unassign"),
26343
26630
  project_id: exports_external2.string().nullable().optional(),
26344
26631
  task_list_id: exports_external2.string().nullable().optional(),
@@ -26367,9 +26654,15 @@ ${task.description}` : null
26367
26654
  resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
26368
26655
  if (resolved.depends_on && Array.isArray(resolved.depends_on))
26369
26656
  resolved.depends_on = resolved.depends_on.map(resolveId);
26370
- if (resolved.estimate !== undefined)
26657
+ if (resolved.estimate !== undefined) {
26371
26658
  resolved.estimated_minutes = resolved.estimate;
26372
- const task = updateTask(resolvedId, resolved, version);
26659
+ delete resolved.estimate;
26660
+ }
26661
+ if (resolved.deadline !== undefined) {
26662
+ resolved.due_at = resolved.deadline;
26663
+ delete resolved.deadline;
26664
+ }
26665
+ const task = updateTask(resolvedId, { ...resolved, version: versionFor(resolvedId, version) });
26373
26666
  return { content: [{ type: "text", text: formatTask(task) }] };
26374
26667
  } catch (e) {
26375
26668
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26394,11 +26687,24 @@ ${task.description}` : null
26394
26687
  var init_task_crud2 = __esm(() => {
26395
26688
  init_zod2();
26396
26689
  init_tasks();
26690
+ init_types();
26397
26691
  });
26398
26692
 
26399
26693
  // src/mcp/tools/task-project-tools.ts
26400
26694
  function registerTaskProjectTools(server, ctx) {
26401
26695
  const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
26696
+ function versionFor(taskId, version) {
26697
+ const current = getTask(taskId);
26698
+ if (!current)
26699
+ throw new TaskNotFoundError(taskId);
26700
+ if (version !== undefined && current.version !== version) {
26701
+ throw new VersionConflictError(taskId, version, current.version);
26702
+ }
26703
+ return current.version;
26704
+ }
26705
+ function updateWithOptionalVersion(taskId, updates, version) {
26706
+ return updateTask(taskId, { ...updates, version: versionFor(taskId, version) });
26707
+ }
26402
26708
  if (shouldRegisterTool("start_task")) {
26403
26709
  server.tool("start_task", "Mark a task as in_progress. Uses optimistic locking via version if provided.", {
26404
26710
  task_id: exports_external2.string().describe("Task ID"),
@@ -26406,7 +26712,12 @@ function registerTaskProjectTools(server, ctx) {
26406
26712
  }, async ({ task_id, version }) => {
26407
26713
  try {
26408
26714
  const resolvedId = resolveId(task_id);
26409
- const task = updateTask(resolvedId, { status: "in_progress" }, version);
26715
+ if (version !== undefined)
26716
+ versionFor(resolvedId, version);
26717
+ const current = getTask(resolvedId);
26718
+ if (!current)
26719
+ throw new TaskNotFoundError(resolvedId);
26720
+ const task = startTask(resolvedId, current.assigned_to || current.agent_id || "mcp");
26410
26721
  return { content: [{ type: "text", text: formatTask(task) }] };
26411
26722
  } catch (e) {
26412
26723
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26422,7 +26733,12 @@ function registerTaskProjectTools(server, ctx) {
26422
26733
  }, async ({ task_id, confidence, completed_at, version }) => {
26423
26734
  try {
26424
26735
  const resolvedId = resolveId(task_id);
26425
- const task = updateTask(resolvedId, { status: "completed", confidence, completed_at }, version);
26736
+ if (version !== undefined)
26737
+ versionFor(resolvedId, version);
26738
+ const current = getTask(resolvedId);
26739
+ if (!current)
26740
+ throw new TaskNotFoundError(resolvedId);
26741
+ const task = completeTask(resolvedId, current.assigned_to || current.agent_id || undefined, undefined, { confidence, completed_at });
26426
26742
  return { content: [{ type: "text", text: formatTask(task) }] };
26427
26743
  } catch (e) {
26428
26744
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26436,7 +26752,7 @@ function registerTaskProjectTools(server, ctx) {
26436
26752
  }, async ({ task_id, version }) => {
26437
26753
  try {
26438
26754
  const resolvedId = resolveId(task_id);
26439
- const task = updateTask(resolvedId, { status: "cancelled" }, version);
26755
+ const task = version === undefined ? setTaskStatus(resolvedId, "cancelled") : updateWithOptionalVersion(resolvedId, { status: "cancelled" }, version);
26440
26756
  return { content: [{ type: "text", text: formatTask(task) }] };
26441
26757
  } catch (e) {
26442
26758
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26452,7 +26768,7 @@ function registerTaskProjectTools(server, ctx) {
26452
26768
  try {
26453
26769
  const resolvedId = resolveId(task_id);
26454
26770
  const resolvedAssignee = resolveId(new_assignee, "agents");
26455
- const task = updateTask(resolvedId, { assigned_to: resolvedAssignee }, version);
26771
+ const task = updateWithOptionalVersion(resolvedId, { assigned_to: resolvedAssignee }, version);
26456
26772
  return { content: [{ type: "text", text: formatTask(task) }] };
26457
26773
  } catch (e) {
26458
26774
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26467,7 +26783,7 @@ function registerTaskProjectTools(server, ctx) {
26467
26783
  }, async ({ task_id, deadline, version }) => {
26468
26784
  try {
26469
26785
  const resolvedId = resolveId(task_id);
26470
- const task = updateTask(resolvedId, { deadline }, version);
26786
+ const task = updateWithOptionalVersion(resolvedId, { due_at: deadline }, version);
26471
26787
  return { content: [{ type: "text", text: formatTask(task) }] };
26472
26788
  } catch (e) {
26473
26789
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26477,12 +26793,12 @@ function registerTaskProjectTools(server, ctx) {
26477
26793
  if (shouldRegisterTool("prioritize_task")) {
26478
26794
  server.tool("prioritize_task", "Set a task's priority.", {
26479
26795
  task_id: exports_external2.string().describe("Task ID"),
26480
- priority: exports_external2.enum(["low", "medium", "high", "urgent"]).describe("New priority"),
26796
+ priority: exports_external2.enum(["low", "medium", "high", "critical"]).describe("New priority"),
26481
26797
  version: exports_external2.number().optional().describe("Expected version for optimistic locking")
26482
26798
  }, async ({ task_id, priority, version }) => {
26483
26799
  try {
26484
26800
  const resolvedId = resolveId(task_id);
26485
- const task = updateTask(resolvedId, { priority }, version);
26801
+ const task = version === undefined ? setTaskPriority(resolvedId, priority) : updateWithOptionalVersion(resolvedId, { priority }, version);
26486
26802
  return { content: [{ type: "text", text: formatTask(task) }] };
26487
26803
  } catch (e) {
26488
26804
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26538,18 +26854,18 @@ function registerTaskProjectTools(server, ctx) {
26538
26854
  if (shouldRegisterTool("bulk_update_tasks")) {
26539
26855
  server.tool("bulk_update_tasks", "Update multiple tasks at once. All tasks must pass the dependency check.", {
26540
26856
  task_ids: exports_external2.array(exports_external2.string()).describe("Array of task IDs to update"),
26541
- status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
26542
- priority: exports_external2.enum(["low", "medium", "high", "urgent"]).optional(),
26857
+ status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
26858
+ priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
26543
26859
  assigned_to: exports_external2.string().nullable().optional().describe("Agent ID or name, null to unassign")
26544
26860
  }, async ({ task_ids, status, priority, assigned_to }) => {
26545
26861
  try {
26546
- const { bulkUpdateTasks: bulkUpdateTasks2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
26862
+ const { bulkUpdateTasks: bulkUpdateTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
26547
26863
  const resolved = task_ids.map(resolveId);
26548
26864
  let resolvedAssignee = assigned_to;
26549
26865
  if (resolvedAssignee && typeof resolvedAssignee === "string")
26550
26866
  resolvedAssignee = resolveId(resolvedAssignee, "agents");
26551
26867
  const result = bulkUpdateTasks2(resolved, { status, priority, assigned_to: resolvedAssignee });
26552
- return { content: [{ type: "text", text: `${result.updated} task(s) updated, ${result.skipped} skipped (dependency check).` }] };
26868
+ return { content: [{ type: "text", text: `${result.updated} task(s) updated, ${result.failed.length} failed.` }] };
26553
26869
  } catch (e) {
26554
26870
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
26555
26871
  }
@@ -26560,8 +26876,8 @@ function registerTaskProjectTools(server, ctx) {
26560
26876
  tasks: exports_external2.array(exports_external2.object({
26561
26877
  title: exports_external2.string(),
26562
26878
  description: exports_external2.string().optional(),
26563
- status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
26564
- priority: exports_external2.enum(["low", "medium", "high", "urgent"]).optional(),
26879
+ status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
26880
+ priority: exports_external2.enum(["low", "medium", "high", "critical"]).optional(),
26565
26881
  project_id: exports_external2.string().optional(),
26566
26882
  task_list_id: exports_external2.string().optional(),
26567
26883
  assigned_to: exports_external2.string().optional(),
@@ -26572,7 +26888,7 @@ function registerTaskProjectTools(server, ctx) {
26572
26888
  })).describe("Array of task objects")
26573
26889
  }, async ({ tasks }) => {
26574
26890
  try {
26575
- const { bulkCreateTasks: bulkCreateTasks2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
26891
+ const { bulkCreateTasks: bulkCreateTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
26576
26892
  const resolved = tasks.map((t) => {
26577
26893
  const r = { ...t };
26578
26894
  if (r.project_id)
@@ -26586,7 +26902,7 @@ function registerTaskProjectTools(server, ctx) {
26586
26902
  return r;
26587
26903
  });
26588
26904
  const result = bulkCreateTasks2(resolved);
26589
- return { content: [{ type: "text", text: `${result.created} task(s) created, ${result.skipped} skipped (duplicate short_id).` }] };
26905
+ return { content: [{ type: "text", text: `${result.created.length} task(s) created.` }] };
26590
26906
  } catch (e) {
26591
26907
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
26592
26908
  }
@@ -26598,9 +26914,9 @@ function registerTaskProjectTools(server, ctx) {
26598
26914
  force: exports_external2.boolean().optional().describe("Skip child check for all tasks (dangerous)")
26599
26915
  }, async ({ task_ids, force }) => {
26600
26916
  try {
26601
- const { bulkDeleteTasks } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
26917
+ const { bulkDeleteTasks: bulkDeleteTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
26602
26918
  const resolved = task_ids.map(resolveId);
26603
- const result = bulkDeleteTasks(resolved, force);
26919
+ const result = bulkDeleteTasks2(resolved, force);
26604
26920
  return { content: [{ type: "text", text: `${result.deleted} task(s) deleted, ${result.skipped} skipped (has children).` }] };
26605
26921
  } catch (e) {
26606
26922
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -26649,7 +26965,7 @@ function registerTaskProjectTools(server, ctx) {
26649
26965
  const project = getProject(resolvedId);
26650
26966
  if (!project)
26651
26967
  throw new TaskNotFoundError(`Project not found: ${project_id}`);
26652
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
26968
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
26653
26969
  const tasks = listTasks3({ project_id: resolvedId, limit: 100 }, undefined);
26654
26970
  const lines = [
26655
26971
  `ID: ${project.id}`,
@@ -26750,7 +27066,7 @@ function registerTaskProjectTools(server, ctx) {
26750
27066
  throw new TaskNotFoundError(`Task list not found: ${task_list_id}`);
26751
27067
  let tasks = [];
26752
27068
  if (include_tasks) {
26753
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27069
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
26754
27070
  tasks = listTasks3({ task_list_id: resolvedId, limit: 200 }, undefined);
26755
27071
  }
26756
27072
  const lines = [
@@ -26850,7 +27166,7 @@ Tasks:` : null,
26850
27166
  throw new TaskNotFoundError(`Plan not found: ${plan_id}`);
26851
27167
  let tasks = [];
26852
27168
  if (include_tasks) {
26853
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27169
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
26854
27170
  tasks = listTasks3({ plan_id: resolvedId, limit: 200 }, undefined);
26855
27171
  }
26856
27172
  const lines = [
@@ -26939,7 +27255,7 @@ Tasks:` : null,
26939
27255
  const tag = getTag(tag_id);
26940
27256
  if (!tag)
26941
27257
  throw new TaskNotFoundError(`Tag not found: ${tag_id}`);
26942
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27258
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
26943
27259
  const tasks = listTasks3({ tags: [tag.name], limit: 100 }, undefined);
26944
27260
  const lines = [
26945
27261
  `Tag: ${tag.name}${tag.color ? ` (${tag.color})` : ""}`,
@@ -27018,7 +27334,7 @@ Tasks:` : null,
27018
27334
  const label = getLabel(label_id);
27019
27335
  if (!label)
27020
27336
  throw new TaskNotFoundError(`Label not found: ${label_id}`);
27021
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27337
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
27022
27338
  const tasks = listTasks3({ tags: [label.name], limit: 100 }, undefined);
27023
27339
  const lines = [
27024
27340
  `Label: ${label.name}${label.color ? ` (${label.color})` : ""}`,
@@ -27069,7 +27385,7 @@ Tasks:` : null,
27069
27385
  try {
27070
27386
  const resolvedId = resolveId(task_id);
27071
27387
  const resolvedAuthor = author ? resolveId(author, "agents") : undefined;
27072
- const comment = addComment({ task_id: resolvedId, body, author: resolvedAuthor });
27388
+ const comment = addComment({ task_id: resolvedId, content: body, agent_id: resolvedAuthor });
27073
27389
  return { content: [{ type: "text", text: `Comment added to ${task_id.slice(0, 8)}: ${body.slice(0, 50)}${body.length > 50 ? "..." : ""}` }] };
27074
27390
  } catch (e) {
27075
27391
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27085,8 +27401,8 @@ Tasks:` : null,
27085
27401
  const comments = listComments(resolvedId);
27086
27402
  if (comments.length === 0)
27087
27403
  return { content: [{ type: "text", text: "No comments." }] };
27088
- const lines = comments.map((c) => `[${c.author || "unknown"}] ${c.created_at?.slice(0, 16)}:
27089
- ${c.body}`);
27404
+ const lines = comments.map((c) => `[${c.agent_id || "unknown"}] ${c.created_at?.slice(0, 16)}:
27405
+ ${c.content}`);
27090
27406
  return { content: [{ type: "text", text: lines.join(`
27091
27407
 
27092
27408
  `) }] };
@@ -27101,7 +27417,7 @@ Tasks:` : null,
27101
27417
  body: exports_external2.string().describe("New comment body")
27102
27418
  }, async ({ comment_id, body }) => {
27103
27419
  try {
27104
- const comment = updateComment(comment_id, { body });
27420
+ const comment = updateComment(comment_id, { content: body });
27105
27421
  return { content: [{ type: "text", text: `Comment ${comment_id.slice(0, 8)} updated.` }] };
27106
27422
  } catch (e) {
27107
27423
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27124,11 +27440,11 @@ Tasks:` : null,
27124
27440
  server.tool("search_tasks", "Full-text search across task titles and descriptions.", {
27125
27441
  query: exports_external2.string().describe("Search query"),
27126
27442
  project_id: exports_external2.string().optional().describe("Filter by project"),
27127
- status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
27443
+ status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
27128
27444
  limit: exports_external2.number().optional().describe("Max results (default: 20)")
27129
27445
  }, async ({ query, project_id, status, limit }) => {
27130
27446
  try {
27131
- const { searchTasks: searchTasks2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27447
+ const { searchTasks: searchTasks2 } = (init_search(), __toCommonJS(exports_search));
27132
27448
  const resolved = { query, limit };
27133
27449
  if (project_id)
27134
27450
  resolved.project_id = resolveId(project_id, "projects");
@@ -27146,29 +27462,6 @@ ${lines.join(`
27146
27462
  }
27147
27463
  });
27148
27464
  }
27149
- if (shouldRegisterTool("sync")) {
27150
- server.tool("sync", "Sync tasks from a GitHub PR or external source into the project.", {
27151
- source: exports_external2.enum(["github_pr", "linear", "asana"]).describe("Source type"),
27152
- source_id: exports_external2.string().describe("PR number, Linear issue ID, or Asana task ID"),
27153
- project_id: exports_external2.string().optional().describe("Project ID to import into"),
27154
- options: exports_external2.record(exports_external2.unknown()).optional().describe("Source-specific options")
27155
- }, async ({ source, source_id, project_id, options }) => {
27156
- try {
27157
- const { syncFromGithubPR, syncFromLinear, syncFromAsana } = (()=>{throw new Error("Cannot require module "+"../lib/sync.js");})();
27158
- let result;
27159
- if (source === "github_pr") {
27160
- result = await syncFromGithubPR({ prNumber: parseInt(source_id), project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
27161
- } else if (source === "linear") {
27162
- result = await syncFromLinear({ issueId: source_id, project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
27163
- } else {
27164
- result = await syncFromAsana({ taskId: source_id, project_id: project_id ? resolveId(project_id, "projects") : undefined, ...options });
27165
- }
27166
- return { content: [{ type: "text", text: `Synced from ${source}: ${result.task?.title || source_id}` }] };
27167
- } catch (e) {
27168
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
27169
- }
27170
- });
27171
- }
27172
27465
  }
27173
27466
  var init_task_project_tools = __esm(() => {
27174
27467
  init_zod2();
@@ -27183,6 +27476,16 @@ var init_task_project_tools = __esm(() => {
27183
27476
  // src/mcp/tools/task-workflow-tools.ts
27184
27477
  function registerTaskWorkflowTools(server, ctx) {
27185
27478
  const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
27479
+ function versionFor(taskId, version) {
27480
+ const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27481
+ const current = getTask2(taskId);
27482
+ if (!current)
27483
+ throw new TaskNotFoundError(taskId);
27484
+ if (version !== undefined && current.version !== version) {
27485
+ throw new VersionConflictError(taskId, version, current.version);
27486
+ }
27487
+ return current.version;
27488
+ }
27186
27489
  if (shouldRegisterTool("approve_task")) {
27187
27490
  server.tool("approve_task", "Approve a task that requires_approval. Records who approved it.", {
27188
27491
  task_id: exports_external2.string().describe("Task ID"),
@@ -27191,13 +27494,14 @@ function registerTaskWorkflowTools(server, ctx) {
27191
27494
  version: exports_external2.number().optional().describe("Expected version for optimistic locking")
27192
27495
  }, async ({ task_id, approved_by, notes, version }) => {
27193
27496
  try {
27194
- const { updateTask: updateTask2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27497
+ const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27195
27498
  const resolvedId = resolveId(task_id);
27196
27499
  const resolvedApprover = approved_by ? resolveId(approved_by, "agents") : undefined;
27197
27500
  const task = updateTask2(resolvedId, {
27198
27501
  approved_by: resolvedApprover,
27199
- metadata: notes ? { approval_notes: notes } : {}
27200
- }, version);
27502
+ metadata: notes ? { approval_notes: notes } : {},
27503
+ version: versionFor(resolvedId, version)
27504
+ });
27201
27505
  return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} approved by ${resolvedApprover || "unknown"}` }] };
27202
27506
  } catch (e) {
27203
27507
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27212,15 +27516,11 @@ function registerTaskWorkflowTools(server, ctx) {
27212
27516
  version: exports_external2.number().optional().describe("Expected version for optimistic locking")
27213
27517
  }, async ({ task_id, reason, agent_id, version }) => {
27214
27518
  try {
27215
- const { updateTask: updateTask2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27519
+ const { failTask: failTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27216
27520
  const resolvedId = resolveId(task_id);
27217
- const task = updateTask2(resolvedId, {
27218
- status: "pending",
27219
- retry_count: (getTask(resolvedId)?.retry_count ?? 0) + 1,
27220
- back_on_board: false,
27221
- metadata: reason ? { failure_reason: reason } : {}
27222
- }, version);
27223
- return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} marked failed. Retry count: ${task.retry_count}` }] };
27521
+ versionFor(resolvedId, version);
27522
+ const result = failTask2(resolvedId, agent_id, reason);
27523
+ return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} marked failed. Retry count: ${result.task.retry_count}` }] };
27224
27524
  } catch (e) {
27225
27525
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
27226
27526
  }
@@ -27229,12 +27529,12 @@ function registerTaskWorkflowTools(server, ctx) {
27229
27529
  if (shouldRegisterTool("get_my_tasks")) {
27230
27530
  server.tool("get_my_tasks", "Get tasks assigned to the calling agent. Supports focus mode scoping.", {
27231
27531
  agent_id: exports_external2.string().optional().describe("Agent ID (defaults to context agent)"),
27232
- status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Filter by status"),
27532
+ status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional().describe("Filter by status"),
27233
27533
  project_id: exports_external2.string().optional().describe("Filter by project (respects focus mode)"),
27234
27534
  limit: exports_external2.number().optional().describe("Max results (default: 50)")
27235
27535
  }, async ({ agent_id, status, project_id, limit }) => {
27236
27536
  try {
27237
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
27537
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
27238
27538
  const focus = ctx.getAgentFocus(agent_id || "");
27239
27539
  const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
27240
27540
  const effectiveProjectId = focus?.project_id || project_id;
@@ -27254,6 +27554,122 @@ function registerTaskWorkflowTools(server, ctx) {
27254
27554
  }
27255
27555
  });
27256
27556
  }
27557
+ if (shouldRegisterTool("get_next_task")) {
27558
+ server.tool("get_next_task", "Get the best available pending task without claiming it.", {
27559
+ agent_id: exports_external2.string().optional().describe("Agent ID or name for assignment affinity"),
27560
+ project_id: exports_external2.string().optional().describe("Filter by project"),
27561
+ task_list_id: exports_external2.string().optional().describe("Filter by task list"),
27562
+ plan_id: exports_external2.string().optional().describe("Filter by plan"),
27563
+ tags: exports_external2.array(exports_external2.string()).optional().describe("Filter by tags")
27564
+ }, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
27565
+ try {
27566
+ const { getNextTask: getNextTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27567
+ const filters = {};
27568
+ if (project_id)
27569
+ filters.project_id = resolveId(project_id, "projects");
27570
+ if (task_list_id)
27571
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
27572
+ if (plan_id)
27573
+ filters.plan_id = resolveId(plan_id, "plans");
27574
+ if (tags)
27575
+ filters.tags = tags;
27576
+ const task = getNextTask2(agent_id, filters);
27577
+ return { content: [{ type: "text", text: task ? formatTask(task) : "No available task." }] };
27578
+ } catch (e) {
27579
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
27580
+ }
27581
+ });
27582
+ }
27583
+ if (shouldRegisterTool("claim_next_task")) {
27584
+ server.tool("claim_next_task", "Atomically claim and start the best available pending task for an agent.", {
27585
+ agent_id: exports_external2.string().describe("Agent ID or name claiming the task"),
27586
+ project_id: exports_external2.string().optional().describe("Filter by project"),
27587
+ task_list_id: exports_external2.string().optional().describe("Filter by task list"),
27588
+ plan_id: exports_external2.string().optional().describe("Filter by plan"),
27589
+ tags: exports_external2.array(exports_external2.string()).optional().describe("Filter by tags")
27590
+ }, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
27591
+ try {
27592
+ const { claimNextTask: claimNextTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27593
+ const filters = {};
27594
+ if (project_id)
27595
+ filters.project_id = resolveId(project_id, "projects");
27596
+ if (task_list_id)
27597
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
27598
+ if (plan_id)
27599
+ filters.plan_id = resolveId(plan_id, "plans");
27600
+ if (tags)
27601
+ filters.tags = tags;
27602
+ const task = claimNextTask2(agent_id, filters);
27603
+ return { content: [{ type: "text", text: task ? formatTask(task) : "No available task to claim." }] };
27604
+ } catch (e) {
27605
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
27606
+ }
27607
+ });
27608
+ }
27609
+ if (shouldRegisterTool("get_tasks_changed_since")) {
27610
+ server.tool("get_tasks_changed_since", "List tasks changed since an ISO timestamp.", {
27611
+ since: exports_external2.string().describe("ISO timestamp"),
27612
+ project_id: exports_external2.string().optional().describe("Filter by project"),
27613
+ task_list_id: exports_external2.string().optional().describe("Filter by task list"),
27614
+ limit: exports_external2.number().optional().describe("Maximum tasks to return")
27615
+ }, async ({ since, project_id, task_list_id, limit }) => {
27616
+ try {
27617
+ const { getTasksChangedSince: getTasksChangedSince2 } = (init_tasks(), __toCommonJS(exports_tasks));
27618
+ const filters = {};
27619
+ if (project_id)
27620
+ filters.project_id = resolveId(project_id, "projects");
27621
+ if (task_list_id)
27622
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
27623
+ const tasks = getTasksChangedSince2(since, filters).slice(0, limit || 50);
27624
+ if (tasks.length === 0)
27625
+ return { content: [{ type: "text", text: "No changed tasks." }] };
27626
+ return { content: [{ type: "text", text: tasks.map(formatTask).join(`
27627
+ `) }] };
27628
+ } catch (e) {
27629
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
27630
+ }
27631
+ });
27632
+ }
27633
+ function registerContextTool(name, description) {
27634
+ if (!shouldRegisterTool(name))
27635
+ return;
27636
+ server.tool(name, description, {
27637
+ agent_id: exports_external2.string().optional().describe("Agent ID or name"),
27638
+ project_id: exports_external2.string().optional().describe("Filter by project"),
27639
+ task_list_id: exports_external2.string().optional().describe("Filter by task list"),
27640
+ explain_blocked: exports_external2.boolean().optional().describe("Include blocked task details")
27641
+ }, async ({ agent_id, project_id, task_list_id, explain_blocked }) => {
27642
+ try {
27643
+ const { getStatus: getStatus2, getNextTask: getNextTask2, getOverdueTasks: getOverdueTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
27644
+ const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
27645
+ const filters = {};
27646
+ if (project_id)
27647
+ filters.project_id = resolveId(project_id, "projects");
27648
+ if (task_list_id)
27649
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
27650
+ const status = getStatus2(filters, agent_id, { explain_blocked });
27651
+ const next_task = getNextTask2(agent_id, filters);
27652
+ const overdue = getOverdueTasks2(filters.project_id);
27653
+ const latest_handoff = getLatestHandoff2(agent_id, filters.project_id);
27654
+ return {
27655
+ content: [{
27656
+ type: "text",
27657
+ text: JSON.stringify({
27658
+ status,
27659
+ next_task,
27660
+ overdue_count: overdue.length,
27661
+ latest_handoff,
27662
+ as_of: new Date().toISOString()
27663
+ }, null, 2)
27664
+ }]
27665
+ };
27666
+ } catch (e) {
27667
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
27668
+ }
27669
+ });
27670
+ }
27671
+ registerContextTool("get_context", "Get session start context: queue status, next task, overdue count, and latest handoff.");
27672
+ registerContextTool("bootstrap", "Bootstrap an agent session with queue context and the next available task.");
27257
27673
  if (shouldRegisterTool("get_org_chart")) {
27258
27674
  server.tool("get_org_chart", "Get the global org chart (agent hierarchy + titles).", {
27259
27675
  format: exports_external2.enum(["text", "json"]).optional().describe("Output format (default: text)")
@@ -27274,7 +27690,7 @@ function registerTaskWorkflowTools(server, ctx) {
27274
27690
  }).join(`
27275
27691
  `);
27276
27692
  };
27277
- const { getOrgChart: getOrgChart2 } = (()=>{throw new Error("Cannot require module "+"../db/agents.js");})();
27693
+ const { getOrgChart: getOrgChart2 } = (init_agents(), __toCommonJS(exports_agents));
27278
27694
  const tree = getOrgChart2();
27279
27695
  if (format === "json") {
27280
27696
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
@@ -27294,10 +27710,10 @@ function registerTaskWorkflowTools(server, ctx) {
27294
27710
  reports_to: exports_external2.string().describe("Manager agent ID or name")
27295
27711
  }, async ({ agent_id, reports_to }) => {
27296
27712
  try {
27297
- const { setReportsTo } = (()=>{throw new Error("Cannot require module "+"../db/agents.js");})();
27713
+ const { updateAgent: updateAgent2 } = (init_agents(), __toCommonJS(exports_agents));
27298
27714
  const resolvedAgent = resolveId(agent_id, "agents");
27299
27715
  const resolvedManager = resolveId(reports_to, "agents");
27300
- setReportsTo(resolvedAgent, resolvedManager);
27716
+ updateAgent2(resolvedAgent, { reports_to: resolvedManager });
27301
27717
  return { content: [{ type: "text", text: `${agent_id} now reports to ${reports_to}` }] };
27302
27718
  } catch (e) {
27303
27719
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27307,6 +27723,7 @@ function registerTaskWorkflowTools(server, ctx) {
27307
27723
  }
27308
27724
  var init_task_workflow_tools = __esm(() => {
27309
27725
  init_zod2();
27726
+ init_types();
27310
27727
  });
27311
27728
 
27312
27729
  // src/mcp/tools/task-auto-tools.ts
@@ -27318,9 +27735,9 @@ function registerTaskAutoTools(server, ctx) {
27318
27735
  project_id: exports_external2.string().optional().describe("Scope to a project")
27319
27736
  }, async ({ days = 7, project_id }) => {
27320
27737
  try {
27321
- const { archiveCompletedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
27738
+ const { archiveCompletedTasks: archiveCompletedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
27322
27739
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
27323
- const count = archiveCompletedTasks(days, resolvedProjectId);
27740
+ const count = archiveCompletedTasks2(days, resolvedProjectId);
27324
27741
  return { content: [{ type: "text", text: `Archived ${count} completed task(s) older than ${days} days.` }] };
27325
27742
  } catch (e) {
27326
27743
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27346,9 +27763,9 @@ function registerTaskAutoTools(server, ctx) {
27346
27763
  limit: exports_external2.number().optional().describe("Max results (default: 50)")
27347
27764
  }, async ({ project_id, limit }) => {
27348
27765
  try {
27349
- const { getArchivedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
27766
+ const { getArchivedTasks: getArchivedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
27350
27767
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
27351
- const tasks = getArchivedTasks({ project_id: resolvedProjectId, limit: limit || 50 });
27768
+ const tasks = getArchivedTasks2({ project_id: resolvedProjectId, limit: limit || 50 });
27352
27769
  if (tasks.length === 0)
27353
27770
  return { content: [{ type: "text", text: "No archived tasks." }] };
27354
27771
  const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} archived ${t.archived_at}`);
@@ -27364,14 +27781,13 @@ function registerTaskAutoTools(server, ctx) {
27364
27781
  task_id: exports_external2.string().describe("Task ID")
27365
27782
  }, async ({ task_id }) => {
27366
27783
  try {
27367
- const { autoAssign } = (init_agents(), __toCommonJS(exports_agents));
27368
27784
  const resolvedId = resolveId(task_id);
27369
- const assignment = autoAssign(resolvedId);
27370
- if (!assignment)
27785
+ const { autoAssignTask: autoAssignTask2 } = (init_auto_assign(), __toCommonJS(exports_auto_assign));
27786
+ const assignment = await autoAssignTask2(resolvedId);
27787
+ if (!assignment.assigned_to)
27371
27788
  return { content: [{ type: "text", text: "No suitable agent found for this task." }] };
27372
- const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27373
- updateTask2(resolvedId, { assigned_to: assignment.agent_id });
27374
- return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} assigned to ${assignment.agent_name} (score: ${assignment.score.toFixed(2)})` }] };
27789
+ const reason = assignment.reason ? ` \u2014 ${assignment.reason}` : "";
27790
+ return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} assigned to ${assignment.agent_name || assignment.assigned_to} via ${assignment.method}${reason}` }] };
27375
27791
  } catch (e) {
27376
27792
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
27377
27793
  }
@@ -27382,10 +27798,23 @@ function registerTaskAutoTools(server, ctx) {
27382
27798
  agent_id: exports_external2.string().optional().describe("Agent ID (defaults to context agent)")
27383
27799
  }, async ({ agent_id }) => {
27384
27800
  try {
27385
- const { getAgentWorkload } = (init_agents(), __toCommonJS(exports_agents));
27801
+ const { listTasks: listTasks3, getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
27386
27802
  const focus = ctx.getAgentFocus(agent_id || "");
27387
27803
  const effectiveAgentId = focus ? focus.agent_id : agent_id || "";
27388
- const workload = getAgentWorkload(effectiveAgentId);
27804
+ if (!effectiveAgentId) {
27805
+ return { content: [{ type: "text", text: "No agent_id provided and no agent focus is active." }], isError: true };
27806
+ }
27807
+ const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
27808
+ const now2 = Date.now();
27809
+ const dueSoonCutoff = now2 + 24 * 60 * 60 * 1000;
27810
+ const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
27811
+ const workload = {
27812
+ in_progress: assigned.filter((t) => t.status === "in_progress").length,
27813
+ pending: assigned.filter((t) => t.status === "pending").length,
27814
+ completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now2 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
27815
+ 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,
27816
+ blocked: blocked.length
27817
+ };
27389
27818
  const lines = [
27390
27819
  `Agent: ${effectiveAgentId}`,
27391
27820
  `In Progress: ${workload.in_progress}`,
@@ -27407,10 +27836,29 @@ function registerTaskAutoTools(server, ctx) {
27407
27836
  max_per_agent: exports_external2.number().optional().describe("Max tasks per agent (default: 5)")
27408
27837
  }, async ({ project_id, max_per_agent }) => {
27409
27838
  try {
27410
- const { rebalanceWorkload } = (init_agents(), __toCommonJS(exports_agents));
27839
+ const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
27840
+ const { listTasks: listTasks3, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27411
27841
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
27412
- const result = rebalanceWorkload({ project_id: resolvedProjectId, max_per_agent: max_per_agent || 5 });
27413
- return { content: [{ type: "text", text: `Rebalanced: moved ${result.moved} task(s), ${result.skipped} skipped.` }] };
27842
+ const limit = max_per_agent || 5;
27843
+ const agents = listAgents2().filter((agent) => agent.status === "active");
27844
+ if (agents.length === 0)
27845
+ return { content: [{ type: "text", text: "No active agents available for rebalancing." }] };
27846
+ const activeTasks = listTasks3({ project_id: resolvedProjectId, status: ["pending", "in_progress"], limit: 1000 }, undefined);
27847
+ const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => t.assigned_to === agent.id).length]));
27848
+ let moved = 0;
27849
+ let skipped = 0;
27850
+ for (const task of activeTasks.filter((t) => t.status === "pending" && t.assigned_to && (load.get(t.assigned_to) ?? 0) > limit)) {
27851
+ const target = agents.filter((agent) => agent.id !== task.assigned_to).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
27852
+ if (!target || (load.get(target.id) ?? 0) >= limit) {
27853
+ skipped++;
27854
+ continue;
27855
+ }
27856
+ updateTask2(task.id, { assigned_to: target.id, version: task.version });
27857
+ load.set(task.assigned_to, (load.get(task.assigned_to) ?? 1) - 1);
27858
+ load.set(target.id, (load.get(target.id) ?? 0) + 1);
27859
+ moved++;
27860
+ }
27861
+ return { content: [{ type: "text", text: `Rebalanced: moved ${moved} task(s), ${skipped} skipped.` }] };
27414
27862
  } catch (e) {
27415
27863
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
27416
27864
  }
@@ -27423,13 +27871,13 @@ function registerTaskAutoTools(server, ctx) {
27423
27871
  agent_id: exports_external2.string().optional().describe("Filter by assignee")
27424
27872
  }, async ({ hours = 24, project_id, agent_id }) => {
27425
27873
  try {
27426
- const { notifyUpcomingDeadlines } = (init_tasks(), __toCommonJS(exports_tasks));
27874
+ const { notifyUpcomingDeadlines: notifyUpcomingDeadlines2 } = (init_tasks(), __toCommonJS(exports_tasks));
27427
27875
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
27428
27876
  const resolvedAgentId = agent_id ? resolveId(agent_id, "agents") : undefined;
27429
- const tasks = notifyUpcomingDeadlines({ hours, project_id: resolvedProjectId, agent_id: resolvedAgentId });
27877
+ const tasks = notifyUpcomingDeadlines2({ hours, project_id: resolvedProjectId, agent_id: resolvedAgentId });
27430
27878
  if (tasks.length === 0)
27431
27879
  return { content: [{ type: "text", text: "No deadlines approaching." }] };
27432
- const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} due ${t.deadline}`);
27880
+ const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} ${t.title} due ${t.due_at}`);
27433
27881
  return { content: [{ type: "text", text: `${tasks.length} task(s) due within ${hours}h:
27434
27882
  ${lines.join(`
27435
27883
  `)}` }] };
@@ -27463,9 +27911,9 @@ ${lines.join(`
27463
27911
  project_id: exports_external2.string().optional().describe("Filter by project")
27464
27912
  }, async ({ project_id }) => {
27465
27913
  try {
27466
- const { getBlockedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
27914
+ const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
27467
27915
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
27468
- const tasks = getBlockedTasks(resolvedProjectId);
27916
+ const tasks = getBlockedTasks2(resolvedProjectId);
27469
27917
  if (tasks.length === 0)
27470
27918
  return { content: [{ type: "text", text: "No blocked tasks." }] };
27471
27919
  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(", ")}`);
@@ -27482,9 +27930,9 @@ ${lines.join(`
27482
27930
  project_id: exports_external2.string().optional().describe("Filter by project")
27483
27931
  }, async ({ project_id }) => {
27484
27932
  try {
27485
- const { getBlockingTasks } = (init_tasks(), __toCommonJS(exports_tasks));
27933
+ const { getBlockingTasks: getBlockingTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
27486
27934
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
27487
- const tasks = getBlockingTasks(resolvedProjectId);
27935
+ const tasks = getBlockingTasks2(resolvedProjectId);
27488
27936
  if (tasks.length === 0)
27489
27937
  return { content: [{ type: "text", text: "No tasks blocking others." }] };
27490
27938
  const lines = tasks.map((t) => `${t.short_id || t.id.slice(0, 8)} [${t.status}] ${t.title} \u2014 blocking ${t.blocking_count} task(s)`);
@@ -27499,20 +27947,18 @@ ${lines.join(`
27499
27947
  if (shouldRegisterTool("get_health")) {
27500
27948
  server.tool("get_health", "Get system health: task counts by status, active agents, project summary.", async () => {
27501
27949
  try {
27502
- const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
27503
- const { listProjects: listProjects2 } = (init_tasks(), __toCommonJS(exports_tasks));
27950
+ const { countTasks: countTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
27951
+ const { listProjects: listProjects2 } = (init_projects(), __toCommonJS(exports_projects));
27504
27952
  const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
27505
- const [pending, inProgress, completed, cancelled] = await Promise.all([
27506
- Promise.resolve(listTasks3({ status: "pending", limit: 1 }, undefined)),
27507
- Promise.resolve(listTasks3({ status: "in_progress", limit: 1 }, undefined)),
27508
- Promise.resolve(listTasks3({ status: "completed", limit: 1 }, undefined)),
27509
- Promise.resolve(listTasks3({ status: "cancelled", limit: 1 }, undefined))
27510
- ]);
27511
- const projects = listProjects2({ limit: 100 });
27512
- const agents = listAgents2({ limit: 100 });
27953
+ const pending = countTasks2({ status: "pending" });
27954
+ const inProgress = countTasks2({ status: "in_progress" });
27955
+ const completed = countTasks2({ status: "completed" });
27956
+ const cancelled = countTasks2({ status: "cancelled" });
27957
+ const projects = listProjects2();
27958
+ const agents = listAgents2();
27513
27959
  const lines = [
27514
27960
  `=== System Health ===`,
27515
- `Tasks: ${pending.total} pending | ${inProgress.total} in progress | ${completed.total} completed | ${cancelled.total} cancelled`,
27961
+ `Tasks: ${pending} pending | ${inProgress} in progress | ${completed} completed | ${cancelled} cancelled`,
27516
27962
  `Projects: ${projects.length} total`,
27517
27963
  `Agents: ${agents.length} registered`
27518
27964
  ];
@@ -27658,17 +28104,31 @@ var init_task_relationships = __esm(() => {
27658
28104
  function registerTaskAdvTools(server, ctx) {
27659
28105
  const { shouldRegisterTool, resolveId, formatError, formatTask, formatTaskDetail } = ctx;
27660
28106
  if (shouldRegisterTool("get_status")) {
27661
- server.tool("get_status", "Get a task's current status including blockers, dependencies, comments, and assignee.", {
27662
- task_id: exports_external2.string().describe("Task ID")
27663
- }, async ({ task_id }) => {
28107
+ server.tool("get_status", "Get queue status summary, or pass task_id for a task's detailed status.", {
28108
+ task_id: exports_external2.string().optional().describe("Task ID for task-specific status"),
28109
+ project_id: exports_external2.string().optional().describe("Filter summary by project"),
28110
+ task_list_id: exports_external2.string().optional().describe("Filter summary by task list"),
28111
+ agent_id: exports_external2.string().optional().describe("Agent for next-task affinity"),
28112
+ explain_blocked: exports_external2.boolean().optional().describe("Include blocked task explanations in summary")
28113
+ }, async ({ task_id, project_id, task_list_id, agent_id, explain_blocked }) => {
27664
28114
  try {
28115
+ if (!task_id) {
28116
+ const { getStatus: getStatus2 } = (init_tasks(), __toCommonJS(exports_tasks));
28117
+ const filters = {};
28118
+ if (project_id)
28119
+ filters.project_id = resolveId(project_id, "projects");
28120
+ if (task_list_id)
28121
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
28122
+ const status = getStatus2(filters, agent_id, { explain_blocked });
28123
+ return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
28124
+ }
27665
28125
  const resolvedId = resolveId(task_id);
27666
- const { getTask: getTask4 } = (init_tasks(), __toCommonJS(exports_tasks));
27667
- const task = getTask4(resolvedId);
28126
+ const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
28127
+ const task = getTask2(resolvedId);
27668
28128
  if (!task)
27669
28129
  throw new Error(`Task not found: ${task_id}`);
27670
28130
  const { getTaskDependencies: getTaskDependencies3 } = (init_tasks(), __toCommonJS(exports_tasks));
27671
- const { listComments: listComments2 } = (init_tasks(), __toCommonJS(exports_tasks));
28131
+ const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
27672
28132
  const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
27673
28133
  const [deps, comments, files] = await Promise.all([
27674
28134
  Promise.resolve(getTaskDependencies3(resolvedId, "both")),
@@ -27678,14 +28138,14 @@ function registerTaskAdvTools(server, ctx) {
27678
28138
  const lines = [
27679
28139
  `Status: ${task.status} | Priority: ${task.priority}`,
27680
28140
  task.assigned_to ? `Assigned: ${task.assigned_to}` : "Unassigned",
27681
- task.deadline ? `Deadline: ${task.deadline}` : null,
28141
+ task.due_at ? `Due: ${task.due_at}` : null,
27682
28142
  task.confidence != null ? `Confidence: ${task.confidence}` : null,
27683
28143
  deps.length > 0 ? `
27684
28144
  Dependencies (${deps.length}):` : null,
27685
28145
  ...deps.map((d) => ` [${d.direction}] ${d.task_id.slice(0, 8)} (${d.status})`),
27686
28146
  comments.length > 0 ? `
27687
28147
  Comments (${comments.length}):` : null,
27688
- ...comments.map((c) => ` [${c.author || "?"}] ${c.created_at?.slice(0, 16)}: ${c.body.slice(0, 80)}`),
28148
+ ...comments.map((c) => ` [${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content.slice(0, 80)}`),
27689
28149
  files.length > 0 ? `
27690
28150
  Files (${files.length}):` : null,
27691
28151
  ...files.map((f) => ` ${f.status} ${f.path}`)
@@ -27703,15 +28163,15 @@ Files (${files.length}):` : null,
27703
28163
  }, async ({ task_id }) => {
27704
28164
  try {
27705
28165
  const resolvedId = resolveId(task_id);
27706
- const { getTask: getTask4 } = (init_tasks(), __toCommonJS(exports_tasks));
27707
- const task = getTask4(resolvedId);
28166
+ const { getTask: getTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
28167
+ const task = getTask2(resolvedId);
27708
28168
  if (!task)
27709
28169
  throw new Error(`Task not found: ${task_id}`);
27710
28170
  const { getTaskDependencies: getTaskDependencies3 } = (init_tasks(), __toCommonJS(exports_tasks));
27711
28171
  const { getTaskRelationships: getTaskRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
27712
- const { listComments: listComments2 } = (init_tasks(), __toCommonJS(exports_tasks));
28172
+ const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
27713
28173
  const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
27714
- const { getTaskCommits: getTaskCommits2 } = (init_tasks(), __toCommonJS(exports_tasks));
28174
+ const { getTaskCommits: getTaskCommits2 } = (init_task_commits(), __toCommonJS(exports_task_commits));
27715
28175
  const { getTaskWatchers: getTaskWatchers2 } = (init_tasks(), __toCommonJS(exports_tasks));
27716
28176
  const [deps, rels, comments, files, commits, watchers] = await Promise.all([
27717
28177
  Promise.resolve(getTaskDependencies3(resolvedId, "both")),
@@ -27732,7 +28192,7 @@ Files (${files.length}):` : null,
27732
28192
  task.tags?.length ? `Tags: ${task.tags.join(", ")}` : null,
27733
28193
  task.created_at ? `Created: ${task.created_at}` : null,
27734
28194
  task.updated_at ? `Updated: ${task.updated_at}` : null,
27735
- task.deadline ? `Deadline: ${task.deadline}` : null,
28195
+ task.due_at ? `Due: ${task.due_at}` : null,
27736
28196
  task.completed_at ? `Completed: ${task.completed_at}` : null,
27737
28197
  deps.length > 0 ? `
27738
28198
  --- Dependencies (${deps.length}) ---` : null,
@@ -27742,7 +28202,7 @@ Files (${files.length}):` : null,
27742
28202
  ...rels.map((r) => ` ${r.source_task_id.slice(0, 8)} --[${r.relationship_type}]--> ${r.target_task_id.slice(0, 8)}`),
27743
28203
  comments.length > 0 ? `
27744
28204
  --- Comments (${comments.length}) ---` : null,
27745
- ...comments.map((c) => ` [${c.author || "?"}] ${c.created_at?.slice(0, 16)}: ${c.body.slice(0, 120)}`),
28205
+ ...comments.map((c) => ` [${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content.slice(0, 120)}`),
27746
28206
  files.length > 0 ? `
27747
28207
  --- Files (${files.length}) ---` : null,
27748
28208
  ...files.map((f) => ` [${f.status}] ${f.path}`),
@@ -27791,8 +28251,8 @@ ${JSON.stringify(task.metadata, null, 2)}` : null
27791
28251
  limit: 20
27792
28252
  }, undefined);
27793
28253
  const completedYesterday = completed.filter((t) => t.completed_at && t.completed_at.startsWith(yesterdayStr));
27794
- const { getBlockedTasks } = (init_tasks(), __toCommonJS(exports_tasks));
27795
- const blocked = getBlockedTasks(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => t.assigned_to === effectiveAgentId);
28254
+ const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
28255
+ const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => t.assigned_to === effectiveAgentId);
27796
28256
  const lines = [
27797
28257
  `Standup for ${effectiveAgentId} (${effectiveProjectId ? `project: ${effectiveProjectId.slice(0, 8)}` : "all projects"})`,
27798
28258
  inProgress.length > 0 ? `
@@ -27821,11 +28281,11 @@ No blocked tasks.`,
27821
28281
  agent_id: exports_external2.string().optional().describe("Agent claiming (defaults to context)")
27822
28282
  }, async ({ task_id, agent_id }) => {
27823
28283
  try {
27824
- const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
28284
+ const { startTask: startTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27825
28285
  const resolvedId = resolveId(task_id);
27826
28286
  const focus = ctx.getAgentFocus(agent_id || "");
27827
- const effectiveAgent = focus ? focus.agent_id : agent_id || "";
27828
- const task = updateTask2(resolvedId, { status: "in_progress", assigned_to: effectiveAgent });
28287
+ const effectiveAgent = focus ? focus.agent_id : agent_id || "mcp";
28288
+ const task = startTask2(resolvedId, effectiveAgent);
27829
28289
  return { content: [{ type: "text", text: formatTask(task) }] };
27830
28290
  } catch (e) {
27831
28291
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27837,8 +28297,12 @@ No blocked tasks.`,
27837
28297
  task_id: exports_external2.string().describe("Task ID")
27838
28298
  }, async ({ task_id }) => {
27839
28299
  try {
27840
- const { updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27841
- const task = updateTask2(resolveId(task_id), { status: "pending", assigned_to: null });
28300
+ const { getTask: getTask2, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
28301
+ const resolvedId = resolveId(task_id);
28302
+ const current = getTask2(resolvedId);
28303
+ if (!current)
28304
+ throw new Error(`Task not found: ${task_id}`);
28305
+ const task = updateTask2(resolvedId, { status: "pending", assigned_to: null, version: current.version });
27842
28306
  return { content: [{ type: "text", text: formatTask(task) }] };
27843
28307
  } catch (e) {
27844
28308
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27851,13 +28315,13 @@ No blocked tasks.`,
27851
28315
  minutes: exports_external2.number().describe("Additional minutes to add to estimate")
27852
28316
  }, async ({ task_id, minutes }) => {
27853
28317
  try {
27854
- const { getTask: getTask4, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
28318
+ const { getTask: getTask2, updateTask: updateTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
27855
28319
  const resolvedId = resolveId(task_id);
27856
- const task = getTask4(resolvedId);
28320
+ const task = getTask2(resolvedId);
27857
28321
  if (!task)
27858
28322
  throw new Error(`Task not found: ${task_id}`);
27859
28323
  const currentEstimate = task.estimated_minutes || 0;
27860
- const updated = updateTask2(resolvedId, { estimated_minutes: currentEstimate + minutes });
28324
+ const updated = updateTask2(resolvedId, { estimated_minutes: currentEstimate + minutes, version: task.version });
27861
28325
  return { content: [{ type: "text", text: `Estimate updated: ${currentEstimate} \u2192 ${updated.estimated_minutes} min (+${minutes})` }] };
27862
28326
  } catch (e) {
27863
28327
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27871,10 +28335,10 @@ No blocked tasks.`,
27871
28335
  author: exports_external2.string().optional().describe("Author agent ID or name")
27872
28336
  }, async ({ task_id, body, author }) => {
27873
28337
  try {
27874
- const { createComment } = (init_tasks(), __toCommonJS(exports_tasks));
28338
+ const { addComment: addComment2 } = (init_comments(), __toCommonJS(exports_comments));
27875
28339
  const resolvedId = resolveId(task_id);
27876
28340
  const resolvedAuthor = author ? resolveId(author, "agents") : undefined;
27877
- createComment({ task_id: resolvedId, body, author: resolvedAuthor });
28341
+ addComment2({ task_id: resolvedId, content: body, agent_id: resolvedAuthor });
27878
28342
  return { content: [{ type: "text", text: `Comment added to ${task_id.slice(0, 8)}` }] };
27879
28343
  } catch (e) {
27880
28344
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -27886,11 +28350,11 @@ No blocked tasks.`,
27886
28350
  task_id: exports_external2.string().describe("Task ID")
27887
28351
  }, async ({ task_id }) => {
27888
28352
  try {
27889
- const { listComments: listComments2 } = (init_tasks(), __toCommonJS(exports_tasks));
28353
+ const { listComments: listComments2 } = (init_comments(), __toCommonJS(exports_comments));
27890
28354
  const comments = listComments2(resolveId(task_id));
27891
28355
  if (comments.length === 0)
27892
28356
  return { content: [{ type: "text", text: "No comments." }] };
27893
- const lines = comments.map((c) => `[${c.author || "?"}] ${c.created_at?.slice(0, 16)}: ${c.body}`);
28357
+ const lines = comments.map((c) => `[${c.agent_id || "?"}] ${c.created_at?.slice(0, 16)}: ${c.content}`);
27894
28358
  return { content: [{ type: "text", text: lines.join(`
27895
28359
 
27896
28360
  `) }] };
@@ -27902,7 +28366,7 @@ No blocked tasks.`,
27902
28366
  if (shouldRegisterTool("list_my_tasks")) {
27903
28367
  server.tool("list_my_tasks", "Alias for get_my_tasks.", {
27904
28368
  agent_id: exports_external2.string().optional().describe("Agent ID (defaults to context agent)"),
27905
- status: exports_external2.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
28369
+ status: exports_external2.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
27906
28370
  project_id: exports_external2.string().optional().describe("Filter by project"),
27907
28371
  limit: exports_external2.number().optional()
27908
28372
  }, async ({ agent_id, status, project_id, limit }) => {
@@ -27927,126 +28391,6 @@ No blocked tasks.`,
27927
28391
  }
27928
28392
  });
27929
28393
  }
27930
- if (shouldRegisterTool("list_agents")) {
27931
- server.tool("list_agents", "List all registered agents.", {
27932
- project_id: exports_external2.string().optional().describe("Filter by project"),
27933
- role: exports_external2.string().optional().describe("Filter by global role"),
27934
- capabilities: exports_external2.array(exports_external2.string()).optional().describe("Filter by capabilities")
27935
- }, async ({ project_id, role, capabilities }) => {
27936
- try {
27937
- const { listAgents: listAgents2 } = (init_agents(), __toCommonJS(exports_agents));
27938
- const resolved = {};
27939
- if (project_id)
27940
- resolved.project_id = resolveId(project_id, "projects");
27941
- if (role)
27942
- resolved.role = role;
27943
- if (capabilities)
27944
- resolved.capabilities = capabilities;
27945
- const agents = listAgents2(resolved);
27946
- if (agents.length === 0)
27947
- return { content: [{ type: "text", text: "No agents found." }] };
27948
- const lines = agents.map((a) => `\u25CF ${a.name} [${a.role || "no role"}]${a.capabilities?.length ? ` caps:[${a.capabilities.join(",")}]` : ""}`);
27949
- return { content: [{ type: "text", text: lines.join(`
27950
- `) }] };
27951
- } catch (e) {
27952
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
27953
- }
27954
- });
27955
- }
27956
- if (shouldRegisterTool("get_agent")) {
27957
- server.tool("get_agent", "Get full details for an agent.", {
27958
- agent_id: exports_external2.string().describe("Agent ID or name")
27959
- }, async ({ agent_id }) => {
27960
- try {
27961
- const { getAgentByName: getAgentByName2, getAgent: getAgent2 } = (init_agents(), __toCommonJS(exports_agents));
27962
- const resolvedId = resolveId(agent_id, "agents");
27963
- const agent = agent_id.includes("@") || !resolvedId.startsWith("agent_") ? getAgentByName2(agent_id) : getAgent2(resolvedId);
27964
- if (!agent)
27965
- return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
27966
- const lines = [
27967
- `ID: ${agent.id}`,
27968
- `Name: ${agent.name}`,
27969
- agent.email ? `Email: ${agent.email}` : null,
27970
- agent.title ? `Title: ${agent.title}` : null,
27971
- agent.role ? `Role: ${agent.role}` : null,
27972
- agent.capabilities?.length ? `Capabilities: ${agent.capabilities.join(", ")}` : null,
27973
- agent.last_seen_at ? `Last Seen: ${agent.last_seen_at}` : null
27974
- ].filter(Boolean);
27975
- return { content: [{ type: "text", text: lines.join(`
27976
- `) }] };
27977
- } catch (e) {
27978
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
27979
- }
27980
- });
27981
- }
27982
- if (shouldRegisterTool("update_agent")) {
27983
- server.tool("update_agent", "Update an agent's fields.", {
27984
- agent_id: exports_external2.string().describe("Agent ID or name"),
27985
- name: exports_external2.string().optional(),
27986
- email: exports_external2.string().optional(),
27987
- title: exports_external2.string().optional(),
27988
- role: exports_external2.string().optional(),
27989
- capabilities: exports_external2.array(exports_external2.string()).optional()
27990
- }, async ({ agent_id, ...updates }) => {
27991
- try {
27992
- const { getAgentByName: getAgentByName2, updateAgent: updateAgent2 } = (init_agents(), __toCommonJS(exports_agents));
27993
- const resolvedId = resolveId(agent_id, "agents");
27994
- const agent = agent_id.includes("@") || !resolvedId.startsWith("agent_") ? getAgentByName2(agent_id) : { id: resolvedId };
27995
- updateAgent2(agent.id, updates);
27996
- return { content: [{ type: "text", text: `Agent ${agent.id.slice(0, 8)} updated.` }] };
27997
- } catch (e) {
27998
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
27999
- }
28000
- });
28001
- }
28002
- if (shouldRegisterTool("delete_agent")) {
28003
- server.tool("delete_agent", "Deregister an agent.", {
28004
- agent_id: exports_external2.string().describe("Agent ID or name")
28005
- }, async ({ agent_id }) => {
28006
- try {
28007
- const { getAgentByName: getAgentByName2, deleteAgent: deleteAgent2 } = (init_agents(), __toCommonJS(exports_agents));
28008
- const resolvedId = resolveId(agent_id, "agents");
28009
- const agent = agent_id.includes("@") || !resolvedId.startsWith("agent_") ? getAgentByName2(agent_id) : { id: resolvedId };
28010
- if (!agent)
28011
- return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
28012
- deleteAgent2(agent.id);
28013
- return { content: [{ type: "text", text: `Agent ${agent_id} deleted.` }] };
28014
- } catch (e) {
28015
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
28016
- }
28017
- });
28018
- }
28019
- if (shouldRegisterTool("register_agent")) {
28020
- server.tool("register_agent", "Register a new agent.", {
28021
- name: exports_external2.string().describe("Agent name"),
28022
- email: exports_external2.string().optional(),
28023
- title: exports_external2.string().optional(),
28024
- role: exports_external2.string().optional(),
28025
- capabilities: exports_external2.array(exports_external2.string()).optional()
28026
- }, async ({ name, email, title, role, capabilities }) => {
28027
- try {
28028
- const { registerAgent: registerAgent2 } = (init_agents(), __toCommonJS(exports_agents));
28029
- const agent = registerAgent2({ name, email, title, role, capabilities });
28030
- return { content: [{ type: "text", text: `Agent registered: ${agent.name} (${agent.id})` }] };
28031
- } catch (e) {
28032
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
28033
- }
28034
- });
28035
- }
28036
- if (shouldRegisterTool("heartbeat")) {
28037
- server.tool("heartbeat", "Agent heartbeat \u2014 updates last_seen_at timestamp.", {
28038
- agent_id: exports_external2.string().describe("Agent ID"),
28039
- status: exports_external2.string().optional().describe("Current status message")
28040
- }, async ({ agent_id, status }) => {
28041
- try {
28042
- const { heartbeat } = (init_agents(), __toCommonJS(exports_agents));
28043
- heartbeat(resolveId(agent_id, "agents"), status);
28044
- return { content: [{ type: "text", text: "Heartbeat received." }] };
28045
- } catch (e) {
28046
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
28047
- }
28048
- });
28049
- }
28050
28394
  }
28051
28395
  var init_task_adv_tools = __esm(() => {
28052
28396
  init_zod2();
@@ -28091,6 +28435,11 @@ function registerTaskMetaTools(server, ctx) {
28091
28435
  prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
28092
28436
  search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
28093
28437
  get_my_tasks: "get_my_tasks \u2014 Get tasks for calling agent. Params: agent_id, status, project_id, limit",
28438
+ 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",
28439
+ 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",
28440
+ get_tasks_changed_since: "get_tasks_changed_since \u2014 List tasks changed since an ISO timestamp. Params: since, project_id, task_list_id, limit",
28441
+ get_context: "get_context \u2014 Get session start context. Params: agent_id, project_id, task_list_id, explain_blocked",
28442
+ bootstrap: "bootstrap \u2014 Bootstrap an agent session with queue context. Params: agent_id, project_id, task_list_id, explain_blocked",
28094
28443
  standup: "standup \u2014 Get standup report. Params: agent_id, project_id",
28095
28444
  patrol_tasks: "patrol_tasks \u2014 Scan for task issues. Params: stuck_minutes, confidence_threshold, project_id",
28096
28445
  get_review_queue: "get_review_queue \u2014 Get tasks needing review. Params: project_id, limit",
@@ -28148,9 +28497,20 @@ function registerTaskMetaTools(server, ctx) {
28148
28497
  get_health: "get_health \u2014 Get system health stats",
28149
28498
  approve_task: "approve_task \u2014 Approve a task. Params: task_id, approved_by, notes, version",
28150
28499
  fail_task: "fail_task \u2014 Mark task failed. Params: task_id, reason, agent_id, version",
28500
+ register_agent: "register_agent \u2014 Register an agent. Params: name, description, role, title, capabilities, session_id, working_dir, force",
28501
+ list_agents: "list_agents \u2014 List registered agents. Params: include_archived",
28502
+ get_agent: "get_agent \u2014 Get agent details. Params: agent_id, id, name",
28503
+ update_agent: "update_agent \u2014 Update an agent. Params: agent_id, id, name, description, role, title, level, capabilities, permissions, metadata",
28504
+ delete_agent: "delete_agent \u2014 Archive an agent. Params: agent_id, id, name",
28505
+ unarchive_agent: "unarchive_agent \u2014 Restore an archived agent. Params: agent_id, id, name",
28506
+ heartbeat: "heartbeat \u2014 Update an agent heartbeat. Params: agent_id",
28507
+ release_agent: "release_agent \u2014 Release an agent session/name. Params: agent_id, session_id",
28508
+ set_focus: "set_focus \u2014 Focus an agent on a project. Params: agent_id, project_id, task_list_id",
28509
+ get_focus: "get_focus \u2014 Get current agent focus. Params: agent_id",
28510
+ unfocus: "unfocus \u2014 Clear agent focus. Params: agent_id",
28511
+ suggest_agent_name: "suggest_agent_name \u2014 Suggest available agent names. Params: working_dir",
28151
28512
  get_org_chart: "get_org_chart \u2014 Get global org chart. Params: format",
28152
28513
  set_reports_to: "set_reports_to \u2014 Set org hierarchy. Params: agent_id, reports_to",
28153
- sync: "sync \u2014 Sync from external source. Params: source, source_id, project_id, options",
28154
28514
  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",
28155
28515
  migrate_pg: "migrate_pg \u2014 Apply PostgreSQL migrations. Params: connection_string"
28156
28516
  };
@@ -28168,6 +28528,74 @@ var init_task_meta_tools = __esm(() => {
28168
28528
  init_zod2();
28169
28529
  });
28170
28530
 
28531
+ // src/db/file-locks.ts
28532
+ var exports_file_locks = {};
28533
+ __export(exports_file_locks, {
28534
+ unlockFile: () => unlockFile,
28535
+ lockFile: () => lockFile,
28536
+ listFileLocks: () => listFileLocks,
28537
+ forceUnlockFile: () => forceUnlockFile,
28538
+ cleanExpiredFileLocks: () => cleanExpiredFileLocks,
28539
+ checkFileLock: () => checkFileLock,
28540
+ FILE_LOCK_DEFAULT_TTL_SECONDS: () => FILE_LOCK_DEFAULT_TTL_SECONDS
28541
+ });
28542
+ function expiresAt(ttlSeconds) {
28543
+ return new Date(Date.now() + ttlSeconds * 1000).toISOString();
28544
+ }
28545
+ function cleanExpiredFileLocks(db) {
28546
+ const d = db || getDatabase();
28547
+ const result = d.run("DELETE FROM file_locks WHERE expires_at <= ?", [now()]);
28548
+ return result.changes;
28549
+ }
28550
+ function lockFile(input, db) {
28551
+ const d = db || getDatabase();
28552
+ const ttl = input.ttl_seconds ?? FILE_LOCK_DEFAULT_TTL_SECONDS;
28553
+ const expiry = expiresAt(ttl);
28554
+ const timestamp = now();
28555
+ cleanExpiredFileLocks(d);
28556
+ const existing = d.query("SELECT * FROM file_locks WHERE path = ?").get(input.path);
28557
+ if (existing) {
28558
+ if (existing.agent_id === input.agent_id) {
28559
+ d.run("UPDATE file_locks SET expires_at = ?, task_id = COALESCE(?, task_id) WHERE id = ?", [expiry, input.task_id ?? null, existing.id]);
28560
+ return d.query("SELECT * FROM file_locks WHERE id = ?").get(existing.id);
28561
+ }
28562
+ throw new LockError(input.path, existing.agent_id);
28563
+ }
28564
+ const id = uuid();
28565
+ 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]);
28566
+ return d.query("SELECT * FROM file_locks WHERE id = ?").get(id);
28567
+ }
28568
+ function unlockFile(path, agentId, db) {
28569
+ const d = db || getDatabase();
28570
+ cleanExpiredFileLocks(d);
28571
+ const result = d.run("DELETE FROM file_locks WHERE path = ? AND agent_id = ?", [path, agentId]);
28572
+ return result.changes > 0;
28573
+ }
28574
+ function checkFileLock(path, db) {
28575
+ const d = db || getDatabase();
28576
+ cleanExpiredFileLocks(d);
28577
+ return d.query("SELECT * FROM file_locks WHERE path = ?").get(path);
28578
+ }
28579
+ function listFileLocks(agentId, db) {
28580
+ const d = db || getDatabase();
28581
+ cleanExpiredFileLocks(d);
28582
+ if (agentId) {
28583
+ return d.query("SELECT * FROM file_locks WHERE agent_id = ? ORDER BY created_at DESC").all(agentId);
28584
+ }
28585
+ return d.query("SELECT * FROM file_locks ORDER BY created_at DESC").all();
28586
+ }
28587
+ function forceUnlockFile(path, db) {
28588
+ const d = db || getDatabase();
28589
+ const result = d.run("DELETE FROM file_locks WHERE path = ?", [path]);
28590
+ return result.changes > 0;
28591
+ }
28592
+ var FILE_LOCK_DEFAULT_TTL_SECONDS;
28593
+ var init_file_locks = __esm(() => {
28594
+ init_database();
28595
+ init_types();
28596
+ FILE_LOCK_DEFAULT_TTL_SECONDS = 30 * 60;
28597
+ });
28598
+
28171
28599
  // src/mcp/tools/task-resources.ts
28172
28600
  function registerTaskResources(server, ctx) {
28173
28601
  const { shouldRegisterTool, resolveId, formatError } = ctx;
@@ -28193,7 +28621,7 @@ function registerTaskResources(server, ctx) {
28193
28621
  note: exports_external2.string().optional().describe("Note about why this file is linked")
28194
28622
  }, async ({ task_id, path, paths: multiplePaths, status, agent_id, note }) => {
28195
28623
  try {
28196
- const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (()=>{throw new Error("Cannot require module "+"../db/task-files.js");})();
28624
+ const { addTaskFile: addTaskFile2, bulkAddTaskFiles: bulkAddTaskFiles2, detectFileConflicts: detectFileConflicts2 } = (init_task_files(), __toCommonJS(exports_task_files));
28197
28625
  const resolvedId = resolveId(task_id);
28198
28626
  let addedFiles;
28199
28627
  if (multiplePaths && multiplePaths.length > 0) {
@@ -28237,7 +28665,7 @@ function registerTaskResources(server, ctx) {
28237
28665
  if (shouldRegisterTool("list_task_files")) {
28238
28666
  server.tool("list_task_files", "List all files linked to a task.", { task_id: exports_external2.string().describe("Task ID") }, async ({ task_id }) => {
28239
28667
  try {
28240
- const { listTaskFiles: listTaskFiles2 } = (()=>{throw new Error("Cannot require module "+"../db/task-files.js");})();
28668
+ const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
28241
28669
  const resolvedId = resolveId(task_id);
28242
28670
  const files = listTaskFiles2(resolvedId);
28243
28671
  if (files.length === 0)
@@ -28254,7 +28682,7 @@ ${lines.join(`
28254
28682
  if (shouldRegisterTool("find_tasks_by_file")) {
28255
28683
  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_external2.string().describe("File path to search for") }, async ({ path }) => {
28256
28684
  try {
28257
- const { findTasksByFile: findTasksByFile2 } = (()=>{throw new Error("Cannot require module "+"../db/task-files.js");})();
28685
+ const { findTasksByFile: findTasksByFile2 } = (init_task_files(), __toCommonJS(exports_task_files));
28258
28686
  const files = findTasksByFile2(path);
28259
28687
  if (files.length === 0)
28260
28688
  return { content: [{ type: "text", text: `No tasks linked to ${path}` }] };
@@ -28274,7 +28702,7 @@ ${lines.join(`
28274
28702
  min_edits: exports_external2.number().optional().describe("Minimum edit count to include (default: 1)")
28275
28703
  }, async ({ limit, project_id, min_edits }) => {
28276
28704
  try {
28277
- const { getFileHeatMap: getFileHeatMap2 } = (()=>{throw new Error("Cannot require module "+"../db/task-files.js");})();
28705
+ const { getFileHeatMap: getFileHeatMap2 } = (init_task_files(), __toCommonJS(exports_task_files));
28278
28706
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
28279
28707
  const results = getFileHeatMap2({ limit, project_id: resolvedProjectId, min_edits });
28280
28708
  return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
@@ -28288,7 +28716,7 @@ ${lines.join(`
28288
28716
  paths: exports_external2.array(exports_external2.string()).describe("Array of file paths to check")
28289
28717
  }, async ({ paths }) => {
28290
28718
  try {
28291
- const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (()=>{throw new Error("Cannot require module "+"../db/task-files.js");})();
28719
+ const { bulkFindTasksByFiles: bulkFindTasksByFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
28292
28720
  const results = bulkFindTasksByFiles2(paths);
28293
28721
  return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
28294
28722
  } catch (e) {
@@ -28301,11 +28729,11 @@ ${lines.join(`
28301
28729
  project_id: exports_external2.string().optional().describe("Filter by project")
28302
28730
  }, async ({ project_id }) => {
28303
28731
  try {
28304
- const { listActiveFiles: listActiveFiles2 } = (()=>{throw new Error("Cannot require module "+"../db/task-files.js");})();
28732
+ const { listActiveFiles: listActiveFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
28305
28733
  let files = listActiveFiles2();
28306
28734
  if (project_id) {
28307
28735
  const pid = resolveId(project_id, "projects");
28308
- const db = (()=>{throw new Error("Cannot require module "+"../db/database.js");})().getDatabase();
28736
+ const db = (init_database(), __toCommonJS(exports_database)).getDatabase();
28309
28737
  files = db.query(`
28310
28738
  SELECT
28311
28739
  tf.path,
@@ -28387,8 +28815,8 @@ ${lines.join(`
28387
28815
  ttl_seconds: exports_external2.number().optional().describe("Lock TTL in seconds (default: 1800 = 30 min)")
28388
28816
  }, async ({ path, agent_id, task_id, ttl_seconds }) => {
28389
28817
  try {
28390
- const { lockFile } = (()=>{throw new Error("Cannot require module "+"../db/file-locks.js");})();
28391
- const lock = lockFile({ path, agent_id, task_id, ttl_seconds });
28818
+ const { lockFile: lockFile2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
28819
+ const lock = lockFile2({ path, agent_id, task_id, ttl_seconds });
28392
28820
  return { content: [{ type: "text", text: JSON.stringify(lock, null, 2) }] };
28393
28821
  } catch (e) {
28394
28822
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -28401,8 +28829,8 @@ ${lines.join(`
28401
28829
  agent_id: exports_external2.string().describe("Agent releasing the lock (must be the lock holder)")
28402
28830
  }, async ({ path, agent_id }) => {
28403
28831
  try {
28404
- const { unlockFile } = (()=>{throw new Error("Cannot require module "+"../db/file-locks.js");})();
28405
- const released = unlockFile(path, agent_id);
28832
+ const { unlockFile: unlockFile2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
28833
+ const released = unlockFile2(path, agent_id);
28406
28834
  return { content: [{ type: "text", text: JSON.stringify({ released, path }) }] };
28407
28835
  } catch (e) {
28408
28836
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -28414,8 +28842,8 @@ ${lines.join(`
28414
28842
  path: exports_external2.string().describe("File path to check")
28415
28843
  }, async ({ path }) => {
28416
28844
  try {
28417
- const { checkFileLock } = (()=>{throw new Error("Cannot require module "+"../db/file-locks.js");})();
28418
- const lock = checkFileLock(path);
28845
+ const { checkFileLock: checkFileLock2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
28846
+ const lock = checkFileLock2(path);
28419
28847
  if (!lock)
28420
28848
  return { content: [{ type: "text", text: JSON.stringify({ path, locked: false }) }] };
28421
28849
  return { content: [{ type: "text", text: JSON.stringify({ path, locked: true, ...lock }) }] };
@@ -28429,8 +28857,8 @@ ${lines.join(`
28429
28857
  agent_id: exports_external2.string().optional().describe("Filter locks by agent")
28430
28858
  }, async ({ agent_id }) => {
28431
28859
  try {
28432
- const { listFileLocks } = (()=>{throw new Error("Cannot require module "+"../db/file-locks.js");})();
28433
- const locks = listFileLocks(agent_id);
28860
+ const { listFileLocks: listFileLocks2 } = (init_file_locks(), __toCommonJS(exports_file_locks));
28861
+ const locks = listFileLocks2(agent_id);
28434
28862
  return { content: [{ type: "text", text: JSON.stringify(locks, null, 2) }] };
28435
28863
  } catch (e) {
28436
28864
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -28446,6 +28874,533 @@ var init_task_resources = __esm(() => {
28446
28874
  init_task_commits();
28447
28875
  });
28448
28876
 
28877
+ // src/db/kg.ts
28878
+ var exports_kg = {};
28879
+ __export(exports_kg, {
28880
+ syncKgEdges: () => syncKgEdges,
28881
+ removeKgEdges: () => removeKgEdges,
28882
+ getRelated: () => getRelated,
28883
+ getImpactAnalysis: () => getImpactAnalysis,
28884
+ getCriticalPath: () => getCriticalPath,
28885
+ findPath: () => findPath,
28886
+ addKgEdge: () => addKgEdge
28887
+ });
28888
+ function rowToEdge(row) {
28889
+ return {
28890
+ ...row,
28891
+ metadata: JSON.parse(row.metadata || "{}")
28892
+ };
28893
+ }
28894
+ function syncKgEdges(db) {
28895
+ const d = db || getDatabase();
28896
+ let synced = 0;
28897
+ const tx = d.transaction(() => {
28898
+ const deps = d.query("SELECT task_id, depends_on FROM task_dependencies").all();
28899
+ for (const dep of deps) {
28900
+ synced += upsertEdge(d, dep.task_id, "task", dep.depends_on, "task", "depends_on");
28901
+ }
28902
+ const assignments = d.query("SELECT id, assigned_to FROM tasks WHERE assigned_to IS NOT NULL").all();
28903
+ for (const a of assignments) {
28904
+ synced += upsertEdge(d, a.id, "task", a.assigned_to, "agent", "assigned_to");
28905
+ }
28906
+ const agents = d.query("SELECT id, reports_to FROM agents WHERE reports_to IS NOT NULL").all();
28907
+ for (const a of agents) {
28908
+ synced += upsertEdge(d, a.id, "agent", a.reports_to, "agent", "reports_to");
28909
+ }
28910
+ const files = d.query("SELECT task_id, path FROM task_files WHERE status != 'removed'").all();
28911
+ for (const f of files) {
28912
+ synced += upsertEdge(d, f.task_id, "task", f.path, "file", "references_file");
28913
+ }
28914
+ const taskProjects = d.query("SELECT id, project_id FROM tasks WHERE project_id IS NOT NULL").all();
28915
+ for (const tp of taskProjects) {
28916
+ synced += upsertEdge(d, tp.id, "task", tp.project_id, "project", "in_project");
28917
+ }
28918
+ const taskPlans = d.query("SELECT id, plan_id FROM tasks WHERE plan_id IS NOT NULL").all();
28919
+ for (const tp of taskPlans) {
28920
+ synced += upsertEdge(d, tp.id, "task", tp.plan_id, "plan", "in_plan");
28921
+ }
28922
+ try {
28923
+ const rels = d.query("SELECT source_task_id, target_task_id, relationship_type FROM task_relationships").all();
28924
+ for (const r of rels) {
28925
+ synced += upsertEdge(d, r.source_task_id, "task", r.target_task_id, "task", r.relationship_type);
28926
+ }
28927
+ } catch {}
28928
+ });
28929
+ tx();
28930
+ return { synced };
28931
+ }
28932
+ function upsertEdge(d, sourceId, sourceType, targetId, targetType, relationType, weight = 1) {
28933
+ try {
28934
+ d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
28935
+ VALUES (?, ?, ?, ?, ?, ?, ?, '{}', ?)`, [uuid(), sourceId, sourceType, targetId, targetType, relationType, weight, now()]);
28936
+ return 1;
28937
+ } catch {
28938
+ return 0;
28939
+ }
28940
+ }
28941
+ function getRelated(entityId, opts, db) {
28942
+ const d = db || getDatabase();
28943
+ const direction = opts?.direction || "both";
28944
+ const conditions = [];
28945
+ const params = [];
28946
+ if (direction === "outgoing" || direction === "both") {
28947
+ conditions.push("source_id = ?");
28948
+ params.push(entityId);
28949
+ }
28950
+ if (direction === "incoming" || direction === "both") {
28951
+ conditions.push("target_id = ?");
28952
+ params.push(entityId);
28953
+ }
28954
+ let sql = `SELECT * FROM kg_edges WHERE (${conditions.join(" OR ")})`;
28955
+ if (opts?.relation_type) {
28956
+ sql += " AND relation_type = ?";
28957
+ params.push(opts.relation_type);
28958
+ }
28959
+ if (opts?.entity_type) {
28960
+ sql += " AND (source_type = ? OR target_type = ?)";
28961
+ params.push(opts.entity_type, opts.entity_type);
28962
+ }
28963
+ sql += " ORDER BY weight DESC, created_at DESC";
28964
+ if (opts?.limit) {
28965
+ sql += " LIMIT ?";
28966
+ params.push(opts.limit);
28967
+ }
28968
+ return d.query(sql).all(...params).map(rowToEdge);
28969
+ }
28970
+ function findPath(sourceId, targetId, opts, db) {
28971
+ const d = db || getDatabase();
28972
+ const maxDepth = opts?.max_depth || 5;
28973
+ const visited = new Set;
28974
+ const queue = [{ id: sourceId, path: [] }];
28975
+ const results = [];
28976
+ visited.add(sourceId);
28977
+ while (queue.length > 0) {
28978
+ const current = queue.shift();
28979
+ if (current.path.length >= maxDepth)
28980
+ continue;
28981
+ let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
28982
+ const params = [current.id];
28983
+ if (opts?.relation_types && opts.relation_types.length > 0) {
28984
+ const placeholders = opts.relation_types.map(() => "?").join(",");
28985
+ sql += ` AND relation_type IN (${placeholders})`;
28986
+ params.push(...opts.relation_types);
28987
+ }
28988
+ const edges = d.query(sql).all(...params).map(rowToEdge);
28989
+ for (const edge of edges) {
28990
+ const nextId = edge.target_id;
28991
+ const newPath = [...current.path, edge];
28992
+ if (nextId === targetId) {
28993
+ results.push(newPath);
28994
+ if (results.length >= 3)
28995
+ return results;
28996
+ continue;
28997
+ }
28998
+ if (!visited.has(nextId)) {
28999
+ visited.add(nextId);
29000
+ queue.push({ id: nextId, path: newPath });
29001
+ }
29002
+ }
29003
+ }
29004
+ return results;
29005
+ }
29006
+ function getImpactAnalysis(entityId, opts, db) {
29007
+ const d = db || getDatabase();
29008
+ const maxDepth = opts?.max_depth || 3;
29009
+ const results = [];
29010
+ const visited = new Set;
29011
+ visited.add(entityId);
29012
+ const queue = [{ id: entityId, depth: 0 }];
29013
+ while (queue.length > 0) {
29014
+ const current = queue.shift();
29015
+ if (current.depth >= maxDepth)
29016
+ continue;
29017
+ let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
29018
+ const params = [current.id];
29019
+ if (opts?.relation_types && opts.relation_types.length > 0) {
29020
+ const placeholders = opts.relation_types.map(() => "?").join(",");
29021
+ sql += ` AND relation_type IN (${placeholders})`;
29022
+ params.push(...opts.relation_types);
29023
+ }
29024
+ const edges = d.query(sql).all(...params).map(rowToEdge);
29025
+ for (const edge of edges) {
29026
+ if (!visited.has(edge.target_id)) {
29027
+ visited.add(edge.target_id);
29028
+ results.push({
29029
+ entity_id: edge.target_id,
29030
+ entity_type: edge.target_type,
29031
+ depth: current.depth + 1,
29032
+ relation: edge.relation_type
29033
+ });
29034
+ queue.push({ id: edge.target_id, depth: current.depth + 1 });
29035
+ }
29036
+ }
29037
+ }
29038
+ return results;
29039
+ }
29040
+ function getCriticalPath(opts, db) {
29041
+ const d = db || getDatabase();
29042
+ let sql = `SELECT source_id, target_id FROM kg_edges WHERE relation_type = 'depends_on'`;
29043
+ const params = [];
29044
+ if (opts?.project_id) {
29045
+ sql += ` AND source_id IN (SELECT id FROM tasks WHERE project_id = ?)`;
29046
+ params.push(opts.project_id);
29047
+ }
29048
+ const edges = d.query(sql).all(...params);
29049
+ const blocks = new Map;
29050
+ for (const e of edges) {
29051
+ if (!blocks.has(e.target_id))
29052
+ blocks.set(e.target_id, new Set);
29053
+ blocks.get(e.target_id).add(e.source_id);
29054
+ }
29055
+ const results = [];
29056
+ for (const [taskId] of blocks) {
29057
+ const visited = new Set;
29058
+ const q = [taskId];
29059
+ let maxDepth = 0;
29060
+ let depth = 0;
29061
+ let levelSize = q.length;
29062
+ while (q.length > 0) {
29063
+ const node = q.shift();
29064
+ levelSize--;
29065
+ const downstream = blocks.get(node);
29066
+ if (downstream) {
29067
+ for (const d2 of downstream) {
29068
+ if (!visited.has(d2)) {
29069
+ visited.add(d2);
29070
+ q.push(d2);
29071
+ }
29072
+ }
29073
+ }
29074
+ if (levelSize === 0) {
29075
+ depth++;
29076
+ maxDepth = Math.max(maxDepth, depth);
29077
+ levelSize = q.length;
29078
+ }
29079
+ }
29080
+ if (visited.size > 0) {
29081
+ results.push({ task_id: taskId, blocking_count: visited.size, depth: maxDepth });
29082
+ }
29083
+ }
29084
+ results.sort((a, b) => b.blocking_count - a.blocking_count);
29085
+ return results.slice(0, opts?.limit || 20);
29086
+ }
29087
+ function addKgEdge(sourceId, sourceType, targetId, targetType, relationType, weight = 1, metadata, db) {
29088
+ const d = db || getDatabase();
29089
+ const id = uuid();
29090
+ const timestamp = now();
29091
+ d.run(`INSERT OR IGNORE INTO kg_edges (id, source_id, source_type, target_id, target_type, relation_type, weight, metadata, created_at)
29092
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, sourceId, sourceType, targetId, targetType, relationType, weight, JSON.stringify(metadata || {}), timestamp]);
29093
+ return { id, source_id: sourceId, source_type: sourceType, target_id: targetId, target_type: targetType, relation_type: relationType, weight, metadata: metadata || {}, created_at: timestamp };
29094
+ }
29095
+ function removeKgEdges(sourceId, targetId, relationType, db) {
29096
+ const d = db || getDatabase();
29097
+ if (relationType) {
29098
+ return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ? AND relation_type = ?", [sourceId, targetId, relationType]).changes;
29099
+ }
29100
+ return d.run("DELETE FROM kg_edges WHERE source_id = ? AND target_id = ?", [sourceId, targetId]).changes;
29101
+ }
29102
+ var init_kg = __esm(() => {
29103
+ init_database();
29104
+ });
29105
+
29106
+ // src/db/project-agent-roles.ts
29107
+ var exports_project_agent_roles = {};
29108
+ __export(exports_project_agent_roles, {
29109
+ setProjectAgentRole: () => setProjectAgentRole,
29110
+ removeProjectAgentRole: () => removeProjectAgentRole,
29111
+ listProjectAgentRoles: () => listProjectAgentRoles,
29112
+ getProjectOrgChart: () => getProjectOrgChart,
29113
+ getAgentProjectRoles: () => getAgentProjectRoles
29114
+ });
29115
+ function rowToRole(row) {
29116
+ return { ...row, is_lead: row.is_lead === 1 };
29117
+ }
29118
+ function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
29119
+ const d = db || getDatabase();
29120
+ const existing = d.query("SELECT * FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?").get(projectId, agentId, role);
29121
+ if (existing) {
29122
+ d.run("UPDATE project_agent_roles SET is_lead = ? WHERE id = ?", [isLead ? 1 : 0, existing.id]);
29123
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
29124
+ }
29125
+ const id = uuid();
29126
+ 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()]);
29127
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
29128
+ }
29129
+ function removeProjectAgentRole(projectId, agentId, role, db) {
29130
+ const d = db || getDatabase();
29131
+ if (role) {
29132
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?", [projectId, agentId, role]).changes;
29133
+ }
29134
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ?", [projectId, agentId]).changes;
29135
+ }
29136
+ function listProjectAgentRoles(projectId, db) {
29137
+ const d = db || getDatabase();
29138
+ return d.query("SELECT * FROM project_agent_roles WHERE project_id = ? ORDER BY role, created_at").all(projectId).map(rowToRole);
29139
+ }
29140
+ function getAgentProjectRoles(agentId, db) {
29141
+ const d = db || getDatabase();
29142
+ return d.query("SELECT * FROM project_agent_roles WHERE agent_id = ? ORDER BY project_id, role").all(agentId).map(rowToRole);
29143
+ }
29144
+ function getProjectOrgChart(projectId, opts, db) {
29145
+ const d = db || getDatabase();
29146
+ const globalTree = getOrgChart(d);
29147
+ const projectRoles = listProjectAgentRoles(projectId, d);
29148
+ const rolesByAgent = new Map;
29149
+ for (const pr of projectRoles) {
29150
+ if (!rolesByAgent.has(pr.agent_id))
29151
+ rolesByAgent.set(pr.agent_id, { roles: [], isLead: false });
29152
+ const entry = rolesByAgent.get(pr.agent_id);
29153
+ entry.roles.push(pr.role);
29154
+ if (pr.is_lead)
29155
+ entry.isLead = true;
29156
+ }
29157
+ function augmentTree(nodes) {
29158
+ return nodes.map((n) => {
29159
+ const override = rolesByAgent.get(n.agent.id);
29160
+ return {
29161
+ ...n,
29162
+ reports: augmentTree(n.reports),
29163
+ project_roles: override?.roles ?? [],
29164
+ is_project_lead: override?.isLead ?? false
29165
+ };
29166
+ }).filter((n) => {
29167
+ if (!opts?.filter_to_project)
29168
+ return true;
29169
+ const hasRole = n.project_roles.length > 0;
29170
+ const hasDescendant = n.reports.length > 0;
29171
+ return hasRole || hasDescendant;
29172
+ });
29173
+ }
29174
+ return augmentTree(globalTree);
29175
+ }
29176
+ var init_project_agent_roles = __esm(() => {
29177
+ init_database();
29178
+ init_agents();
29179
+ });
29180
+
29181
+ // src/db/patrol.ts
29182
+ var exports_patrol = {};
29183
+ __export(exports_patrol, {
29184
+ patrolTasks: () => patrolTasks,
29185
+ getReviewQueue: () => getReviewQueue
29186
+ });
29187
+ function rowToTask3(row) {
29188
+ return {
29189
+ ...row,
29190
+ status: row.status,
29191
+ priority: row.priority,
29192
+ tags: JSON.parse(row.tags || "[]"),
29193
+ metadata: JSON.parse(row.metadata || "{}"),
29194
+ requires_approval: Boolean(row.requires_approval)
29195
+ };
29196
+ }
29197
+ function patrolTasks(opts, db) {
29198
+ const d = db || getDatabase();
29199
+ const stuckMinutes = opts?.stuck_minutes || 60;
29200
+ const confidenceThreshold = opts?.confidence_threshold || 0.5;
29201
+ const issues = [];
29202
+ let projectFilter = "";
29203
+ const projectParams = [];
29204
+ if (opts?.project_id) {
29205
+ projectFilter = " AND project_id = ?";
29206
+ projectParams.push(opts.project_id);
29207
+ }
29208
+ const stuckCutoff = new Date(Date.now() - stuckMinutes * 60 * 1000).toISOString();
29209
+ const stuckRows = d.query(`SELECT * FROM tasks WHERE status = 'in_progress' AND updated_at < ?${projectFilter} ORDER BY updated_at ASC`).all(stuckCutoff, ...projectParams);
29210
+ for (const row of stuckRows) {
29211
+ const task = rowToTask3(row);
29212
+ const minutesStuck = Math.round((Date.now() - new Date(task.updated_at).getTime()) / 60000);
29213
+ issues.push({
29214
+ type: "stuck",
29215
+ task_id: task.id,
29216
+ task_title: task.title,
29217
+ severity: minutesStuck > 480 ? "critical" : minutesStuck > 120 ? "high" : "medium",
29218
+ detail: `In progress for ${minutesStuck} minutes without update`
29219
+ });
29220
+ }
29221
+ 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);
29222
+ for (const row of lowConfRows) {
29223
+ const task = rowToTask3(row);
29224
+ issues.push({
29225
+ type: "low_confidence",
29226
+ task_id: task.id,
29227
+ task_title: task.title,
29228
+ severity: (task.confidence ?? 0) < 0.3 ? "high" : "medium",
29229
+ detail: `Completed with confidence ${task.confidence} (threshold: ${confidenceThreshold})`
29230
+ });
29231
+ }
29232
+ 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();
29233
+ for (const row of orphanedRows) {
29234
+ const task = rowToTask3(row);
29235
+ issues.push({
29236
+ type: "orphaned",
29237
+ task_id: task.id,
29238
+ task_title: task.title,
29239
+ severity: "low",
29240
+ detail: "Pending task with no project, no agent, and no assignee"
29241
+ });
29242
+ }
29243
+ 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);
29244
+ for (const row of needsReviewRows) {
29245
+ const task = rowToTask3(row);
29246
+ issues.push({
29247
+ type: "needs_review",
29248
+ task_id: task.id,
29249
+ task_title: task.title,
29250
+ severity: "medium",
29251
+ detail: "Completed but requires approval \u2014 not yet reviewed"
29252
+ });
29253
+ }
29254
+ const pendingWithDeps = d.query(`SELECT t.* FROM tasks t
29255
+ WHERE t.status = 'pending'${projectFilter}
29256
+ AND t.id IN (SELECT task_id FROM task_dependencies)`).all(...projectParams);
29257
+ for (const row of pendingWithDeps) {
29258
+ const deps = d.query(`SELECT d.depends_on, t.status FROM task_dependencies d
29259
+ JOIN tasks t ON t.id = d.depends_on
29260
+ WHERE d.task_id = ?`).all(row.id);
29261
+ if (deps.length > 0 && deps.every((dep) => dep.status === "failed" || dep.status === "cancelled")) {
29262
+ const task = rowToTask3(row);
29263
+ issues.push({
29264
+ type: "zombie_blocked",
29265
+ task_id: task.id,
29266
+ task_title: task.title,
29267
+ severity: "high",
29268
+ detail: `Blocked by ${deps.length} task(s) that are all failed/cancelled \u2014 will never unblock`
29269
+ });
29270
+ }
29271
+ }
29272
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
29273
+ issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
29274
+ return {
29275
+ issues,
29276
+ total_issues: issues.length,
29277
+ scanned_at: new Date().toISOString()
29278
+ };
29279
+ }
29280
+ function getReviewQueue(opts, db) {
29281
+ const d = db || getDatabase();
29282
+ let sql = `SELECT * FROM tasks WHERE status = 'completed' AND (
29283
+ (requires_approval = 1 AND approved_by IS NULL)
29284
+ OR (confidence IS NOT NULL AND confidence < 0.5)
29285
+ )`;
29286
+ const params = [];
29287
+ if (opts?.project_id) {
29288
+ sql += " AND project_id = ?";
29289
+ params.push(opts.project_id);
29290
+ }
29291
+ sql += " ORDER BY CASE WHEN requires_approval = 1 AND approved_by IS NULL THEN 0 ELSE 1 END, confidence ASC";
29292
+ if (opts?.limit) {
29293
+ sql += " LIMIT ?";
29294
+ params.push(opts.limit);
29295
+ }
29296
+ return d.query(sql).all(...params).map(rowToTask3);
29297
+ }
29298
+ var init_patrol = __esm(() => {
29299
+ init_database();
29300
+ });
29301
+
29302
+ // src/db/agent-metrics.ts
29303
+ var exports_agent_metrics = {};
29304
+ __export(exports_agent_metrics, {
29305
+ scoreTask: () => scoreTask,
29306
+ getLeaderboard: () => getLeaderboard,
29307
+ getAgentMetrics: () => getAgentMetrics
29308
+ });
29309
+ function getAgentMetrics(agentId, opts, db) {
29310
+ const d = db || getDatabase();
29311
+ const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
29312
+ if (!agent)
29313
+ return null;
29314
+ let projectFilter = "";
29315
+ const params = [agent.id, agent.id];
29316
+ if (opts?.project_id) {
29317
+ projectFilter = " AND project_id = ?";
29318
+ params.push(opts.project_id);
29319
+ }
29320
+ const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}`).get(...params).count;
29321
+ const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'failed'${projectFilter}`).get(...params).count;
29322
+ const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'in_progress'${projectFilter}`).get(...params).count;
29323
+ const total = completed + failed;
29324
+ const completionRate = total > 0 ? completed / total : 0;
29325
+ const avgTime = d.query(`SELECT AVG(
29326
+ (julianday(completed_at) - julianday(created_at)) * 24 * 60
29327
+ ) as avg_minutes
29328
+ FROM tasks
29329
+ WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
29330
+ const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
29331
+ FROM tasks
29332
+ WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
29333
+ const reviewTasks = d.query(`SELECT metadata FROM tasks
29334
+ WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}
29335
+ AND metadata LIKE '%_review_score%'`).all(...params);
29336
+ let reviewScoreAvg = null;
29337
+ if (reviewTasks.length > 0) {
29338
+ let total2 = 0;
29339
+ let count = 0;
29340
+ for (const row of reviewTasks) {
29341
+ try {
29342
+ const meta = JSON.parse(row.metadata);
29343
+ if (typeof meta._review_score === "number") {
29344
+ total2 += meta._review_score;
29345
+ count++;
29346
+ }
29347
+ } catch {}
29348
+ }
29349
+ if (count > 0)
29350
+ reviewScoreAvg = total2 / count;
29351
+ }
29352
+ const speedScore = avgTime?.avg_minutes != null ? Math.max(0, 1 - avgTime.avg_minutes / (60 * 24)) : 0.5;
29353
+ const confidenceScore = avgConf?.avg_confidence ?? 0.5;
29354
+ const volumeScore = Math.min(1, completed / 50);
29355
+ const compositeScore = completionRate * 0.3 + speedScore * 0.2 + confidenceScore * 0.3 + volumeScore * 0.2;
29356
+ return {
29357
+ agent_id: agent.id,
29358
+ agent_name: agent.name,
29359
+ tasks_completed: completed,
29360
+ tasks_failed: failed,
29361
+ tasks_in_progress: inProgress,
29362
+ completion_rate: Math.round(completionRate * 1000) / 1000,
29363
+ avg_completion_minutes: avgTime?.avg_minutes != null ? Math.round(avgTime.avg_minutes * 10) / 10 : null,
29364
+ avg_confidence: avgConf?.avg_confidence != null ? Math.round(avgConf.avg_confidence * 1000) / 1000 : null,
29365
+ review_score_avg: reviewScoreAvg != null ? Math.round(reviewScoreAvg * 1000) / 1000 : null,
29366
+ composite_score: Math.round(compositeScore * 1000) / 1000
29367
+ };
29368
+ }
29369
+ function getLeaderboard(opts, db) {
29370
+ const d = db || getDatabase();
29371
+ const agents = d.query("SELECT id FROM agents ORDER BY name").all();
29372
+ const entries = [];
29373
+ for (const agent of agents) {
29374
+ const metrics = getAgentMetrics(agent.id, { project_id: opts?.project_id }, d);
29375
+ if (metrics && (metrics.tasks_completed > 0 || metrics.tasks_failed > 0 || metrics.tasks_in_progress > 0)) {
29376
+ entries.push(metrics);
29377
+ }
29378
+ }
29379
+ entries.sort((a, b) => b.composite_score - a.composite_score);
29380
+ const limit = opts?.limit || 20;
29381
+ return entries.slice(0, limit).map((entry, idx) => ({
29382
+ ...entry,
29383
+ rank: idx + 1
29384
+ }));
29385
+ }
29386
+ function scoreTask(taskId, score, reviewerId, db) {
29387
+ const d = db || getDatabase();
29388
+ if (score < 0 || score > 1)
29389
+ throw new Error("Score must be between 0 and 1");
29390
+ const task = d.query("SELECT metadata FROM tasks WHERE id = ?").get(taskId);
29391
+ if (!task)
29392
+ throw new Error(`Task not found: ${taskId}`);
29393
+ const metadata = JSON.parse(task.metadata || "{}");
29394
+ metadata._review_score = score;
29395
+ if (reviewerId)
29396
+ metadata._reviewed_by = reviewerId;
29397
+ metadata._reviewed_at = now();
29398
+ d.run("UPDATE tasks SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), now(), taskId]);
29399
+ }
29400
+ var init_agent_metrics = __esm(() => {
29401
+ init_database();
29402
+ });
29403
+
28449
29404
  // src/mcp/tools/task-rel-tools.ts
28450
29405
  function registerTaskRelTools(server, ctx) {
28451
29406
  const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
@@ -28460,7 +29415,7 @@ function registerTaskRelTools(server, ctx) {
28460
29415
  next_steps: exports_external2.array(exports_external2.string()).optional().describe("Recommended next actions")
28461
29416
  }, async ({ agent_id, project_id, summary, completed, in_progress, blockers, next_steps }) => {
28462
29417
  try {
28463
- const { createHandoff: createHandoff2 } = (()=>{throw new Error("Cannot require module "+"../db/handoffs.js");})();
29418
+ const { createHandoff: createHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
28464
29419
  const handoff = createHandoff2({
28465
29420
  agent_id,
28466
29421
  project_id: project_id ? resolveId(project_id, "projects") : undefined,
@@ -28482,7 +29437,7 @@ function registerTaskRelTools(server, ctx) {
28482
29437
  project_id: exports_external2.string().optional().describe("Filter by project")
28483
29438
  }, async ({ agent_id, project_id }) => {
28484
29439
  try {
28485
- const { getLatestHandoff: getLatestHandoff2 } = (()=>{throw new Error("Cannot require module "+"../db/handoffs.js");})();
29440
+ const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
28486
29441
  const handoff = getLatestHandoff2(agent_id, project_id ? resolveId(project_id, "projects") : undefined);
28487
29442
  if (!handoff)
28488
29443
  return { content: [{ type: "text", text: "No handoffs found." }] };
@@ -28513,7 +29468,7 @@ function registerTaskRelTools(server, ctx) {
28513
29468
  created_by: exports_external2.string().optional().describe("Agent ID who created this relationship")
28514
29469
  }, async ({ source_task_id, target_task_id, relationship_type, created_by }) => {
28515
29470
  try {
28516
- const { addTaskRelationship: addTaskRelationship2 } = (()=>{throw new Error("Cannot require module "+"../db/task-relationships.js");})();
29471
+ const { addTaskRelationship: addTaskRelationship2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
28517
29472
  const rel = addTaskRelationship2({
28518
29473
  source_task_id: resolveId(source_task_id),
28519
29474
  target_task_id: resolveId(target_task_id),
@@ -28534,7 +29489,7 @@ function registerTaskRelTools(server, ctx) {
28534
29489
  relationship_type: exports_external2.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
28535
29490
  }, async ({ id, source_task_id, target_task_id, relationship_type }) => {
28536
29491
  try {
28537
- const { removeTaskRelationship: removeTaskRelationship2, removeTaskRelationshipByPair: removeTaskRelationshipByPair2 } = (()=>{throw new Error("Cannot require module "+"../db/task-relationships.js");})();
29492
+ const { removeTaskRelationship: removeTaskRelationship2, removeTaskRelationshipByPair: removeTaskRelationshipByPair2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
28538
29493
  let removed = false;
28539
29494
  if (id) {
28540
29495
  removed = removeTaskRelationship2(id);
@@ -28555,7 +29510,7 @@ function registerTaskRelTools(server, ctx) {
28555
29510
  relationship_type: exports_external2.enum(["related_to", "conflicts_with", "similar_to", "duplicates", "supersedes", "modifies_same_file"]).optional()
28556
29511
  }, async ({ task_id, relationship_type }) => {
28557
29512
  try {
28558
- const { getTaskRelationships: getTaskRelationships2 } = (()=>{throw new Error("Cannot require module "+"../db/task-relationships.js");})();
29513
+ const { getTaskRelationships: getTaskRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
28559
29514
  const rels = getTaskRelationships2(resolveId(task_id), relationship_type);
28560
29515
  if (rels.length === 0)
28561
29516
  return { content: [{ type: "text", text: "No relationships found." }] };
@@ -28572,7 +29527,7 @@ function registerTaskRelTools(server, ctx) {
28572
29527
  task_id: exports_external2.string().describe("Task ID to detect file relationships for")
28573
29528
  }, async ({ task_id }) => {
28574
29529
  try {
28575
- const { autoDetectFileRelationships: autoDetectFileRelationships2 } = (()=>{throw new Error("Cannot require module "+"../db/task-relationships.js");})();
29530
+ const { autoDetectFileRelationships: autoDetectFileRelationships2 } = (init_task_relationships(), __toCommonJS(exports_task_relationships));
28576
29531
  const created = autoDetectFileRelationships2(resolveId(task_id));
28577
29532
  return { content: [{ type: "text", text: created.length > 0 ? `Created ${created.length} file relationship(s).` : "No file overlaps detected." }] };
28578
29533
  } catch (e) {
@@ -28583,8 +29538,8 @@ function registerTaskRelTools(server, ctx) {
28583
29538
  if (shouldRegisterTool("sync_kg")) {
28584
29539
  server.tool("sync_kg", "Sync all existing relationships into the knowledge graph edges table. Idempotent.", {}, async () => {
28585
29540
  try {
28586
- const { syncKgEdges } = (()=>{throw new Error("Cannot require module "+"../db/kg.js");})();
28587
- const result = syncKgEdges();
29541
+ const { syncKgEdges: syncKgEdges2 } = (init_kg(), __toCommonJS(exports_kg));
29542
+ const result = syncKgEdges2();
28588
29543
  return { content: [{ type: "text", text: `Knowledge graph synced: ${result.synced} edge(s) processed.` }] };
28589
29544
  } catch (e) {
28590
29545
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -28600,8 +29555,8 @@ function registerTaskRelTools(server, ctx) {
28600
29555
  limit: exports_external2.number().optional().describe("Max results")
28601
29556
  }, async ({ entity_id, relation_type, entity_type, direction, limit }) => {
28602
29557
  try {
28603
- const { getRelated } = (()=>{throw new Error("Cannot require module "+"../db/kg.js");})();
28604
- const edges = getRelated(entity_id, { relation_type, entity_type, direction, limit });
29558
+ const { getRelated: getRelated2 } = (init_kg(), __toCommonJS(exports_kg));
29559
+ const edges = getRelated2(entity_id, { relation_type, entity_type, direction, limit });
28605
29560
  if (edges.length === 0)
28606
29561
  return { content: [{ type: "text", text: "No related entities found." }] };
28607
29562
  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})`);
@@ -28620,8 +29575,8 @@ function registerTaskRelTools(server, ctx) {
28620
29575
  relation_types: exports_external2.array(exports_external2.string()).optional().describe("Filter by relation types")
28621
29576
  }, async ({ source_id, target_id, max_depth, relation_types }) => {
28622
29577
  try {
28623
- const { findPath } = (()=>{throw new Error("Cannot require module "+"../db/kg.js");})();
28624
- const paths = findPath(source_id, target_id, { max_depth, relation_types });
29578
+ const { findPath: findPath2 } = (init_kg(), __toCommonJS(exports_kg));
29579
+ const paths = findPath2(source_id, target_id, { max_depth, relation_types });
28625
29580
  if (paths.length === 0)
28626
29581
  return { content: [{ type: "text", text: "No path found." }] };
28627
29582
  const lines = paths.map((path, i) => {
@@ -28645,8 +29600,8 @@ function registerTaskRelTools(server, ctx) {
28645
29600
  relation_types: exports_external2.array(exports_external2.string()).optional().describe("Filter by relation types")
28646
29601
  }, async ({ entity_id, max_depth, relation_types }) => {
28647
29602
  try {
28648
- const { getImpactAnalysis } = (()=>{throw new Error("Cannot require module "+"../db/kg.js");})();
28649
- const impact = getImpactAnalysis(entity_id, { max_depth, relation_types });
29603
+ const { getImpactAnalysis: getImpactAnalysis2 } = (init_kg(), __toCommonJS(exports_kg));
29604
+ const impact = getImpactAnalysis2(entity_id, { max_depth, relation_types });
28650
29605
  if (impact.length === 0)
28651
29606
  return { content: [{ type: "text", text: "No downstream impact detected." }] };
28652
29607
  const byDepth = new Map;
@@ -28676,8 +29631,8 @@ Depth ${depth}:`);
28676
29631
  limit: exports_external2.number().optional().describe("Max results (default: 20)")
28677
29632
  }, async ({ project_id, limit }) => {
28678
29633
  try {
28679
- const { getCriticalPath } = (()=>{throw new Error("Cannot require module "+"../db/kg.js");})();
28680
- const result = getCriticalPath({ project_id: project_id ? resolveId(project_id, "projects") : undefined, limit });
29634
+ const { getCriticalPath: getCriticalPath2 } = (init_kg(), __toCommonJS(exports_kg));
29635
+ const result = getCriticalPath2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, limit });
28681
29636
  if (result.length === 0)
28682
29637
  return { content: [{ type: "text", text: "No critical path data. Run sync_kg first to populate the knowledge graph." }] };
28683
29638
  const lines = result.map((r, i) => `${i + 1}. ${r.task_id.slice(0, 8)} blocks ${r.blocking_count} task(s), max depth ${r.depth}`);
@@ -28697,13 +29652,13 @@ ${lines.join(`
28697
29652
  is_lead: exports_external2.coerce.boolean().optional().describe("Whether this agent is the project lead for this role")
28698
29653
  }, async ({ project_id, agent_name, role, is_lead }) => {
28699
29654
  try {
28700
- const { setProjectAgentRole } = (()=>{throw new Error("Cannot require module "+"../db/project-agent-roles.js");})();
28701
- const { getAgentByName: getAgentByName2 } = (()=>{throw new Error("Cannot require module "+"../db/agents.js");})();
29655
+ const { setProjectAgentRole: setProjectAgentRole2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
29656
+ const { getAgentByName: getAgentByName2 } = (init_agents(), __toCommonJS(exports_agents));
28702
29657
  const agent = getAgentByName2(agent_name);
28703
29658
  if (!agent)
28704
29659
  return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
28705
29660
  const pid = resolveId(project_id, "projects");
28706
- const result = setProjectAgentRole(pid, agent.id, role, is_lead ?? false);
29661
+ const result = setProjectAgentRole2(pid, agent.id, role, is_lead ?? false);
28707
29662
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
28708
29663
  } catch (e) {
28709
29664
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -28733,9 +29688,9 @@ ${lines.join(`
28733
29688
  }).join(`
28734
29689
  `);
28735
29690
  };
28736
- const { getProjectOrgChart } = (()=>{throw new Error("Cannot require module "+"../db/project-agent-roles.js");})();
29691
+ const { getProjectOrgChart: getProjectOrgChart2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
28737
29692
  const pid = resolveId(project_id, "projects");
28738
- const tree = getProjectOrgChart(pid, { filter_to_project });
29693
+ const tree = getProjectOrgChart2(pid, { filter_to_project });
28739
29694
  if (format === "json") {
28740
29695
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
28741
29696
  }
@@ -28753,9 +29708,9 @@ ${lines.join(`
28753
29708
  project_id: exports_external2.string().describe("Project ID")
28754
29709
  }, async ({ project_id }) => {
28755
29710
  try {
28756
- const { listProjectAgentRoles } = (()=>{throw new Error("Cannot require module "+"../db/project-agent-roles.js");})();
29711
+ const { listProjectAgentRoles: listProjectAgentRoles2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
28757
29712
  const pid = resolveId(project_id, "projects");
28758
- const roles = listProjectAgentRoles(pid);
29713
+ const roles = listProjectAgentRoles2(pid);
28759
29714
  return { content: [{ type: "text", text: JSON.stringify(roles, null, 2) }] };
28760
29715
  } catch (e) {
28761
29716
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -28769,7 +29724,7 @@ ${lines.join(`
28769
29724
  limit: exports_external2.number().optional().describe("Max results")
28770
29725
  }, async ({ capabilities, min_score, limit }) => {
28771
29726
  try {
28772
- const { getCapableAgents: getCapableAgents2 } = (()=>{throw new Error("Cannot require module "+"../db/agents.js");})();
29727
+ const { getCapableAgents: getCapableAgents2 } = (init_agents(), __toCommonJS(exports_agents));
28773
29728
  const results = getCapableAgents2(capabilities, { min_score, limit });
28774
29729
  if (results.length === 0)
28775
29730
  return { content: [{ type: "text", text: "No agents match the given capabilities." }] };
@@ -28788,8 +29743,8 @@ ${lines.join(`
28788
29743
  project_id: exports_external2.string().optional().describe("Filter by project")
28789
29744
  }, async ({ stuck_minutes, confidence_threshold, project_id }) => {
28790
29745
  try {
28791
- const { patrolTasks } = (()=>{throw new Error("Cannot require module "+"../db/patrol.js");})();
28792
- const result = patrolTasks({
29746
+ const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
29747
+ const result = patrolTasks2({
28793
29748
  stuck_minutes,
28794
29749
  confidence_threshold,
28795
29750
  project_id: project_id ? resolveId(project_id, "projects") : undefined
@@ -28815,8 +29770,8 @@ ${lines.join(`
28815
29770
  limit: exports_external2.number().optional().describe("Max results (default: all)")
28816
29771
  }, async ({ project_id, limit }) => {
28817
29772
  try {
28818
- const { getReviewQueue } = (()=>{throw new Error("Cannot require module "+"../db/patrol.js");})();
28819
- const tasks = getReviewQueue({
29773
+ const { getReviewQueue: getReviewQueue2 } = (init_patrol(), __toCommonJS(exports_patrol));
29774
+ const tasks = getReviewQueue2({
28820
29775
  project_id: project_id ? resolveId(project_id, "projects") : undefined,
28821
29776
  limit
28822
29777
  });
@@ -28842,8 +29797,8 @@ ${lines.join(`
28842
29797
  reviewer_id: exports_external2.string().optional().describe("Agent ID of reviewer")
28843
29798
  }, async ({ task_id, score, reviewer_id }) => {
28844
29799
  try {
28845
- const { scoreTask } = (()=>{throw new Error("Cannot require module "+"../db/agent-metrics.js");})();
28846
- scoreTask(resolveId(task_id), score, reviewer_id);
29800
+ const { scoreTask: scoreTask2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
29801
+ scoreTask2(resolveId(task_id), score, reviewer_id);
28847
29802
  return { content: [{ type: "text", text: `Task ${task_id.slice(0, 8)} scored: ${score}` }] };
28848
29803
  } catch (e) {
28849
29804
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -28860,7 +29815,7 @@ ${lines.join(`
28860
29815
  notes: exports_external2.string().optional().describe("Notes about what was done")
28861
29816
  }, async ({ task_id, minutes, agent_id, started_at, ended_at, notes }) => {
28862
29817
  try {
28863
- const { logTime: logTime2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
29818
+ const { logTime: logTime2 } = (init_tasks(), __toCommonJS(exports_tasks));
28864
29819
  logTime2({ task_id: resolveId(task_id), minutes, agent_id, started_at, ended_at, notes });
28865
29820
  return { content: [{ type: "text", text: `Logged ${minutes} min on task ${task_id.slice(0, 8)}` }] };
28866
29821
  } catch (e) {
@@ -28875,7 +29830,7 @@ ${lines.join(`
28875
29830
  since: exports_external2.string().optional().describe("ISO date \u2014 only tasks completed after this date")
28876
29831
  }, async ({ project_id, agent_id, since }) => {
28877
29832
  try {
28878
- const { getTimeReport: getTimeReport2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
29833
+ const { getTimeReport: getTimeReport2 } = (init_tasks(), __toCommonJS(exports_tasks));
28879
29834
  const report = getTimeReport2({ project_id: project_id ? resolveId(project_id, "projects") : undefined, agent_id, since });
28880
29835
  if (report.length === 0)
28881
29836
  return { content: [{ type: "text", text: "No completed tasks found." }] };
@@ -28899,7 +29854,7 @@ ${lines.join(`
28899
29854
  agent_id: exports_external2.string().optional().describe("Agent subscribing (defaults to context agent)")
28900
29855
  }, async ({ task_id, agent_id }) => {
28901
29856
  try {
28902
- const { watchTask: watchTask2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
29857
+ const { watchTask: watchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
28903
29858
  watchTask2(resolveId(task_id), agent_id || "");
28904
29859
  return { content: [{ type: "text", text: `Now watching task ${task_id.slice(0, 8)}` }] };
28905
29860
  } catch (e) {
@@ -28913,7 +29868,7 @@ ${lines.join(`
28913
29868
  agent_id: exports_external2.string().optional().describe("Agent unsubscribing (defaults to context agent)")
28914
29869
  }, async ({ task_id, agent_id }) => {
28915
29870
  try {
28916
- const { unwatchTask: unwatchTask2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
29871
+ const { unwatchTask: unwatchTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
28917
29872
  unwatchTask2(resolveId(task_id), agent_id || "");
28918
29873
  return { content: [{ type: "text", text: `Stopped watching task ${task_id.slice(0, 8)}` }] };
28919
29874
  } catch (e) {
@@ -28926,7 +29881,7 @@ ${lines.join(`
28926
29881
  task_id: exports_external2.string().describe("Task ID")
28927
29882
  }, async ({ task_id }) => {
28928
29883
  try {
28929
- const { getTaskWatchers: getTaskWatchers2 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
29884
+ const { getTaskWatchers: getTaskWatchers2 } = (init_tasks(), __toCommonJS(exports_tasks));
28930
29885
  const watchers = getTaskWatchers2(resolveId(task_id));
28931
29886
  if (watchers.length === 0)
28932
29887
  return { content: [{ type: "text", text: "No watchers." }] };
@@ -28945,15 +29900,15 @@ ${lines.join(`
28945
29900
  agent_id: exports_external2.string().optional().describe("Filter by assignee")
28946
29901
  }, async ({ project_id, plan_id, task_list_id, since, agent_id }) => {
28947
29902
  try {
28948
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
28949
- const { patrolTasks } = (()=>{throw new Error("Cannot require module "+"../db/patrol.js");})();
29903
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
29904
+ const { patrolTasks: patrolTasks2 } = (init_patrol(), __toCommonJS(exports_patrol));
28950
29905
  const completed = listTasks3({ status: "completed", project_id, plan_id, task_list_id, assigned_to: agent_id, limit: 500 }, undefined);
28951
29906
  const filtered = since ? completed.filter((t) => t.completed_at && t.completed_at >= since) : completed;
28952
29907
  const total = filtered.length;
28953
29908
  const lowConf = filtered.filter((t) => t.confidence != null && t.confidence < 0.7).length;
28954
29909
  const withEstimate = filtered.filter((t) => t.estimated_minutes != null && t.actual_minutes != null);
28955
29910
  const avgDiff = withEstimate.length > 0 ? withEstimate.reduce((acc, t) => acc + (t.actual_minutes - t.estimated_minutes), 0) / withEstimate.length : 0;
28956
- const patrolResult = patrolTasks({ project_id: project_id ? resolveId(project_id, "projects") : undefined });
29911
+ const patrolResult = patrolTasks2({ project_id: project_id ? resolveId(project_id, "projects") : undefined });
28957
29912
  const stuck = patrolResult.issues.filter((i) => i.type === "stuck").length;
28958
29913
  const lines = [
28959
29914
  `Retro (${total} completed tasks${since ? ` since ${since}` : ""})`,
@@ -28974,7 +29929,7 @@ ${lines.join(`
28974
29929
  limit: exports_external2.number().optional().describe("Max results (default: 20)")
28975
29930
  }, async ({ project_id, limit }) => {
28976
29931
  try {
28977
- const { listTasks: listTasks3 } = (()=>{throw new Error("Cannot require module "+"../db/tasks.js");})();
29932
+ const { listTasks: listTasks3 } = (init_tasks(), __toCommonJS(exports_tasks));
28978
29933
  const tasks = listTasks3({ status: "pending", project_id, assigned_to: "", limit: limit || 20 }, undefined);
28979
29934
  if (tasks.length === 0)
28980
29935
  return { content: [{ type: "text", text: "Inbox is empty." }] };
@@ -28993,8 +29948,8 @@ ${lines.join(`
28993
29948
  project_id: exports_external2.string().optional().describe("Filter by project")
28994
29949
  }, async ({ agent_id, project_id }) => {
28995
29950
  try {
28996
- const { getAgentMetrics } = (()=>{throw new Error("Cannot require module "+"../db/agent-metrics.js");})();
28997
- const metrics = getAgentMetrics(agent_id, {
29951
+ const { getAgentMetrics: getAgentMetrics2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
29952
+ const metrics = getAgentMetrics2(agent_id, {
28998
29953
  project_id: project_id ? resolveId(project_id, "projects") : undefined
28999
29954
  });
29000
29955
  if (!metrics)
@@ -29021,8 +29976,8 @@ ${lines.join(`
29021
29976
  limit: exports_external2.number().optional().describe("Max entries (default: 20)")
29022
29977
  }, async ({ project_id, limit }) => {
29023
29978
  try {
29024
- const { getLeaderboard } = (()=>{throw new Error("Cannot require module "+"../db/agent-metrics.js");})();
29025
- const entries = getLeaderboard({
29979
+ const { getLeaderboard: getLeaderboard2 } = (init_agent_metrics(), __toCommonJS(exports_agent_metrics));
29980
+ const entries = getLeaderboard2({
29026
29981
  project_id: project_id ? resolveId(project_id, "projects") : undefined,
29027
29982
  limit
29028
29983
  });
@@ -29982,6 +30937,361 @@ var init_machines2 = __esm(() => {
29982
30937
  init_database();
29983
30938
  });
29984
30939
 
30940
+ // src/mcp/tools/agents.ts
30941
+ function registerAgentTools(server, { shouldRegisterTool, resolveId, formatError, agentFocusMap, getAgentFocus }) {
30942
+ if (shouldRegisterTool("set_focus")) {
30943
+ server.tool("set_focus", "Focus this agent on a project. All list/search/status tools will default to this project.", {
30944
+ agent_id: exports_external2.string().describe("Agent ID or name"),
30945
+ project_id: exports_external2.string().optional().describe("Project to focus on. Omit to clear."),
30946
+ task_list_id: exports_external2.string().optional().describe("Task list to focus on")
30947
+ }, async ({ agent_id, project_id, task_list_id }) => {
30948
+ try {
30949
+ const resolvedProject = project_id ? resolveId(project_id, "projects") : undefined;
30950
+ const focus = { agent_id, project_id: resolvedProject, task_list_id };
30951
+ agentFocusMap.set(agent_id, focus);
30952
+ try {
30953
+ const agent = getAgentByName(agent_id) || getAgent(agent_id);
30954
+ if (agent) {
30955
+ const db = getDatabase();
30956
+ db.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [resolvedProject || null, agent.id]);
30957
+ }
30958
+ } catch {}
30959
+ const projectName = resolvedProject ? ` (${resolvedProject.slice(0, 8)})` : "";
30960
+ return { content: [{ type: "text", text: `Focused on project${projectName}. Read tools will default to this scope. Pass explicit project_id to override.` }] };
30961
+ } catch (e) {
30962
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
30963
+ }
30964
+ });
30965
+ }
30966
+ if (shouldRegisterTool("get_focus")) {
30967
+ server.tool("get_focus", "Get the current focus for an agent.", { agent_id: exports_external2.string().describe("Agent ID or name") }, async ({ agent_id }) => {
30968
+ const focus = getAgentFocus(agent_id);
30969
+ if (!focus?.project_id) {
30970
+ return { content: [{ type: "text", text: "No focus set. Showing all projects." }] };
30971
+ }
30972
+ return { content: [{ type: "text", text: `Focused on project: ${focus.project_id}${focus.task_list_id ? `, task list: ${focus.task_list_id}` : ""}` }] };
30973
+ });
30974
+ }
30975
+ if (shouldRegisterTool("unfocus")) {
30976
+ server.tool("unfocus", "Clear focus \u2014 show all projects and tasks.", { agent_id: exports_external2.string().describe("Agent ID or name") }, async ({ agent_id }) => {
30977
+ agentFocusMap.delete(agent_id);
30978
+ try {
30979
+ const agent = getAgentByName(agent_id) || getAgent(agent_id);
30980
+ if (agent) {
30981
+ const db = getDatabase();
30982
+ db.run("UPDATE agents SET active_project_id = NULL WHERE id = ?", [agent.id]);
30983
+ }
30984
+ } catch {}
30985
+ return { content: [{ type: "text", text: "Focus cleared. Showing all projects." }] };
30986
+ });
30987
+ }
30988
+ if (shouldRegisterTool("register_agent")) {
30989
+ 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.", {
30990
+ name: exports_external2.string().describe("Agent name \u2014 any name is allowed. Use suggest_agent_name to see pool suggestions and avoid conflicts."),
30991
+ description: exports_external2.string().optional(),
30992
+ role: exports_external2.string().optional(),
30993
+ title: exports_external2.string().optional(),
30994
+ level: exports_external2.string().optional(),
30995
+ permissions: exports_external2.array(exports_external2.string()).optional(),
30996
+ capabilities: exports_external2.array(exports_external2.string()).optional().describe("Agent capabilities/skills for task routing (e.g. ['typescript', 'testing', 'devops'])"),
30997
+ session_id: exports_external2.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."),
30998
+ working_dir: exports_external2.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"),
30999
+ force: exports_external2.boolean().optional().describe("Force takeover of an active agent's name. Use with caution \u2014 only when you know the previous session is dead.")
31000
+ }, async ({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force }) => {
31001
+ try {
31002
+ const pool = getAgentPoolForProject(working_dir);
31003
+ const result = registerAgent({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force, pool: pool || undefined });
31004
+ if (isAgentConflict(result)) {
31005
+ const suggestLine = result.suggestions && result.suggestions.length > 0 ? `
31006
+ Available names: ${result.suggestions.join(", ")}` : "";
31007
+ const hint = `CONFLICT: ${result.message}${suggestLine}`;
31008
+ return {
31009
+ content: [{ type: "text", text: hint }],
31010
+ isError: true
31011
+ };
31012
+ }
31013
+ const agent = result;
31014
+ const poolLine = pool ? `
31015
+ Pool: [${pool.join(", ")}]` : "";
31016
+ return {
31017
+ content: [{
31018
+ type: "text",
31019
+ text: `Agent registered:
31020
+ ID: ${agent.id}
31021
+ Name: ${agent.name}${agent.description ? `
31022
+ Description: ${agent.description}` : ""}
31023
+ Session: ${agent.session_id ?? "unbound"}${poolLine}
31024
+ Created: ${agent.created_at}
31025
+ Last seen: ${agent.last_seen_at}`
31026
+ }]
31027
+ };
31028
+ } catch (e) {
31029
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31030
+ }
31031
+ });
31032
+ }
31033
+ if (shouldRegisterTool("suggest_agent_name")) {
31034
+ 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.", {
31035
+ working_dir: exports_external2.string().optional().describe("Your working directory \u2014 used to look up the project's allowed name pool from config")
31036
+ }, async ({ working_dir }) => {
31037
+ try {
31038
+ const pool = getAgentPoolForProject(working_dir);
31039
+ const cutoff = new Date(Date.now() - 30 * 60 * 1000).toISOString();
31040
+ const allActive = listAgents().filter((a) => a.last_seen_at > cutoff);
31041
+ if (!pool) {
31042
+ const lines2 = [
31043
+ "No agent pool configured \u2014 any name is allowed.",
31044
+ 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.",
31045
+ `
31046
+ To restrict names, configure agent_pool or project_pools in ~/.hasna/todos/config.json`
31047
+ ];
31048
+ return { content: [{ type: "text", text: lines2.join(`
31049
+ `) }] };
31050
+ }
31051
+ const available = getAvailableNamesFromPool(pool, getDatabase());
31052
+ const activeInPool = allActive.filter((a) => pool.map((n) => n.toLowerCase()).includes(a.name));
31053
+ const lines = [
31054
+ `Project pool: ${pool.join(", ")}`,
31055
+ `Available now (${available.length}): ${available.length > 0 ? available.join(", ") : "none \u2014 all names in use"}`,
31056
+ 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",
31057
+ available.length > 0 ? `
31058
+ Suggested: ${available[0]}` : `
31059
+ No names available. Wait for an active agent to go stale (30min timeout).`
31060
+ ];
31061
+ return { content: [{ type: "text", text: lines.join(`
31062
+ `) }] };
31063
+ } catch (e) {
31064
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31065
+ }
31066
+ });
31067
+ }
31068
+ if (shouldRegisterTool("list_agents")) {
31069
+ server.tool("list_agents", "List all registered agents. By default shows only active agents \u2014 set include_archived to see archived ones too.", {
31070
+ include_archived: exports_external2.boolean().optional().describe("Include archived agents in the list (default: false)")
31071
+ }, async ({ include_archived }) => {
31072
+ try {
31073
+ const agents = listAgents({ include_archived: include_archived ?? false });
31074
+ if (agents.length === 0) {
31075
+ return { content: [{ type: "text", text: "No agents registered." }] };
31076
+ }
31077
+ const text = agents.map((a) => {
31078
+ const statusTag = a.status === "archived" ? " [archived]" : "";
31079
+ return `${a.id} | ${a.name}${statusTag}${a.description ? ` - ${a.description}` : ""} (last seen: ${a.last_seen_at})`;
31080
+ }).join(`
31081
+ `);
31082
+ return { content: [{ type: "text", text: `${agents.length} agent(s):
31083
+ ${text}` }] };
31084
+ } catch (e) {
31085
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31086
+ }
31087
+ });
31088
+ }
31089
+ if (shouldRegisterTool("get_agent")) {
31090
+ server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
31091
+ agent_id: exports_external2.string().optional(),
31092
+ id: exports_external2.string().optional(),
31093
+ name: exports_external2.string().optional()
31094
+ }, async ({ agent_id, id, name }) => {
31095
+ try {
31096
+ const identifier = agent_id || id || name;
31097
+ if (!identifier) {
31098
+ return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
31099
+ }
31100
+ const agent = getAgent(identifier) || getAgentByName(identifier);
31101
+ if (!agent) {
31102
+ return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
31103
+ }
31104
+ const parts = [
31105
+ `ID: ${agent.id}`,
31106
+ `Name: ${agent.name}`
31107
+ ];
31108
+ if (agent.description)
31109
+ parts.push(`Description: ${agent.description}`);
31110
+ if (Object.keys(agent.metadata).length > 0)
31111
+ parts.push(`Metadata: ${JSON.stringify(agent.metadata)}`);
31112
+ parts.push(`Created: ${agent.created_at}`);
31113
+ parts.push(`Last seen: ${agent.last_seen_at}`);
31114
+ return { content: [{ type: "text", text: parts.join(`
31115
+ `) }] };
31116
+ } catch (e) {
31117
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31118
+ }
31119
+ });
31120
+ }
31121
+ if (shouldRegisterTool("rename_agent")) {
31122
+ server.tool("rename_agent", "Rename an agent. Resolve by id or current name.", {
31123
+ id: exports_external2.string().optional(),
31124
+ name: exports_external2.string().optional(),
31125
+ new_name: exports_external2.string()
31126
+ }, async ({ id, name, new_name }) => {
31127
+ try {
31128
+ if (!id && !name) {
31129
+ return { content: [{ type: "text", text: "Provide either id or name." }], isError: true };
31130
+ }
31131
+ const agent = id ? getAgent(id) : getAgentByName(name);
31132
+ if (!agent) {
31133
+ return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
31134
+ }
31135
+ const oldName = agent.name;
31136
+ const updated = updateAgent(agent.id, { name: new_name });
31137
+ const db = getDatabase();
31138
+ const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
31139
+ const taskNote = tasksResult.changes > 0 ? `
31140
+ Updated assigned_to on ${tasksResult.changes} task(s).` : "";
31141
+ return {
31142
+ content: [{
31143
+ type: "text",
31144
+ text: `Agent renamed: ${oldName} -> ${updated.name}
31145
+ ID: ${updated.id}${taskNote}`
31146
+ }]
31147
+ };
31148
+ } catch (e) {
31149
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31150
+ }
31151
+ });
31152
+ }
31153
+ if (shouldRegisterTool("update_agent")) {
31154
+ server.tool("update_agent", "Update an agent's description, role, title, or other metadata. Resolve by id or name.", {
31155
+ agent_id: exports_external2.string().optional(),
31156
+ id: exports_external2.string().optional(),
31157
+ name: exports_external2.string().optional(),
31158
+ description: exports_external2.string().optional(),
31159
+ role: exports_external2.string().optional(),
31160
+ title: exports_external2.string().optional(),
31161
+ level: exports_external2.string().optional(),
31162
+ capabilities: exports_external2.array(exports_external2.string()).optional(),
31163
+ permissions: exports_external2.array(exports_external2.string()).optional(),
31164
+ metadata: exports_external2.record(exports_external2.unknown()).optional()
31165
+ }, async ({ agent_id, id, name, ...updates }) => {
31166
+ try {
31167
+ const identifier = agent_id || id || name;
31168
+ if (!identifier) {
31169
+ return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
31170
+ }
31171
+ const agent = getAgent(identifier) || getAgentByName(identifier);
31172
+ if (!agent) {
31173
+ return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
31174
+ }
31175
+ const updated = updateAgent(agent.id, updates);
31176
+ return { content: [{ type: "text", text: `Agent updated: ${updated.name} (${updated.id.slice(0, 8)})` }] };
31177
+ } catch (e) {
31178
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31179
+ }
31180
+ });
31181
+ }
31182
+ if (shouldRegisterTool("delete_agent")) {
31183
+ 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.", {
31184
+ agent_id: exports_external2.string().optional(),
31185
+ id: exports_external2.string().optional(),
31186
+ name: exports_external2.string().optional()
31187
+ }, async ({ agent_id, id, name }) => {
31188
+ try {
31189
+ const identifier = agent_id || id || name;
31190
+ if (!identifier) {
31191
+ return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
31192
+ }
31193
+ const agent = getAgent(identifier) || getAgentByName(identifier);
31194
+ if (!agent) {
31195
+ return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
31196
+ }
31197
+ const archived = archiveAgent(agent.id);
31198
+ return {
31199
+ content: [{
31200
+ type: "text",
31201
+ text: archived ? `Agent archived: ${agent.name} (${agent.id}). Use unarchive_agent to restore.` : `Failed to archive agent: ${agent.name}`
31202
+ }],
31203
+ isError: !archived
31204
+ };
31205
+ } catch (e) {
31206
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31207
+ }
31208
+ });
31209
+ }
31210
+ if (shouldRegisterTool("unarchive_agent")) {
31211
+ server.tool("unarchive_agent", "Restore an archived agent back to active status. Resolve by id or name.", {
31212
+ agent_id: exports_external2.string().optional(),
31213
+ id: exports_external2.string().optional(),
31214
+ name: exports_external2.string().optional()
31215
+ }, async ({ agent_id, id, name }) => {
31216
+ try {
31217
+ const identifier = agent_id || id || name;
31218
+ if (!identifier) {
31219
+ return { content: [{ type: "text", text: "Provide agent_id, id, or name." }], isError: true };
31220
+ }
31221
+ const agent = getAgent(identifier) || getAgentByName(identifier);
31222
+ if (!agent) {
31223
+ return { content: [{ type: "text", text: `Agent not found: ${identifier}` }], isError: true };
31224
+ }
31225
+ if (agent.status === "active") {
31226
+ return { content: [{ type: "text", text: `Agent ${agent.name} is already active.` }] };
31227
+ }
31228
+ const restored = unarchiveAgent(agent.id);
31229
+ return {
31230
+ content: [{
31231
+ type: "text",
31232
+ text: restored ? `Agent restored: ${agent.name} (${agent.id}) is now active.` : `Failed to restore agent: ${agent.name}`
31233
+ }],
31234
+ isError: !restored
31235
+ };
31236
+ } catch (e) {
31237
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31238
+ }
31239
+ });
31240
+ }
31241
+ if (shouldRegisterTool("heartbeat")) {
31242
+ 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.", {
31243
+ agent_id: exports_external2.string().describe("Your agent ID or name.")
31244
+ }, async ({ agent_id }) => {
31245
+ try {
31246
+ const agent = getAgent(agent_id) || getAgentByName(agent_id);
31247
+ if (!agent) {
31248
+ return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
31249
+ }
31250
+ updateAgentActivity(agent.id);
31251
+ return {
31252
+ content: [{
31253
+ type: "text",
31254
+ text: `Heartbeat: ${agent.name} (${agent.id}) \u2014 last_seen_at updated to ${new Date().toISOString()}`
31255
+ }]
31256
+ };
31257
+ } catch (e) {
31258
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31259
+ }
31260
+ });
31261
+ }
31262
+ if (shouldRegisterTool("release_agent")) {
31263
+ 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.", {
31264
+ agent_id: exports_external2.string().describe("Your agent ID or name."),
31265
+ session_id: exports_external2.string().optional().describe("Your session ID \u2014 if provided, release only succeeds if it matches (prevents other sessions from releasing your agent).")
31266
+ }, async ({ agent_id, session_id }) => {
31267
+ try {
31268
+ const agent = getAgent(agent_id) || getAgentByName(agent_id);
31269
+ if (!agent) {
31270
+ return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
31271
+ }
31272
+ const released = releaseAgent(agent.id, session_id);
31273
+ if (!released) {
31274
+ return { content: [{ type: "text", text: `Release denied: session_id does not match agent's current session.` }], isError: true };
31275
+ }
31276
+ return {
31277
+ content: [{
31278
+ type: "text",
31279
+ text: `Agent released: ${agent.name} (${agent.id}) \u2014 session cleared, name is now available.`
31280
+ }]
31281
+ };
31282
+ } catch (e) {
31283
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
31284
+ }
31285
+ });
31286
+ }
31287
+ }
31288
+ var init_agents2 = __esm(() => {
31289
+ init_zod2();
31290
+ init_agents();
31291
+ init_config();
31292
+ init_database();
31293
+ });
31294
+
29985
31295
  // src/mcp/index.ts
29986
31296
  var exports_mcp = {};
29987
31297
  __export(exports_mcp, {
@@ -29989,17 +31299,23 @@ __export(exports_mcp, {
29989
31299
  });
29990
31300
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
29991
31301
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
29992
- import { readFileSync as readFileSync7 } from "fs";
31302
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
29993
31303
  import { join as join12, dirname as dirname6 } from "path";
29994
31304
  import { fileURLToPath as fileURLToPath3 } from "url";
29995
31305
  function getMcpVersion() {
29996
31306
  try {
29997
- const __dir = dirname6(fileURLToPath3(import.meta.url));
29998
- const pkgPath = join12(__dir, "..", "package.json");
29999
- return JSON.parse(readFileSync7(pkgPath, "utf-8")).version || "0.0.0";
31307
+ let dir = dirname6(fileURLToPath3(import.meta.url));
31308
+ for (let i = 0;i < 4; i++) {
31309
+ const pkgPath = join12(dir, "package.json");
31310
+ if (existsSync9(pkgPath)) {
31311
+ return JSON.parse(readFileSync7(pkgPath, "utf-8")).version || "0.0.0";
31312
+ }
31313
+ dir = dirname6(dir);
31314
+ }
30000
31315
  } catch {
30001
31316
  return "0.0.0";
30002
31317
  }
31318
+ return "0.0.0";
30003
31319
  }
30004
31320
  function shouldRegisterTool(name) {
30005
31321
  if (TODOS_PROFILE === "minimal")
@@ -30148,7 +31464,6 @@ var init_mcp = __esm(() => {
30148
31464
  init_cloud();
30149
31465
  init_agents();
30150
31466
  init_database();
30151
- init_logger();
30152
31467
  init_types();
30153
31468
  init_dispatch2();
30154
31469
  init_task_crud2();
@@ -30161,6 +31476,7 @@ var init_mcp = __esm(() => {
30161
31476
  init_task_rel_tools();
30162
31477
  init_code_tools();
30163
31478
  init_machines2();
31479
+ init_agents2();
30164
31480
  server = new McpServer({
30165
31481
  name: "todos",
30166
31482
  version: getMcpVersion()
@@ -30178,6 +31494,7 @@ var init_mcp = __esm(() => {
30178
31494
  "get_next_task",
30179
31495
  "bootstrap",
30180
31496
  "get_tasks_changed_since",
31497
+ "get_health",
30181
31498
  "heartbeat",
30182
31499
  "release_agent"
30183
31500
  ]);
@@ -30218,6 +31535,7 @@ var init_mcp = __esm(() => {
30218
31535
  registerTaskResources(server, toolContext);
30219
31536
  registerTaskRelTools(server, toolContext);
30220
31537
  registerCodeTools(server, toolContext);
31538
+ registerAgentTools(server, { ...toolContext, agentFocusMap });
30221
31539
  registerMachineTools(server, { shouldRegisterTool, formatError });
30222
31540
  registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
30223
31541
  registerCloudSyncTools(server, { shouldRegisterTool, formatError });
@@ -30768,7 +32086,7 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
30768
32086
  opts.tags = opts.tags || opts.tag;
30769
32087
  opts.list = opts.list || opts.taskList;
30770
32088
  const resolvedId = resolveTaskId(id);
30771
- const current = getTask2(resolvedId);
32089
+ const current = getTask(resolvedId);
30772
32090
  if (!current) {
30773
32091
  console.error(chalk2.red(`Task not found: ${id}`));
30774
32092
  process.exit(1);
@@ -30829,7 +32147,7 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
30829
32147
  program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
30830
32148
  const globalOpts = program2.opts();
30831
32149
  const resolvedId = resolveTaskId(id);
30832
- const task = getTask2(resolvedId);
32150
+ const task = getTask(resolvedId);
30833
32151
  if (!task) {
30834
32152
  console.error(chalk2.red(`Task not found: ${id}`));
30835
32153
  process.exit(1);
@@ -31431,7 +32749,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
31431
32749
  updated.activeForm = existing.task.activeForm;
31432
32750
  writeClaudeTask(dir, updated);
31433
32751
  if (recordConflict) {
31434
- const latest = getTask2(task.id);
32752
+ const latest = getTask(task.id);
31435
32753
  if (latest) {
31436
32754
  const conflict = {
31437
32755
  agent: "claude",
@@ -31454,7 +32772,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
31454
32772
  ct.subject = formatPrefixedSubject(task.title, prefixConfig.prefix, prefixCounter);
31455
32773
  }
31456
32774
  writeClaudeTask(dir, ct);
31457
- const current = getTask2(task.id);
32775
+ const current = getTask(task.id);
31458
32776
  if (current) {
31459
32777
  const newMeta = { ...current.metadata, claude_task_id: claudeId };
31460
32778
  updateTask(task.id, { version: current.version, metadata: newMeta });
@@ -31656,7 +32974,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
31656
32974
  const updated = taskToAgentTask(task, existing.task.id, existing.task.metadata);
31657
32975
  writeAgentTask(dir, updated);
31658
32976
  if (recordConflict) {
31659
- const latest = getTask2(task.id);
32977
+ const latest = getTask(task.id);
31660
32978
  if (latest) {
31661
32979
  const conflict = {
31662
32980
  agent,
@@ -31675,7 +32993,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
31675
32993
  hwm++;
31676
32994
  const at = taskToAgentTask(task, externalId);
31677
32995
  writeAgentTask(dir, at);
31678
- const current = getTask2(task.id);
32996
+ const current = getTask(task.id);
31679
32997
  if (current) {
31680
32998
  const newMeta = { ...current.metadata, [metaKey]: externalId };
31681
32999
  updateTask(task.id, { version: current.version, metadata: newMeta });
@@ -32758,7 +34076,7 @@ ${tasks.length} task(s) shown`));
32758
34076
  program2.command("blame <file>").description("Show which tasks/agents touched a file and why \u2014 combines task_files + task_commits").action(async (filePath) => {
32759
34077
  const globalOpts = program2.opts();
32760
34078
  const { findTasksByFile: findTasksByFile2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
32761
- const { getTask: getTask3 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
34079
+ const { getTask: getTask2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
32762
34080
  const db = getDatabase();
32763
34081
  const taskFiles = findTasksByFile2(filePath, db);
32764
34082
  const commitRows = db.query("SELECT tc.*, t.title, t.short_id FROM task_commits tc JOIN tasks t ON t.id = tc.task_id WHERE tc.files_changed LIKE ? ORDER BY tc.committed_at DESC").all(`%${filePath}%`);
@@ -32772,7 +34090,7 @@ Blame: ${filePath}
32772
34090
  if (taskFiles.length > 0) {
32773
34091
  console.log(chalk6.bold("Task File Links:"));
32774
34092
  for (const tf of taskFiles) {
32775
- const task = getTask3(tf.task_id, db);
34093
+ const task = getTask2(tf.task_id, db);
32776
34094
  const title = task ? task.title : "unknown";
32777
34095
  const sid = task?.short_id || tf.task_id.slice(0, 8);
32778
34096
  console.log(` ${chalk6.cyan(sid)} ${title} \u2014 ${chalk6.dim(tf.role || "file")} ${chalk6.dim(tf.updated_at)}`);
@@ -32806,74 +34124,10 @@ Commit Links (${commitRows.length}):`));
32806
34124
  init_database();
32807
34125
  init_tasks();
32808
34126
  init_audit();
32809
- import chalk7 from "chalk";
32810
-
32811
- // src/db/handoffs.ts
32812
- init_database();
32813
- function createHandoff(input, db) {
32814
- const d = db || getDatabase();
32815
- const id = uuid();
32816
- const timestamp = now();
32817
- d.run(`INSERT INTO handoffs (id, agent_id, project_id, summary, completed, in_progress, blockers, next_steps, created_at)
32818
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
32819
- id,
32820
- input.agent_id || null,
32821
- input.project_id || null,
32822
- input.summary,
32823
- input.completed ? JSON.stringify(input.completed) : null,
32824
- input.in_progress ? JSON.stringify(input.in_progress) : null,
32825
- input.blockers ? JSON.stringify(input.blockers) : null,
32826
- input.next_steps ? JSON.stringify(input.next_steps) : null,
32827
- timestamp
32828
- ]);
32829
- return {
32830
- id,
32831
- agent_id: input.agent_id || null,
32832
- project_id: input.project_id || null,
32833
- summary: input.summary,
32834
- completed: input.completed || null,
32835
- in_progress: input.in_progress || null,
32836
- blockers: input.blockers || null,
32837
- next_steps: input.next_steps || null,
32838
- created_at: timestamp
32839
- };
32840
- }
32841
- function rowToHandoff(row) {
32842
- return {
32843
- ...row,
32844
- completed: row.completed ? JSON.parse(row.completed) : null,
32845
- in_progress: row.in_progress ? JSON.parse(row.in_progress) : null,
32846
- blockers: row.blockers ? JSON.parse(row.blockers) : null,
32847
- next_steps: row.next_steps ? JSON.parse(row.next_steps) : null
32848
- };
32849
- }
32850
- function listHandoffs(projectId, limit = 10, db) {
32851
- const d = db || getDatabase();
32852
- if (projectId) {
32853
- return d.query("SELECT * FROM handoffs WHERE project_id = ? ORDER BY rowid DESC LIMIT ?").all(projectId, limit).map(rowToHandoff);
32854
- }
32855
- return d.query("SELECT * FROM handoffs ORDER BY rowid DESC LIMIT ?").all(limit).map(rowToHandoff);
32856
- }
32857
- function getLatestHandoff(agentId, projectId, db) {
32858
- const d = db || getDatabase();
32859
- let query = "SELECT * FROM handoffs WHERE 1=1";
32860
- const params = [];
32861
- if (agentId) {
32862
- query += " AND agent_id = ?";
32863
- params.push(agentId);
32864
- }
32865
- if (projectId) {
32866
- query += " AND project_id = ?";
32867
- params.push(projectId);
32868
- }
32869
- query += " ORDER BY rowid DESC LIMIT 1";
32870
- const row = d.query(query).get(...params);
32871
- return row ? rowToHandoff(row) : null;
32872
- }
32873
-
32874
- // src/cli/commands/query-commands.ts
34127
+ init_handoffs();
32875
34128
  init_recurrence();
32876
34129
  init_helpers();
34130
+ import chalk7 from "chalk";
32877
34131
  function registerQueryCommands(program2) {
32878
34132
  program2.command("next").description("Show the best pending task to work on next").option("--agent <id>", "Prefer tasks assigned to this agent").option("--project <id>", "Filter to project").option("-j, --json", "Output as JSON").action(async (opts) => {
32879
34133
  const globalOpts = program2.opts();
@@ -33162,7 +34416,7 @@ No task claimed (nothing available).`));
33162
34416
  const globalOpts = program2.opts();
33163
34417
  const resolvedId = resolveTaskId(id);
33164
34418
  const db = getDatabase();
33165
- const task = getTask2(resolvedId, db);
34419
+ const task = getTask(resolvedId, db);
33166
34420
  if (!task) {
33167
34421
  console.error(chalk7.red(`Task not found: ${id}`));
33168
34422
  process.exit(1);
@@ -33182,7 +34436,7 @@ No task claimed (nothing available).`));
33182
34436
  const globalOpts = program2.opts();
33183
34437
  const resolvedId = resolveTaskId(id);
33184
34438
  const db = getDatabase();
33185
- const task = getTask2(resolvedId, db);
34439
+ const task = getTask(resolvedId, db);
33186
34440
  if (!task) {
33187
34441
  console.error(chalk7.red(`Task not found: ${id}`));
33188
34442
  process.exit(1);
@@ -33202,7 +34456,7 @@ No task claimed (nothing available).`));
33202
34456
  const globalOpts = program2.opts();
33203
34457
  const resolvedId = resolveTaskId(id);
33204
34458
  const db = getDatabase();
33205
- const task = getTask2(resolvedId, db);
34459
+ const task = getTask(resolvedId, db);
33206
34460
  if (!task) {
33207
34461
  console.error(chalk7.red(`Task not found: ${id}`));
33208
34462
  process.exit(1);
@@ -33223,7 +34477,7 @@ No task claimed (nothing available).`));
33223
34477
  const globalOpts = program2.opts();
33224
34478
  const resolvedId = resolveTaskId(id);
33225
34479
  const db = getDatabase();
33226
- const task = getTask2(resolvedId, db);
34480
+ const task = getTask(resolvedId, db);
33227
34481
  if (!task) {
33228
34482
  console.error(chalk7.red(`Task not found: ${id}`));
33229
34483
  process.exit(1);
@@ -34386,7 +35640,7 @@ init_tasks();
34386
35640
  init_helpers();
34387
35641
  import chalk9 from "chalk";
34388
35642
  import { execSync as execSync3 } from "child_process";
34389
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, chmodSync } from "fs";
35643
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, chmodSync } from "fs";
34390
35644
  import { dirname as dirname7, join as join13 } from "path";
34391
35645
  var HOME2 = process.env["HOME"] || process.env["USERPROFILE"] || "~";
34392
35646
  function getMcpBinaryPath() {
@@ -34396,12 +35650,12 @@ function getMcpBinaryPath() {
34396
35650
  return p;
34397
35651
  } catch {}
34398
35652
  const bunBin = join13(HOME2, ".bun", "bin", "todos-mcp");
34399
- if (existsSync9(bunBin))
35653
+ if (existsSync10(bunBin))
34400
35654
  return bunBin;
34401
35655
  return "todos-mcp";
34402
35656
  }
34403
35657
  function readJsonFile2(path) {
34404
- if (!existsSync9(path))
35658
+ if (!existsSync10(path))
34405
35659
  return {};
34406
35660
  try {
34407
35661
  return JSON.parse(readFileSync8(path, "utf-8"));
@@ -34411,19 +35665,19 @@ function readJsonFile2(path) {
34411
35665
  }
34412
35666
  function writeJsonFile2(path, data) {
34413
35667
  const dir = dirname7(path);
34414
- if (!existsSync9(dir))
35668
+ if (!existsSync10(dir))
34415
35669
  mkdirSync5(dir, { recursive: true });
34416
35670
  writeFileSync5(path, JSON.stringify(data, null, 2) + `
34417
35671
  `);
34418
35672
  }
34419
35673
  function readTomlFile(path) {
34420
- if (!existsSync9(path))
35674
+ if (!existsSync10(path))
34421
35675
  return "";
34422
35676
  return readFileSync8(path, "utf-8");
34423
35677
  }
34424
35678
  function writeTomlFile(path, content) {
34425
35679
  const dir = dirname7(path);
34426
- if (!existsSync9(dir))
35680
+ if (!existsSync10(dir))
34427
35681
  mkdirSync5(dir, { recursive: true });
34428
35682
  writeFileSync5(path, content);
34429
35683
  }
@@ -34567,7 +35821,7 @@ function registerMcpHooksCommands(program2) {
34567
35821
  todosBin = p;
34568
35822
  } catch {}
34569
35823
  const hooksDir = join13(process.cwd(), ".claude", "hooks");
34570
- if (!existsSync9(hooksDir))
35824
+ if (!existsSync10(hooksDir))
34571
35825
  mkdirSync5(hooksDir, { recursive: true });
34572
35826
  const hookScript = `#!/usr/bin/env bash
34573
35827
  # Auto-generated by: todos hooks install
@@ -34689,7 +35943,7 @@ exit 0
34689
35943
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
34690
35944
  const hookPath = `${gitDir}/hooks/post-commit`;
34691
35945
  const marker = "# todos-auto-link";
34692
- if (existsSync9(hookPath)) {
35946
+ if (existsSync10(hookPath)) {
34693
35947
  const existing = readFileSync8(hookPath, "utf-8");
34694
35948
  if (existing.includes(marker)) {
34695
35949
  console.log(chalk9.yellow("Hook already installed."));
@@ -34717,7 +35971,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
34717
35971
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
34718
35972
  const hookPath = `${gitDir}/hooks/post-commit`;
34719
35973
  const marker = "# todos-auto-link";
34720
- if (!existsSync9(hookPath)) {
35974
+ if (!existsSync10(hookPath)) {
34721
35975
  console.log(chalk9.dim("No post-commit hook found."));
34722
35976
  return;
34723
35977
  }