@hasna/todos 0.13.11 → 0.14.0

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.
Files changed (54) hide show
  1. package/dist/cli/cloud-router.d.ts.map +1 -1
  2. package/dist/cli/commands/agent-commands.d.ts +39 -0
  3. package/dist/cli/commands/agent-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/dispatch.d.ts.map +1 -1
  5. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  6. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  7. package/dist/cli/helpers.d.ts +64 -2
  8. package/dist/cli/helpers.d.ts.map +1 -1
  9. package/dist/cli/index.js +510 -149
  10. package/dist/contracts.js +102 -71
  11. package/dist/db/agent-metrics.d.ts.map +1 -1
  12. package/dist/db/audit.d.ts.map +1 -1
  13. package/dist/db/budgets.d.ts.map +1 -1
  14. package/dist/db/database.d.ts +44 -0
  15. package/dist/db/database.d.ts.map +1 -1
  16. package/dist/db/handoffs.d.ts.map +1 -1
  17. package/dist/db/task-crud.d.ts +1 -0
  18. package/dist/db/task-crud.d.ts.map +1 -1
  19. package/dist/db/task-files.d.ts.map +1 -1
  20. package/dist/db/task-lifecycle.d.ts.map +1 -1
  21. package/dist/db/task-relations.d.ts.map +1 -1
  22. package/dist/db/task-status.d.ts.map +1 -1
  23. package/dist/db/webhooks.d.ts.map +1 -1
  24. package/dist/index.js +134 -93
  25. package/dist/lib/capacity-forecasts.d.ts.map +1 -1
  26. package/dist/lib/completion-guard.d.ts.map +1 -1
  27. package/dist/lib/enum-vocabulary.d.ts +72 -0
  28. package/dist/lib/enum-vocabulary.d.ts.map +1 -0
  29. package/dist/lib/local-notifications.d.ts.map +1 -1
  30. package/dist/lib/local-reports.d.ts.map +1 -1
  31. package/dist/lib/notification-reminders.d.ts.map +1 -1
  32. package/dist/lib/saved-search-views.d.ts +12 -1
  33. package/dist/lib/saved-search-views.d.ts.map +1 -1
  34. package/dist/lib/search.d.ts.map +1 -1
  35. package/dist/lib/usage-ledger.d.ts.map +1 -1
  36. package/dist/mcp/index.js +349 -115
  37. package/dist/mcp/tools/agents.d.ts.map +1 -1
  38. package/dist/mcp/tools/task-adv-tools.d.ts.map +1 -1
  39. package/dist/mcp/tools/task-auto-tools.d.ts.map +1 -1
  40. package/dist/mcp/tools/task-resources.d.ts.map +1 -1
  41. package/dist/mcp.js +1 -1
  42. package/dist/registry.js +102 -71
  43. package/dist/release-provenance.json +5 -5
  44. package/dist/sdk/v1.generated.d.ts +2 -2
  45. package/dist/sdk/v1.generated.d.ts.map +1 -1
  46. package/dist/server/index.js +349 -115
  47. package/dist/server/openapi.d.ts +58 -0
  48. package/dist/server/openapi.d.ts.map +1 -1
  49. package/dist/server/routes.d.ts.map +1 -1
  50. package/dist/server/v1.d.ts.map +1 -1
  51. package/dist/storage.js +69 -46
  52. package/dist/types/index.d.ts +11 -0
  53. package/dist/types/index.d.ts.map +1 -1
  54. package/package.json +1 -1
package/dist/mcp/index.js CHANGED
@@ -44,7 +44,10 @@ var __require = import.meta.require;
44
44
  function isBlockingDependencyStatus(status) {
45
45
  return status !== "completed" && status !== "cancelled";
46
46
  }
47
- var TASK_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
47
+ function isTerminalStatus(status) {
48
+ return status === "completed" || status === "failed" || status === "cancelled";
49
+ }
50
+ var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
48
51
  var init_types = __esm(() => {
49
52
  TASK_STATUSES = [
50
53
  "pending",
@@ -53,6 +56,12 @@ var init_types = __esm(() => {
53
56
  "failed",
54
57
  "cancelled"
55
58
  ];
59
+ TASK_PRIORITIES = [
60
+ "low",
61
+ "medium",
62
+ "high",
63
+ "critical"
64
+ ];
56
65
  VersionConflictError = class VersionConflictError extends Error {
57
66
  taskId;
58
67
  expectedVersion;
@@ -4111,14 +4120,17 @@ var exports_database = {};
4111
4120
  __export(exports_database, {
4112
4121
  uuid: () => uuid,
4113
4122
  resolvePartialId: () => resolvePartialId,
4123
+ resolveAssignedToAliases: () => resolveAssignedToAliases,
4114
4124
  resetDatabase: () => resetDatabase,
4115
4125
  now: () => now,
4126
+ lowerInClause: () => lowerInClause,
4116
4127
  lockExpiryCutoff: () => lockExpiryCutoff,
4117
4128
  isLockExpired: () => isLockExpired,
4118
4129
  getDatabasePath: () => getDatabasePath,
4119
4130
  getDatabase: () => getDatabase,
4120
4131
  closeDatabase: () => closeDatabase,
4121
4132
  clearExpiredLocks: () => clearExpiredLocks,
4133
+ assignedToAliasSet: () => assignedToAliasSet,
4122
4134
  LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
4123
4135
  });
4124
4136
  import { Database } from "bun:sqlite";
@@ -4347,6 +4359,33 @@ function resolvePartialId(db, table, partialId) {
4347
4359
  }
4348
4360
  return null;
4349
4361
  }
4362
+ function resolveAssignedToAliases(db, ref) {
4363
+ const aliases = new Set([ref]);
4364
+ let agentId;
4365
+ try {
4366
+ agentId = resolvePartialId(db, "agents", ref);
4367
+ } catch (err) {
4368
+ if (!(err instanceof IdentityAliasAmbiguousError))
4369
+ throw err;
4370
+ agentId = null;
4371
+ }
4372
+ if (agentId) {
4373
+ aliases.add(agentId);
4374
+ const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
4375
+ if (row?.name)
4376
+ aliases.add(row.name);
4377
+ }
4378
+ return [...aliases];
4379
+ }
4380
+ function assignedToAliasSet(db, ref) {
4381
+ return new Set(resolveAssignedToAliases(db, ref).map((a) => a.toLowerCase()));
4382
+ }
4383
+ function lowerInClause(column, values, params) {
4384
+ if (values.length === 0)
4385
+ return "1=0";
4386
+ params.push(...values.map((v) => v.toLowerCase()));
4387
+ return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
4388
+ }
4350
4389
  var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
4351
4390
  var init_database = __esm(() => {
4352
4391
  init_schema();
@@ -9991,17 +10030,19 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
9991
10030
  throw new CompletionGuardError(`Too fast: task was started ${Math.floor(elapsedSeconds)}s ago. Minimum work duration is ${config.min_work_seconds}s. Wait ${remaining}s.`, remaining);
9992
10031
  }
9993
10032
  }
10033
+ const assignedAliasParams = [];
10034
+ const assignedInClause = agent ? lowerInClause("assigned_to", resolveAssignedToAliases(db, agent), assignedAliasParams) : "";
9994
10035
  if (agent && config.max_completions_per_window && config.window_minutes) {
9995
10036
  const windowStart = new Date(Date.now() - config.window_minutes * 60 * 1000).toISOString();
9996
10037
  const result = db.query(`SELECT COUNT(*) as count FROM tasks
9997
- WHERE completed_at > ? AND (assigned_to = ? OR agent_id = ?)`).get(windowStart, agent, agent);
10038
+ WHERE completed_at > ? AND (${assignedInClause} OR agent_id = ?)`).get(windowStart, ...assignedAliasParams, agent);
9998
10039
  if (result.count >= config.max_completions_per_window) {
9999
10040
  throw new CompletionGuardError(`Rate limit: ${result.count} tasks completed in the last ${config.window_minutes} minutes (max ${config.max_completions_per_window}). Slow down.`);
10000
10041
  }
10001
10042
  }
10002
10043
  if (agent && config.cooldown_seconds) {
10003
10044
  const result = db.query(`SELECT MAX(completed_at) as last_completed FROM tasks
10004
- WHERE completed_at IS NOT NULL AND (assigned_to = ? OR agent_id = ?) AND id != ?`).get(agent, agent, task.id);
10045
+ WHERE completed_at IS NOT NULL AND (${assignedInClause} OR agent_id = ?) AND id != ?`).get(...assignedAliasParams, agent, task.id);
10005
10046
  if (result.last_completed) {
10006
10047
  const elapsedSeconds = (Date.now() - new Date(result.last_completed).getTime()) / 1000;
10007
10048
  if (elapsedSeconds < config.cooldown_seconds) {
@@ -10015,6 +10056,7 @@ var init_completion_guard = __esm(() => {
10015
10056
  init_types();
10016
10057
  init_config2();
10017
10058
  init_projects();
10059
+ init_database();
10018
10060
  });
10019
10061
 
10020
10062
  // src/lib/event-emission-safety.ts
@@ -12013,13 +12055,17 @@ function logDelivery(d, webhookId, event, payload, statusCode, response, attempt
12013
12055
  const id = uuid();
12014
12056
  d.run(`INSERT INTO webhook_deliveries (id, webhook_id, event, payload, status_code, response, attempt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, webhookId, event, payload, statusCode, response, attempt, now()]);
12015
12057
  }
12016
- function matchesScope(wh, payload) {
12058
+ function matchesScope(wh, payload, db) {
12017
12059
  if (wh.project_id && payload.project_id !== wh.project_id)
12018
12060
  return false;
12019
12061
  if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
12020
12062
  return false;
12021
- if (wh.agent_id && payload.agent_id !== wh.agent_id && payload.assigned_to !== wh.agent_id)
12022
- return false;
12063
+ if (wh.agent_id && payload.agent_id !== wh.agent_id) {
12064
+ const aliasSet = assignedToAliasSet(db, wh.agent_id);
12065
+ const assignedTo = typeof payload.assigned_to === "string" ? payload.assigned_to.toLowerCase() : "";
12066
+ if (!aliasSet.has(assignedTo))
12067
+ return false;
12068
+ }
12023
12069
  if (wh.task_id && payload.id !== wh.task_id)
12024
12070
  return false;
12025
12071
  return true;
@@ -12086,7 +12132,7 @@ async function dispatchWebhook2(event, payload, db) {
12086
12132
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
12087
12133
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
12088
12134
  for (const wh of webhooks) {
12089
- if (!matchesScope(wh, payloadObj))
12135
+ if (!matchesScope(wh, payloadObj, d))
12090
12136
  continue;
12091
12137
  const body = JSON.stringify({ event, payload, timestamp: now() });
12092
12138
  deliverWebhook(wh, event, body, 1, d).catch((err) => {
@@ -13202,14 +13248,16 @@ function getNextTask(agentId, filters, db) {
13202
13248
  conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
13203
13249
  const where = conditions.join(" AND ");
13204
13250
  let recentProjectIds = [];
13251
+ const assignedAliasParams = [];
13252
+ const assignedInClause = agentId ? lowerInClause("assigned_to", resolveAssignedToAliases(d, agentId), assignedAliasParams) : "";
13205
13253
  if (agentId) {
13206
- const recentRows = d.query(`SELECT DISTINCT project_id FROM tasks WHERE assigned_to = ? AND status = 'completed' AND project_id IS NOT NULL ORDER BY completed_at DESC LIMIT 3`).all(agentId);
13254
+ const recentRows = d.query(`SELECT DISTINCT project_id FROM tasks WHERE ${assignedInClause} AND status = 'completed' AND project_id IS NOT NULL ORDER BY completed_at DESC LIMIT 3`).all(...assignedAliasParams);
13207
13255
  recentProjectIds = recentRows.map((r) => r.project_id);
13208
13256
  }
13209
13257
  let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
13210
13258
  if (agentId) {
13211
- sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
13212
- params.push(agentId);
13259
+ sql += `CASE WHEN ${assignedInClause} THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
13260
+ params.push(...assignedAliasParams);
13213
13261
  }
13214
13262
  if (recentProjectIds.length > 0) {
13215
13263
  const placeholders = recentProjectIds.map(() => "?").join(",");
@@ -13603,30 +13651,6 @@ function getTaskWithRelations(id, db) {
13603
13651
  checklist
13604
13652
  };
13605
13653
  }
13606
- function resolveAssignedToAliases(db, ref) {
13607
- const aliases = new Set([ref]);
13608
- let agentId;
13609
- try {
13610
- agentId = resolvePartialId(db, "agents", ref);
13611
- } catch (err) {
13612
- if (!(err instanceof IdentityAliasAmbiguousError))
13613
- throw err;
13614
- agentId = null;
13615
- }
13616
- if (agentId) {
13617
- aliases.add(agentId);
13618
- const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
13619
- if (row?.name)
13620
- aliases.add(row.name);
13621
- }
13622
- return [...aliases];
13623
- }
13624
- function lowerInClause(column, values, params) {
13625
- if (values.length === 0)
13626
- return "1=0";
13627
- params.push(...values.map((v) => v.toLowerCase()));
13628
- return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
13629
- }
13630
13654
  function listTasks(filter = {}, db) {
13631
13655
  const d = db || getDatabase();
13632
13656
  const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
@@ -13912,11 +13936,13 @@ function updateTask(id, input, db) {
13912
13936
  }
13913
13937
  sets.push("status = ?");
13914
13938
  params.push(input.status);
13939
+ if (isTerminalStatus(input.status)) {
13940
+ sets.push("locked_by = NULL");
13941
+ sets.push("locked_at = NULL");
13942
+ }
13915
13943
  if (input.status === "completed") {
13916
13944
  sets.push("completed_at = ?");
13917
13945
  params.push(completionTimestamp);
13918
- sets.push("locked_by = NULL");
13919
- sets.push("locked_at = NULL");
13920
13946
  } else if (task.status === "completed" && input.completed_at === undefined) {
13921
13947
  sets.push("completed_at = NULL");
13922
13948
  }
@@ -14040,6 +14066,7 @@ function updateTask(id, input, db) {
14040
14066
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
14041
14067
  const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
14042
14068
  const completedNow = input.status === "completed";
14069
+ const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
14043
14070
  const updatedTask = {
14044
14071
  ...task,
14045
14072
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
@@ -14047,8 +14074,8 @@ function updateTask(id, input, db) {
14047
14074
  metadata: input.metadata ?? task.metadata,
14048
14075
  version: task.version + 1,
14049
14076
  updated_at: timestamp2,
14050
- locked_by: completedNow ? null : task.locked_by,
14051
- locked_at: completedNow ? null : task.locked_at,
14077
+ locked_by: terminalNow ? null : task.locked_by,
14078
+ locked_at: terminalNow ? null : task.locked_at,
14052
14079
  completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
14053
14080
  sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
14054
14081
  actual_minutes: input.actual_minutes ?? task.actual_minutes,
@@ -14099,6 +14126,7 @@ function deleteTask(id, db) {
14099
14126
  var init_task_crud = __esm(() => {
14100
14127
  init_types();
14101
14128
  init_database();
14129
+ init_database();
14102
14130
  init_completion_guard();
14103
14131
  init_event_emission_safety();
14104
14132
  init_event_hooks();
@@ -14261,8 +14289,8 @@ function getTaskStats(filters, db) {
14261
14289
  params.push(filters.task_list_id);
14262
14290
  }
14263
14291
  if (filters?.agent_id) {
14264
- conditions.push("(agent_id = ? OR assigned_to = ?)");
14265
- params.push(filters.agent_id, filters.agent_id);
14292
+ params.push(filters.agent_id);
14293
+ conditions.push(`(agent_id = ? OR ${lowerInClause("assigned_to", resolveAssignedToAliases(d, filters.agent_id), params)})`);
14266
14294
  }
14267
14295
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
14268
14296
  const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
@@ -14445,8 +14473,7 @@ function getEscalatedTasks(opts = {}, db, at = new Date) {
14445
14473
  params.push(opts.project_id);
14446
14474
  }
14447
14475
  if (opts.agent_id) {
14448
- conditions.push("assigned_to = ?");
14449
- params.push(opts.agent_id);
14476
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
14450
14477
  }
14451
14478
  const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC, created_at ASC`).all(...params);
14452
14479
  return rows.map(rowToTask).map((task) => {
@@ -14494,8 +14521,7 @@ function notifyUpcomingDeadlines(opts = {}, db) {
14494
14521
  params.push(opts.project_id);
14495
14522
  }
14496
14523
  if (opts.agent_id) {
14497
- conditions.push("assigned_to = ?");
14498
- params.push(opts.agent_id);
14524
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
14499
14525
  }
14500
14526
  const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC`).all(...params);
14501
14527
  return rows.map(rowToTask);
@@ -14753,13 +14779,14 @@ function getTimeReport(opts, db) {
14753
14779
  params.push(opts.plan_id);
14754
14780
  }
14755
14781
  if (opts?.agent_id) {
14782
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.agent_id), params);
14756
14783
  conditions.push(`(
14757
- t.assigned_to = ?
14784
+ ${assignedInClause}
14758
14785
  OR t.agent_id = ?
14759
14786
  OR EXISTS (SELECT 1 FROM task_time_logs ttl WHERE ttl.task_id = t.id AND ttl.agent_id = ?)
14760
14787
  OR EXISTS (SELECT 1 FROM focus_sessions fs WHERE fs.task_id = t.id AND fs.agent_id = ?)
14761
14788
  )`);
14762
- params.push(opts.agent_id, opts.agent_id, opts.agent_id, opts.agent_id);
14789
+ params.push(opts.agent_id, opts.agent_id, opts.agent_id);
14763
14790
  }
14764
14791
  if (opts?.since) {
14765
14792
  conditions.push("(t.completed_at >= ? OR t.updated_at >= ?)");
@@ -15755,7 +15782,7 @@ function listActiveFiles(db) {
15755
15782
  a.name AS agent_name
15756
15783
  FROM task_files tf
15757
15784
  JOIN tasks t ON tf.task_id = t.id
15758
- LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
15785
+ LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name))))
15759
15786
  WHERE t.status = 'in_progress'
15760
15787
  AND tf.status != 'removed'
15761
15788
  ORDER BY tf.updated_at DESC
@@ -23056,11 +23083,12 @@ function runAlerts(input, db, checkedAt, quieted) {
23056
23083
  if (input.include_runs === false)
23057
23084
  return [];
23058
23085
  const since = Date.parse(input.run_since || new Date(Date.parse(checkedAt) - 24 * 60 * 60000).toISOString());
23086
+ const runAlertAliases = input.agent_id ? assignedToAliasSet(db, input.agent_id) : null;
23059
23087
  return listTaskRuns(undefined, db).filter((run) => run.completed_at && Date.parse(run.completed_at) >= since).map((run) => {
23060
23088
  const task = getTask(run.task_id, db);
23061
23089
  if (input.project_id && task?.project_id !== input.project_id)
23062
23090
  return null;
23063
- if (input.agent_id && run.agent_id !== input.agent_id && task?.assigned_to !== input.agent_id)
23091
+ if (input.agent_id && run.agent_id !== input.agent_id && !(runAlertAliases && runAlertAliases.has((task?.assigned_to ?? "").toLowerCase())))
23064
23092
  return null;
23065
23093
  const failed = ["failed", "cancelled"].includes(run.status);
23066
23094
  return {
@@ -23105,6 +23133,7 @@ async function checkLocalNotifications(input = {}, db) {
23105
23133
  const warnings = [];
23106
23134
  if (Number.isNaN(checkedMs))
23107
23135
  warnings.push("Invalid check timestamp; due-soon window may be incomplete.");
23136
+ const notifyAgentAliases = input.agent_id ? assignedToAliasSet(d, input.agent_id) : null;
23108
23137
  const alerts = [];
23109
23138
  for (const escalation of getEscalatedTasks({ project_id: input.project_id, agent_id: input.agent_id }, d, new Date(checkedAt))) {
23110
23139
  for (const reason of escalation.reasons) {
@@ -23116,7 +23145,7 @@ async function checkLocalNotifications(input = {}, db) {
23116
23145
  for (const task of listTasks({ project_id: input.project_id, include_archived: false, limit: 5000 }, d)) {
23117
23146
  if (!unfinished(task) || !task.due_at)
23118
23147
  continue;
23119
- if (input.agent_id && task.assigned_to !== input.agent_id && task.agent_id !== input.agent_id)
23148
+ if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
23120
23149
  continue;
23121
23150
  if (task.due_at > checkedAt && task.due_at <= dueSoonUntil) {
23122
23151
  alerts.push(taskAlert(task, "task_due_soon", "task.due_soon", "warning", `Due soon: ${task.title}`, task.due_at, quieted));
@@ -23124,7 +23153,7 @@ async function checkLocalNotifications(input = {}, db) {
23124
23153
  }
23125
23154
  }
23126
23155
  for (const task of getStaleTasks(staleMinutes, { project_id: input.project_id }, d)) {
23127
- if (input.agent_id && task.assigned_to !== input.agent_id && task.agent_id !== input.agent_id)
23156
+ if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
23128
23157
  continue;
23129
23158
  alerts.push(taskAlert(task, "task_stale", "task.stale", "warning", `Stale task: ${task.title}`, checkedAt, quieted, { stale_minutes: staleMinutes }));
23130
23159
  }
@@ -25144,10 +25173,12 @@ function matchesProfile(profile, query) {
25144
25173
  return false;
25145
25174
  return true;
25146
25175
  }
25147
- function taskMatchesAgent(task, agentId) {
25176
+ function taskMatchesAgent(task, agentId, aliasSet) {
25148
25177
  if (!agentId)
25149
25178
  return true;
25150
- return task.assigned_to === agentId || task.agent_id === agentId;
25179
+ if (task.agent_id === agentId)
25180
+ return true;
25181
+ return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
25151
25182
  }
25152
25183
  function estimateRemaining(task) {
25153
25184
  if (task.status === "completed" || task.status === "cancelled")
@@ -25217,7 +25248,8 @@ function removeCapacityProfile(idOrAgent, projectId) {
25217
25248
  }
25218
25249
  function getPlanningForecast(input = {}, db) {
25219
25250
  const startDate = input.start_date ? dateOnly(input.start_date) : todayIso();
25220
- const tasks = listTasks({ project_id: input.project_id, plan_id: input.plan_id, limit: 1e4 }, db).filter((task) => taskMatchesAgent(task, input.agent_id));
25251
+ const aliasSet = input.agent_id ? assignedToAliasSet(db || getDatabase(), input.agent_id) : undefined;
25252
+ const tasks = listTasks({ project_id: input.project_id, plan_id: input.plan_id, limit: 1e4 }, db).filter((task) => taskMatchesAgent(task, input.agent_id, aliasSet));
25221
25253
  const reportByTask = new Map(getTimeReport({ project_id: input.project_id, plan_id: input.plan_id, agent_id: input.agent_id, include_open: true }, db).map((entry) => [entry.task_id, entry]));
25222
25254
  const profiles = listCapacityProfiles({ agent_id: input.agent_id, project_id: input.project_id });
25223
25255
  const globalProfiles = input.project_id ? listCapacityProfiles({ agent_id: input.agent_id }).filter((profile) => profile.project_id === null) : [];
@@ -25315,6 +25347,7 @@ function renderPlanningForecastMarkdown(forecast) {
25315
25347
  }
25316
25348
  var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
25317
25349
  var init_capacity_forecasts = __esm(() => {
25350
+ init_database();
25318
25351
  init_tasks();
25319
25352
  init_task_relations();
25320
25353
  init_config2();
@@ -25976,8 +26009,7 @@ function searchTasks(options, projectId, taskListId, db) {
25976
26009
  }
25977
26010
  }
25978
26011
  if (opts.assigned_to) {
25979
- sql += " AND t.assigned_to = ?";
25980
- params.push(opts.assigned_to);
26012
+ sql += ` AND ${lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.assigned_to), params)}`;
25981
26013
  }
25982
26014
  if (opts.agent_id) {
25983
26015
  sql += " AND t.agent_id = ?";
@@ -26029,7 +26061,8 @@ __export(exports_saved_search_views, {
26029
26061
  normalizeScope: () => normalizeScope,
26030
26062
  listSearchViews: () => listSearchViews,
26031
26063
  getSearchView: () => getSearchView,
26032
- deleteSearchView: () => deleteSearchView
26064
+ deleteSearchView: () => deleteSearchView,
26065
+ SAVED_SEARCH_SCOPES: () => SAVED_SEARCH_SCOPES
26033
26066
  });
26034
26067
  function parseFilters(value) {
26035
26068
  if (!value)
@@ -26049,10 +26082,8 @@ function rowToSavedSearchView(row) {
26049
26082
  };
26050
26083
  }
26051
26084
  function normalizeScope(scope) {
26052
- if (scope === "all" || scope === "tasks" || scope === "projects" || scope === "plans" || scope === "runs" || scope === "comments") {
26053
- return scope;
26054
- }
26055
- return "tasks";
26085
+ const candidate = (scope ?? "").trim().toLowerCase();
26086
+ return SAVED_SEARCH_SCOPES.includes(candidate) ? candidate : "tasks";
26056
26087
  }
26057
26088
  function normalizeName4(name) {
26058
26089
  const normalized = name.trim();
@@ -26355,10 +26386,12 @@ function runSearchView(idOrName, db) {
26355
26386
  throw new Error(`Saved search view not found: ${idOrName}`);
26356
26387
  return { ...runSavedSearch(view.filters, view.scope, d), view };
26357
26388
  }
26389
+ var SAVED_SEARCH_SCOPES;
26358
26390
  var init_saved_search_views = __esm(() => {
26359
26391
  init_database();
26360
26392
  init_local_fields();
26361
26393
  init_search();
26394
+ SAVED_SEARCH_SCOPES = ["all", "tasks", "projects", "plans", "runs", "comments"];
26362
26395
  });
26363
26396
 
26364
26397
  // src/mcp/tools/task-project-tools.ts
@@ -28908,8 +28941,10 @@ function getLatestHandoff(agentId, projectId, db) {
28908
28941
  function createSessionRecoveryHandoff(input, db) {
28909
28942
  const d = db || getDatabase();
28910
28943
  const limit = input.limit || 20;
28911
- const conditions = ["status = 'in_progress'", "(assigned_to = ? OR agent_id = ? OR locked_by = ?)"];
28912
- const params = [input.agent_id, input.agent_id, input.agent_id];
28944
+ const params = [];
28945
+ const assignedInClause = lowerInClause("assigned_to", resolveAssignedToAliases(d, input.agent_id), params);
28946
+ const conditions = ["status = 'in_progress'", `(${assignedInClause} OR agent_id = ? OR locked_by = ?)`];
28947
+ params.push(input.agent_id, input.agent_id);
28913
28948
  if (input.session_id) {
28914
28949
  conditions.push("session_id = ?");
28915
28950
  params.push(input.session_id);
@@ -30133,7 +30168,8 @@ function registerTaskAutoTools(server, ctx) {
30133
30168
  const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
30134
30169
  const now4 = Date.now();
30135
30170
  const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
30136
- const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
30171
+ const blockedAliases = assignedToAliasSet(getDatabase(), effectiveAgentId);
30172
+ const blocked = getBlockedTasks2().filter((t) => blockedAliases.has((t.assigned_to ?? "").toLowerCase()));
30137
30173
  const workload = {
30138
30174
  in_progress: assigned.filter((t) => t.status === "in_progress").length,
30139
30175
  pending: assigned.filter((t) => t.status === "pending").length,
@@ -30170,17 +30206,38 @@ function registerTaskAutoTools(server, ctx) {
30170
30206
  if (agents.length === 0)
30171
30207
  return { content: [{ type: "text", text: "No active agents available for rebalancing." }] };
30172
30208
  const activeTasks = listTasks3({ project_id: resolvedProjectId, status: ["pending", "in_progress"], limit: 1000 }, undefined);
30173
- const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => t.assigned_to === agent.id).length]));
30209
+ const agentKeyByAlias = new Map;
30210
+ const ambiguousAgentAliases = new Set;
30211
+ const indexAgentAlias = (alias, agentId) => {
30212
+ const normalizedAlias2 = alias.toLowerCase();
30213
+ if (ambiguousAgentAliases.has(normalizedAlias2))
30214
+ return;
30215
+ const existingAgentId = agentKeyByAlias.get(normalizedAlias2);
30216
+ if (existingAgentId !== undefined && existingAgentId !== agentId) {
30217
+ agentKeyByAlias.delete(normalizedAlias2);
30218
+ ambiguousAgentAliases.add(normalizedAlias2);
30219
+ return;
30220
+ }
30221
+ agentKeyByAlias.set(normalizedAlias2, agentId);
30222
+ };
30223
+ for (const agent of agents) {
30224
+ indexAgentAlias(String(agent.id), agent.id);
30225
+ if (agent.name)
30226
+ indexAgentAlias(String(agent.name), agent.id);
30227
+ }
30228
+ const canonicalAgentKey = (assignedTo) => assignedTo ? agentKeyByAlias.get(assignedTo.toLowerCase()) : undefined;
30229
+ const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => canonicalAgentKey(t.assigned_to) === agent.id).length]));
30174
30230
  let moved = 0;
30175
30231
  let skipped = 0;
30176
- for (const task of activeTasks.filter((t) => t.status === "pending" && t.assigned_to && (load.get(t.assigned_to) ?? 0) > limit)) {
30177
- const target = agents.filter((agent) => agent.id !== task.assigned_to).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
30232
+ for (const task of activeTasks.filter((t) => t.status === "pending" && canonicalAgentKey(t.assigned_to) && (load.get(canonicalAgentKey(t.assigned_to)) ?? 0) > limit)) {
30233
+ const currentAgentKey = canonicalAgentKey(task.assigned_to);
30234
+ const target = agents.filter((agent) => agent.id !== currentAgentKey).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
30178
30235
  if (!target || (load.get(target.id) ?? 0) >= limit) {
30179
30236
  skipped++;
30180
30237
  continue;
30181
30238
  }
30182
30239
  updateTask2(task.id, { assigned_to: target.id, version: task.version });
30183
- load.set(task.assigned_to, (load.get(task.assigned_to) ?? 1) - 1);
30240
+ load.set(currentAgentKey, (load.get(currentAgentKey) ?? 1) - 1);
30184
30241
  load.set(target.id, (load.get(target.id) ?? 0) + 1);
30185
30242
  moved++;
30186
30243
  }
@@ -30359,6 +30416,7 @@ ${lines.join(`
30359
30416
  var init_task_auto_tools = __esm(() => {
30360
30417
  init_zod();
30361
30418
  init_cloud_router();
30419
+ init_database();
30362
30420
  });
30363
30421
 
30364
30422
  // src/lib/context-packs.ts
@@ -31538,7 +31596,8 @@ ${JSON.stringify(task.metadata, null, 2)}` : null
31538
31596
  }, undefined);
31539
31597
  const completedYesterday = completed.filter((t) => t.completed_at && t.completed_at.startsWith(yesterdayStr));
31540
31598
  const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
31541
- const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => t.assigned_to === effectiveAgentId);
31599
+ const blockedAliases = effectiveAgentId ? assignedToAliasSet(getDatabase(), effectiveAgentId) : null;
31600
+ const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => blockedAliases ? blockedAliases.has((t.assigned_to ?? "").toLowerCase()) : false);
31542
31601
  const lines = [
31543
31602
  `Standup for ${effectiveAgentId} (${effectiveProjectId ? `project: ${effectiveProjectId.slice(0, 8)}` : "all projects"})`,
31544
31603
  inProgress.length > 0 ? `
@@ -31766,6 +31825,7 @@ var init_task_adv_tools = __esm(() => {
31766
31825
  init_zod();
31767
31826
  init_token_utils();
31768
31827
  init_cloud_router();
31828
+ init_database();
31769
31829
  });
31770
31830
 
31771
31831
  // src/mcp/tools/task-meta-tools.ts
@@ -33683,27 +33743,29 @@ function getAgentMetrics(agentId, opts, db) {
33683
33743
  const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
33684
33744
  if (!agent)
33685
33745
  return null;
33746
+ const assignedAliases = resolveAssignedToAliases(d, agent.id).map((a) => a.toLowerCase());
33747
+ const assignedInClause = `LOWER(assigned_to) IN (${assignedAliases.map(() => "?").join(",")})`;
33686
33748
  let projectFilter = "";
33687
- const params = [agent.id, agent.id];
33749
+ const params = [agent.id, ...assignedAliases];
33688
33750
  if (opts?.project_id) {
33689
33751
  projectFilter = " AND project_id = ?";
33690
33752
  params.push(opts.project_id);
33691
33753
  }
33692
- const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}`).get(...params).count;
33693
- const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'failed'${projectFilter}`).get(...params).count;
33694
- const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'in_progress'${projectFilter}`).get(...params).count;
33754
+ const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}`).get(...params).count;
33755
+ const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'failed'${projectFilter}`).get(...params).count;
33756
+ const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'in_progress'${projectFilter}`).get(...params).count;
33695
33757
  const total = completed + failed;
33696
33758
  const completionRate = total > 0 ? completed / total : 0;
33697
33759
  const avgTime = d.query(`SELECT AVG(
33698
33760
  (julianday(completed_at) - julianday(created_at)) * 24 * 60
33699
33761
  ) as avg_minutes
33700
33762
  FROM tasks
33701
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
33763
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
33702
33764
  const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
33703
33765
  FROM tasks
33704
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
33766
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
33705
33767
  const reviewTasks = d.query(`SELECT metadata FROM tasks
33706
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}
33768
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}
33707
33769
  AND metadata LIKE '%_review_score%'`).all(...params);
33708
33770
  let reviewScoreAvg = null;
33709
33771
  if (reviewTasks.length > 0) {
@@ -34167,14 +34229,15 @@ function millisBetween(start, end) {
34167
34229
  return 0;
34168
34230
  return endMs - startMs;
34169
34231
  }
34170
- function addTaskScope(where, params, options, alias = "t") {
34232
+ function addTaskScope(where, params, options, db, alias = "t") {
34171
34233
  if (options.project_id) {
34172
34234
  where.push(`${alias}.project_id = ?`);
34173
34235
  params.push(options.project_id);
34174
34236
  }
34175
34237
  if (options.agent_id) {
34176
- where.push(`(${alias}.agent_id = ? OR ${alias}.assigned_to = ?)`);
34177
- params.push(options.agent_id, options.agent_id);
34238
+ params.push(options.agent_id);
34239
+ const assignedInClause = lowerInClause(`${alias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
34240
+ where.push(`(${alias}.agent_id = ? OR ${assignedInClause})`);
34178
34241
  }
34179
34242
  if (options.since) {
34180
34243
  where.push(`${alias}.created_at >= ?`);
@@ -34185,14 +34248,15 @@ function addTaskScope(where, params, options, alias = "t") {
34185
34248
  params.push(options.until);
34186
34249
  }
34187
34250
  }
34188
- function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
34251
+ function addRunScope(where, params, options, db, runAlias = "r", taskAlias = "t") {
34189
34252
  if (options.project_id) {
34190
34253
  where.push(`${taskAlias}.project_id = ?`);
34191
34254
  params.push(options.project_id);
34192
34255
  }
34193
34256
  if (options.agent_id) {
34194
- where.push(`(${runAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${taskAlias}.assigned_to = ?)`);
34195
- params.push(options.agent_id, options.agent_id, options.agent_id);
34257
+ params.push(options.agent_id, options.agent_id);
34258
+ const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
34259
+ where.push(`(${runAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
34196
34260
  }
34197
34261
  if (options.since) {
34198
34262
  where.push(`${runAlias}.started_at >= ?`);
@@ -34203,14 +34267,15 @@ function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
34203
34267
  params.push(options.until);
34204
34268
  }
34205
34269
  }
34206
- function addTraceScope(where, params, options, traceAlias = "tr", taskAlias = "t") {
34270
+ function addTraceScope(where, params, options, db, traceAlias = "tr", taskAlias = "t") {
34207
34271
  if (options.project_id) {
34208
34272
  where.push(`${taskAlias}.project_id = ?`);
34209
34273
  params.push(options.project_id);
34210
34274
  }
34211
34275
  if (options.agent_id) {
34212
- where.push(`(${traceAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${taskAlias}.assigned_to = ?)`);
34213
- params.push(options.agent_id, options.agent_id, options.agent_id);
34276
+ params.push(options.agent_id, options.agent_id);
34277
+ const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
34278
+ where.push(`(${traceAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
34214
34279
  }
34215
34280
  if (options.since) {
34216
34281
  where.push(`${traceAlias}.created_at >= ?`);
@@ -34270,7 +34335,7 @@ function createLocalUsageLedger(options = {}, db) {
34270
34335
  const generatedAt = options.generated_at || new Date().toISOString();
34271
34336
  const taskWhere = [];
34272
34337
  const taskParams = [];
34273
- addTaskScope(taskWhere, taskParams, options);
34338
+ addTaskScope(taskWhere, taskParams, options, d);
34274
34339
  const taskClause = taskWhere.length ? `WHERE ${taskWhere.join(" AND ")}` : "";
34275
34340
  const taskTotals = queryOne(d, `SELECT COUNT(*) as tasks, COALESCE(SUM(cost_tokens), 0) as task_tokens, COALESCE(SUM(cost_usd), 0) as task_cost_usd FROM tasks t ${taskClause}`, taskParams);
34276
34341
  let projectCount = 0;
@@ -34279,14 +34344,14 @@ function createLocalUsageLedger(options = {}, db) {
34279
34344
  } else if (options.agent_id) {
34280
34345
  const projectWhere = ["t.project_id IS NOT NULL"];
34281
34346
  const projectParams = [];
34282
- addTaskScope(projectWhere, projectParams, options);
34347
+ addTaskScope(projectWhere, projectParams, options, d);
34283
34348
  projectCount = queryOne(d, `SELECT COUNT(DISTINCT t.project_id) as count FROM tasks t WHERE ${projectWhere.join(" AND ")}`, projectParams).count;
34284
34349
  } else {
34285
34350
  projectCount = queryOne(d, "SELECT COUNT(*) as count FROM projects", []).count;
34286
34351
  }
34287
34352
  const runWhere = [];
34288
34353
  const runParams = [];
34289
- addRunScope(runWhere, runParams, options);
34354
+ addRunScope(runWhere, runParams, options, d);
34290
34355
  const runClause = runWhere.length ? `WHERE ${runWhere.join(" AND ")}` : "";
34291
34356
  const runs = queryAll(d, `SELECT r.id, r.started_at, r.completed_at, r.metadata
34292
34357
  FROM task_runs r JOIN tasks t ON t.id = r.task_id
@@ -34303,7 +34368,7 @@ function createLocalUsageLedger(options = {}, db) {
34303
34368
  ${runClause}`, runParams);
34304
34369
  const traceWhere = [];
34305
34370
  const traceParams = [];
34306
- addTraceScope(traceWhere, traceParams, options);
34371
+ addTraceScope(traceWhere, traceParams, options, d);
34307
34372
  const traceClause = traceWhere.length ? `WHERE ${traceWhere.join(" AND ")}` : "";
34308
34373
  const traceTotals = queryOne(d, `SELECT COUNT(*) as traces,
34309
34374
  COALESCE(SUM(tr.tokens), 0) as tokens,
@@ -34447,10 +34512,12 @@ function limitValue(value) {
34447
34512
  function isTerminal(task) {
34448
34513
  return task.status === "completed" || task.status === "failed" || task.status === "cancelled";
34449
34514
  }
34450
- function sameAgent(task, agentId) {
34515
+ function sameAgent(task, agentId, aliasSet) {
34451
34516
  if (!agentId)
34452
34517
  return true;
34453
- return task.assigned_to === agentId || task.agent_id === agentId;
34518
+ if (task.agent_id === agentId)
34519
+ return true;
34520
+ return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
34454
34521
  }
34455
34522
  function withinTaskWindow(task, options) {
34456
34523
  const time = Date.parse(task.updated_at);
@@ -34463,11 +34530,12 @@ function withinTaskWindow(task, options) {
34463
34530
  return true;
34464
34531
  }
34465
34532
  function scopedTasks(options, db) {
34533
+ const aliasSet = options.agent_id ? assignedToAliasSet(db, options.agent_id) : undefined;
34466
34534
  return listTasks({
34467
34535
  project_id: options.project_id,
34468
34536
  plan_id: options.plan_id,
34469
34537
  include_archived: false
34470
- }, db).filter((task) => sameAgent(task, options.agent_id) && withinTaskWindow(task, options));
34538
+ }, db).filter((task) => sameAgent(task, options.agent_id, aliasSet) && withinTaskWindow(task, options));
34471
34539
  }
34472
34540
  function summarizeTask(task) {
34473
34541
  return {
@@ -34533,7 +34601,7 @@ function initialAgentSummary(agentId) {
34533
34601
  }
34534
34602
  };
34535
34603
  }
34536
- function addScopeClauses(where, params, options, timeColumn) {
34604
+ function addScopeClauses(where, params, options, timeColumn, db) {
34537
34605
  if (options.project_id) {
34538
34606
  where.push("t.project_id = ?");
34539
34607
  params.push(options.project_id);
@@ -34543,8 +34611,9 @@ function addScopeClauses(where, params, options, timeColumn) {
34543
34611
  params.push(options.plan_id);
34544
34612
  }
34545
34613
  if (options.agent_id) {
34546
- where.push("(r.agent_id = ? OR t.agent_id = ? OR t.assigned_to = ?)");
34547
- params.push(options.agent_id, options.agent_id, options.agent_id);
34614
+ params.push(options.agent_id, options.agent_id);
34615
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
34616
+ where.push(`(r.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
34548
34617
  }
34549
34618
  if (options.since) {
34550
34619
  where.push(`${timeColumn} >= ?`);
@@ -34558,7 +34627,7 @@ function addScopeClauses(where, params, options, timeColumn) {
34558
34627
  function loadRuns(options, db) {
34559
34628
  const where = ["t.archived_at IS NULL"];
34560
34629
  const params = [];
34561
- addScopeClauses(where, params, options, "r.started_at");
34630
+ addScopeClauses(where, params, options, "r.started_at", db);
34562
34631
  return db.query(`
34563
34632
  SELECT
34564
34633
  r.id,
@@ -34598,8 +34667,9 @@ function loadVerifications(options, db) {
34598
34667
  params.push(options.plan_id);
34599
34668
  }
34600
34669
  if (options.agent_id) {
34601
- where.push("(v.agent_id = ? OR t.agent_id = ? OR t.assigned_to = ?)");
34602
- params.push(options.agent_id, options.agent_id, options.agent_id);
34670
+ params.push(options.agent_id, options.agent_id);
34671
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
34672
+ where.push(`(v.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
34603
34673
  }
34604
34674
  if (options.since) {
34605
34675
  where.push("v.run_at >= ?");
@@ -34821,7 +34891,6 @@ function renderLocalReportMarkdown(report) {
34821
34891
  }
34822
34892
  var LOCAL_REPORT_SCHEMA_VERSION = 1, LOCAL_REPORT_TYPES;
34823
34893
  var init_local_reports = __esm(() => {
34824
- init_database();
34825
34894
  init_database();
34826
34895
  init_plans();
34827
34896
  init_tasks();
@@ -34843,7 +34912,7 @@ var package_default;
34843
34912
  var init_package = __esm(() => {
34844
34913
  package_default = {
34845
34914
  name: "@hasna/todos",
34846
- version: "0.13.11",
34915
+ version: "0.14.0",
34847
34916
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
34848
34917
  type: "module",
34849
34918
  main: "dist/index.js",
@@ -39060,7 +39129,7 @@ ${lines.join(`
39060
39129
  a.name AS agent_name
39061
39130
  FROM task_files tf
39062
39131
  JOIN tasks t ON tf.task_id = t.id
39063
- LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
39132
+ LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name))))
39064
39133
  WHERE t.status = 'in_progress'
39065
39134
  AND tf.status != 'removed'
39066
39135
  AND t.project_id = ?
@@ -42822,9 +42891,16 @@ ${text2}` }] };
42822
42891
  return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
42823
42892
  }
42824
42893
  const oldName = agent.name;
42825
- const updated = updateAgent(agent.id, { name: new_name });
42826
42894
  const db = getDatabase();
42827
- const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
42895
+ let oldNameUniquelyIdentifiesAgent = false;
42896
+ try {
42897
+ oldNameUniquelyIdentifiesAgent = resolvePartialId(db, "agents", oldName) === agent.id;
42898
+ } catch (error) {
42899
+ if (!(error instanceof IdentityAliasAmbiguousError))
42900
+ throw error;
42901
+ }
42902
+ const updated = updateAgent(agent.id, { name: new_name });
42903
+ const tasksResult = db.run(oldNameUniquelyIdentifiesAgent ? "UPDATE tasks SET assigned_to = ? WHERE LOWER(assigned_to) = LOWER(?)" : "UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
42828
42904
  const taskNote = tasksResult.changes > 0 ? `
42829
42905
  Updated assigned_to on ${tasksResult.changes} task(s).` : "";
42830
42906
  return {
@@ -43008,6 +43084,7 @@ var init_agents2 = __esm(() => {
43008
43084
  init_agents();
43009
43085
  init_config2();
43010
43086
  init_database();
43087
+ init_types();
43011
43088
  init_cloud_router();
43012
43089
  });
43013
43090
 
@@ -46416,9 +46493,11 @@ async function updateTask2(id, input, store) {
46416
46493
  throw new Error(`Task ${id} version conflict: expected ${existing.version}, got ${input.version}`);
46417
46494
  }
46418
46495
  const reopened = existing.status === "completed" && input.status !== undefined && input.status !== "completed" && input.completed_at === undefined;
46496
+ const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
46419
46497
  const task2 = {
46420
46498
  ...existing,
46421
46499
  ...definedPatch(input),
46500
+ ...terminalNow ? { locked_by: null, locked_at: null } : {},
46422
46501
  version: existing.version + 1,
46423
46502
  updated_at: new Date().toISOString(),
46424
46503
  tags: input.tags ?? existing.tags,
@@ -47991,6 +48070,96 @@ var init_auth_posture = __esm(() => {
47991
48070
  LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "::1", "[::1]", "ip6-localhost"]);
47992
48071
  });
47993
48072
 
48073
+ // src/lib/enum-vocabulary.ts
48074
+ function editDistance(a, b) {
48075
+ if (a === b)
48076
+ return 0;
48077
+ if (!a.length)
48078
+ return b.length;
48079
+ if (!b.length)
48080
+ return a.length;
48081
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
48082
+ for (let i = 1;i <= a.length; i += 1) {
48083
+ const current = [i];
48084
+ for (let j = 1;j <= b.length; j += 1) {
48085
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
48086
+ }
48087
+ previous = current;
48088
+ }
48089
+ return previous[b.length];
48090
+ }
48091
+ function suggestVocabularyMatches(value, vocabulary, limit = 3) {
48092
+ const needle = value.trim().toLowerCase();
48093
+ if (!needle)
48094
+ return [];
48095
+ const scored = [];
48096
+ for (const member of vocabulary) {
48097
+ const candidate = member.toLowerCase();
48098
+ if (candidate.startsWith(needle) || needle.startsWith(candidate)) {
48099
+ scored.push({ member, score: 0 });
48100
+ continue;
48101
+ }
48102
+ if (candidate.includes(needle) || needle.includes(candidate)) {
48103
+ scored.push({ member, score: 1 });
48104
+ continue;
48105
+ }
48106
+ const distance = editDistance(needle, candidate);
48107
+ if (distance <= Math.max(1, Math.floor(candidate.length / 3))) {
48108
+ scored.push({ member, score: 1 + distance });
48109
+ }
48110
+ }
48111
+ return scored.sort((a, b) => a.score - b.score || a.member.localeCompare(b.member)).slice(0, limit).map((entry2) => entry2.member);
48112
+ }
48113
+ function resolveEnumVocabulary(raw, spec) {
48114
+ const allowList = spec.allowList !== false;
48115
+ const rawElements = (allowList ? raw.split(",") : [raw]).map((element) => element.trim()).filter((element) => element.length > 0);
48116
+ if (rawElements.length === 0) {
48117
+ return {
48118
+ ok: false,
48119
+ message: `${spec.name} requires a value. Allowed values: ${spec.vocabulary.join(", ")}.`,
48120
+ invalid: []
48121
+ };
48122
+ }
48123
+ const normalized = rawElements.map((element) => spec.normalize ? spec.normalize(element) : element);
48124
+ const allowed = new Set(spec.vocabulary);
48125
+ const invalid2 = [];
48126
+ for (let i = 0;i < normalized.length; i += 1) {
48127
+ if (!allowed.has(normalized[i]))
48128
+ invalid2.push(rawElements[i]);
48129
+ }
48130
+ if (invalid2.length === 0) {
48131
+ return { ok: true, values: [...new Set(normalized)] };
48132
+ }
48133
+ const label = invalid2.length === 1 ? "value" : "values";
48134
+ const parts = [
48135
+ `Invalid ${spec.name} ${label}: ${invalid2.join(", ")}.`,
48136
+ `Allowed values: ${spec.vocabulary.join(", ")}.`
48137
+ ];
48138
+ const hints = new Set;
48139
+ const suggestions = new Set;
48140
+ for (let i = 0;i < normalized.length; i += 1) {
48141
+ const canonical = normalized[i];
48142
+ if (allowed.has(canonical))
48143
+ continue;
48144
+ const hint = spec.hints?.[canonical.toLowerCase()];
48145
+ if (hint) {
48146
+ hints.add(hint);
48147
+ continue;
48148
+ }
48149
+ for (const match of suggestVocabularyMatches(canonical, spec.vocabulary)) {
48150
+ suggestions.add(match);
48151
+ }
48152
+ }
48153
+ if (suggestions.size > 0)
48154
+ parts.push(`Did you mean ${[...suggestions].join(", ")}?`);
48155
+ for (const hint of hints)
48156
+ parts.push(hint);
48157
+ return { ok: false, message: parts.join(" "), invalid: invalid2 };
48158
+ }
48159
+ function collapseEnumValues(values) {
48160
+ return values.length === 1 ? values[0] : values;
48161
+ }
48162
+
47994
48163
  // src/db/orgs.ts
47995
48164
  function rowToOrg(row) {
47996
48165
  return { ...row, metadata: JSON.parse(row.metadata || "{}") };
@@ -48291,8 +48460,19 @@ function handleStats(_ctx, json2) {
48291
48460
  recurring_tasks: countRecurringTasks()
48292
48461
  });
48293
48462
  }
48463
+ function taskStatusQueryParam(url) {
48464
+ const raw = url.searchParams.get("status");
48465
+ if (!raw)
48466
+ return { ok: true, value: undefined };
48467
+ const result = resolveEnumVocabulary(raw, { name: "status", vocabulary: TASK_STATUSES });
48468
+ if (!result.ok)
48469
+ return { ok: false, message: result.message };
48470
+ return { ok: true, value: collapseEnumValues(result.values) };
48471
+ }
48294
48472
  async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
48295
- const status = url.searchParams.get("status") || undefined;
48473
+ const statusParam = taskStatusQueryParam(url);
48474
+ if (!statusParam.ok)
48475
+ return json2({ error: statusParam.message }, 400);
48296
48476
  const projectId = url.searchParams.get("project_id") || undefined;
48297
48477
  const sessionId = url.searchParams.get("session_id") || undefined;
48298
48478
  const agentId = url.searchParams.get("agent_id") || undefined;
@@ -48300,7 +48480,7 @@ async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
48300
48480
  const offsetParam = url.searchParams.get("offset");
48301
48481
  const fields = parseFieldsParam(url);
48302
48482
  const tasks = listTasks({
48303
- status,
48483
+ status: statusParam.value,
48304
48484
  project_id: projectId,
48305
48485
  session_id: sessionId,
48306
48486
  agent_id: agentId,
@@ -48365,9 +48545,19 @@ async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
48365
48545
  }
48366
48546
  function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
48367
48547
  const format = url.searchParams.get("format") || "json";
48368
- const status = url.searchParams.get("status") || undefined;
48548
+ const statusParam = taskStatusQueryParam(url);
48549
+ if (!statusParam.ok) {
48550
+ return new Response(JSON.stringify({ error: statusParam.message }), {
48551
+ status: 400,
48552
+ headers: { "Content-Type": "application/json" }
48553
+ });
48554
+ }
48369
48555
  const projectId = url.searchParams.get("project_id") || undefined;
48370
- const tasks = listTasks({ status, project_id: projectId, limit: 1e4 });
48556
+ const tasks = listTasks({
48557
+ status: statusParam.value,
48558
+ project_id: projectId,
48559
+ limit: 1e4
48560
+ });
48371
48561
  const summaries = tasks.map((t) => taskToSummary2(t));
48372
48562
  if (format === "csv") {
48373
48563
  const headers = ["id", "short_id", "title", "status", "priority", "project_id", "assigned_to", "agent_id", "created_at", "updated_at", "completed_at", "due_at"];
@@ -48682,8 +48872,9 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
48682
48872
  }
48683
48873
  }
48684
48874
  function handleAgentQueue(agentId, _ctx, json2, taskToSummary2) {
48875
+ const aliasSet = assignedToAliasSet(getDatabase(), agentId);
48685
48876
  const pending = listTasks({ status: "pending" });
48686
- const queue = pending.filter((t) => t.assigned_to === agentId || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
48877
+ const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
48687
48878
  const order = { critical: 0, high: 1, medium: 2, low: 3 };
48688
48879
  queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
48689
48880
  return json2(queue.map((t) => taskToSummary2(t)));
@@ -48948,6 +49139,7 @@ var init_routes = __esm(() => {
48948
49139
  init_tasks();
48949
49140
  init_database();
48950
49141
  init_types();
49142
+ init_types();
48951
49143
  init_projects();
48952
49144
  init_agents();
48953
49145
  init_plans();
@@ -49901,8 +50093,38 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
49901
50093
  operationId: "listTasks",
49902
50094
  summary: "List tasks",
49903
50095
  parameters: [
49904
- { name: "status", in: "query", schema: { type: "string" } },
49905
- { name: "priority", in: "query", schema: { type: "string" } },
50096
+ {
50097
+ name: "status",
50098
+ in: "query",
50099
+ description: `Task status, or a comma-separated list of statuses. Allowed values: ${TASK_STATUSES.join(", ")}.`,
50100
+ style: "form",
50101
+ explode: false,
50102
+ schema: {
50103
+ oneOf: [
50104
+ { type: "string", enum: [...TASK_STATUSES] },
50105
+ {
50106
+ type: "array",
50107
+ items: { type: "string", enum: [...TASK_STATUSES] }
50108
+ }
50109
+ ]
50110
+ }
50111
+ },
50112
+ {
50113
+ name: "priority",
50114
+ in: "query",
50115
+ description: `Task priority, or a comma-separated list of priorities. Allowed values: ${TASK_PRIORITIES.join(", ")}.`,
50116
+ style: "form",
50117
+ explode: false,
50118
+ schema: {
50119
+ oneOf: [
50120
+ { type: "string", enum: [...TASK_PRIORITIES] },
50121
+ {
50122
+ type: "array",
50123
+ items: { type: "string", enum: [...TASK_PRIORITIES] }
50124
+ }
50125
+ ]
50126
+ }
50127
+ },
49906
50128
  { name: "project_id", in: "query", schema: { type: "string" } },
49907
50129
  { name: "parent_id", in: "query", schema: { type: "string", nullable: true } },
49908
50130
  { name: "include_subtasks", in: "query", schema: { type: "boolean" } },
@@ -50414,6 +50636,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
50414
50636
  var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
50415
50637
  var init_openapi = __esm(() => {
50416
50638
  init_package_version();
50639
+ init_types();
50417
50640
  taskSchema = {
50418
50641
  type: "object",
50419
50642
  properties: {
@@ -50681,6 +50904,15 @@ function json3(body, status = 200) {
50681
50904
  function error(status, message, extra) {
50682
50905
  return json3({ error: message, ...extra ?? {} }, status);
50683
50906
  }
50907
+ function enumQueryParam(url, name, vocabulary) {
50908
+ const raw = url.searchParams.get(name);
50909
+ if (raw === null || raw === "")
50910
+ return { ok: true, value: undefined };
50911
+ const result = resolveEnumVocabulary(raw, { name, vocabulary });
50912
+ if (!result.ok)
50913
+ return { ok: false, response: error(400, result.message) };
50914
+ return { ok: true, value: collapseEnumValues(result.values) };
50915
+ }
50684
50916
  function validateTaskCompletion(value) {
50685
50917
  if (!value || typeof value !== "object" || Array.isArray(value))
50686
50918
  return { ok: false, message: "completion body must be an object" };
@@ -51101,14 +51333,16 @@ async function handleV1Request(req, url, dependencies = {}) {
51101
51333
  return error(400, "include_subtasks must be true or false");
51102
51334
  }
51103
51335
  const hasParentFilter = url.searchParams.has("parent_id");
51336
+ const statusParam = enumQueryParam(url, "status", TASK_STATUSES);
51337
+ if (!statusParam.ok)
51338
+ return statusParam.response;
51339
+ const priorityParam = enumQueryParam(url, "priority", TASK_PRIORITIES);
51340
+ if (!priorityParam.ok)
51341
+ return priorityParam.response;
51104
51342
  const filter = {
51105
51343
  ...url.searchParams.get("q") ? { query: url.searchParams.get("q") } : {},
51106
- ...url.searchParams.get("status") ? {
51107
- status: url.searchParams.get("status").includes(",") ? url.searchParams.get("status").split(",") : url.searchParams.get("status")
51108
- } : {},
51109
- ...url.searchParams.get("priority") ? {
51110
- priority: url.searchParams.get("priority").includes(",") ? url.searchParams.get("priority").split(",") : url.searchParams.get("priority")
51111
- } : {},
51344
+ ...statusParam.value !== undefined ? { status: statusParam.value } : {},
51345
+ ...priorityParam.value !== undefined ? { priority: priorityParam.value } : {},
51112
51346
  ...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
51113
51347
  ...hasParentFilter ? { parent_id: url.searchParams.get("parent_id") || null, include_subtasks: true } : includeSubtasks !== null ? { include_subtasks: includeSubtasks === "true" } : {},
51114
51348
  ...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {},