@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
@@ -70,7 +70,7 @@ var package_default;
70
70
  var init_package = __esm(() => {
71
71
  package_default = {
72
72
  name: "@hasna/todos",
73
- version: "0.13.11",
73
+ version: "0.14.0",
74
74
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
75
75
  type: "module",
76
76
  main: "dist/index.js",
@@ -850,7 +850,10 @@ var init_cloud_client = __esm(() => {
850
850
  function isBlockingDependencyStatus(status) {
851
851
  return status !== "completed" && status !== "cancelled";
852
852
  }
853
- var TASK_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
853
+ function isTerminalStatus(status) {
854
+ return status === "completed" || status === "failed" || status === "cancelled";
855
+ }
856
+ var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
854
857
  var init_types = __esm(() => {
855
858
  TASK_STATUSES = [
856
859
  "pending",
@@ -859,6 +862,12 @@ var init_types = __esm(() => {
859
862
  "failed",
860
863
  "cancelled"
861
864
  ];
865
+ TASK_PRIORITIES = [
866
+ "low",
867
+ "medium",
868
+ "high",
869
+ "critical"
870
+ ];
862
871
  VersionConflictError = class VersionConflictError extends Error {
863
872
  taskId;
864
873
  expectedVersion;
@@ -2770,9 +2779,11 @@ async function updateTask(id, input, store) {
2770
2779
  throw new Error(`Task ${id} version conflict: expected ${existing.version}, got ${input.version}`);
2771
2780
  }
2772
2781
  const reopened = existing.status === "completed" && input.status !== undefined && input.status !== "completed" && input.completed_at === undefined;
2782
+ const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
2773
2783
  const task = {
2774
2784
  ...existing,
2775
2785
  ...definedPatch(input),
2786
+ ...terminalNow ? { locked_by: null, locked_at: null } : {},
2776
2787
  version: existing.version + 1,
2777
2788
  updated_at: new Date().toISOString(),
2778
2789
  tags: input.tags ?? existing.tags,
@@ -9312,14 +9323,17 @@ var exports_database = {};
9312
9323
  __export(exports_database, {
9313
9324
  uuid: () => uuid,
9314
9325
  resolvePartialId: () => resolvePartialId,
9326
+ resolveAssignedToAliases: () => resolveAssignedToAliases,
9315
9327
  resetDatabase: () => resetDatabase,
9316
9328
  now: () => now,
9329
+ lowerInClause: () => lowerInClause,
9317
9330
  lockExpiryCutoff: () => lockExpiryCutoff,
9318
9331
  isLockExpired: () => isLockExpired,
9319
9332
  getDatabasePath: () => getDatabasePath,
9320
9333
  getDatabase: () => getDatabase,
9321
9334
  closeDatabase: () => closeDatabase,
9322
9335
  clearExpiredLocks: () => clearExpiredLocks,
9336
+ assignedToAliasSet: () => assignedToAliasSet,
9323
9337
  LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
9324
9338
  });
9325
9339
  import { Database } from "bun:sqlite";
@@ -9548,6 +9562,33 @@ function resolvePartialId(db, table, partialId) {
9548
9562
  }
9549
9563
  return null;
9550
9564
  }
9565
+ function resolveAssignedToAliases(db, ref) {
9566
+ const aliases = new Set([ref]);
9567
+ let agentId;
9568
+ try {
9569
+ agentId = resolvePartialId(db, "agents", ref);
9570
+ } catch (err) {
9571
+ if (!(err instanceof IdentityAliasAmbiguousError))
9572
+ throw err;
9573
+ agentId = null;
9574
+ }
9575
+ if (agentId) {
9576
+ aliases.add(agentId);
9577
+ const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
9578
+ if (row?.name)
9579
+ aliases.add(row.name);
9580
+ }
9581
+ return [...aliases];
9582
+ }
9583
+ function assignedToAliasSet(db, ref) {
9584
+ return new Set(resolveAssignedToAliases(db, ref).map((a) => a.toLowerCase()));
9585
+ }
9586
+ function lowerInClause(column, values, params) {
9587
+ if (values.length === 0)
9588
+ return "1=0";
9589
+ params.push(...values.map((v) => v.toLowerCase()));
9590
+ return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
9591
+ }
9551
9592
  var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
9552
9593
  var init_database = __esm(() => {
9553
9594
  init_schema();
@@ -10117,17 +10158,19 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
10117
10158
  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);
10118
10159
  }
10119
10160
  }
10161
+ const assignedAliasParams = [];
10162
+ const assignedInClause = agent ? lowerInClause("assigned_to", resolveAssignedToAliases(db, agent), assignedAliasParams) : "";
10120
10163
  if (agent && config.max_completions_per_window && config.window_minutes) {
10121
10164
  const windowStart = new Date(Date.now() - config.window_minutes * 60 * 1000).toISOString();
10122
10165
  const result = db.query(`SELECT COUNT(*) as count FROM tasks
10123
- WHERE completed_at > ? AND (assigned_to = ? OR agent_id = ?)`).get(windowStart, agent, agent);
10166
+ WHERE completed_at > ? AND (${assignedInClause} OR agent_id = ?)`).get(windowStart, ...assignedAliasParams, agent);
10124
10167
  if (result.count >= config.max_completions_per_window) {
10125
10168
  throw new CompletionGuardError(`Rate limit: ${result.count} tasks completed in the last ${config.window_minutes} minutes (max ${config.max_completions_per_window}). Slow down.`);
10126
10169
  }
10127
10170
  }
10128
10171
  if (agent && config.cooldown_seconds) {
10129
10172
  const result = db.query(`SELECT MAX(completed_at) as last_completed FROM tasks
10130
- WHERE completed_at IS NOT NULL AND (assigned_to = ? OR agent_id = ?) AND id != ?`).get(agent, agent, task.id);
10173
+ WHERE completed_at IS NOT NULL AND (${assignedInClause} OR agent_id = ?) AND id != ?`).get(...assignedAliasParams, agent, task.id);
10131
10174
  if (result.last_completed) {
10132
10175
  const elapsedSeconds = (Date.now() - new Date(result.last_completed).getTime()) / 1000;
10133
10176
  if (elapsedSeconds < config.cooldown_seconds) {
@@ -10141,6 +10184,7 @@ var init_completion_guard = __esm(() => {
10141
10184
  init_types();
10142
10185
  init_config2();
10143
10186
  init_projects();
10187
+ init_database();
10144
10188
  });
10145
10189
 
10146
10190
  // src/lib/event-emission-safety.ts
@@ -12318,13 +12362,17 @@ function logDelivery(d, webhookId, event, payload, statusCode, response, attempt
12318
12362
  const id = uuid();
12319
12363
  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()]);
12320
12364
  }
12321
- function matchesScope(wh, payload) {
12365
+ function matchesScope(wh, payload, db) {
12322
12366
  if (wh.project_id && payload.project_id !== wh.project_id)
12323
12367
  return false;
12324
12368
  if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
12325
12369
  return false;
12326
- if (wh.agent_id && payload.agent_id !== wh.agent_id && payload.assigned_to !== wh.agent_id)
12327
- return false;
12370
+ if (wh.agent_id && payload.agent_id !== wh.agent_id) {
12371
+ const aliasSet = assignedToAliasSet(db, wh.agent_id);
12372
+ const assignedTo = typeof payload.assigned_to === "string" ? payload.assigned_to.toLowerCase() : "";
12373
+ if (!aliasSet.has(assignedTo))
12374
+ return false;
12375
+ }
12328
12376
  if (wh.task_id && payload.id !== wh.task_id)
12329
12377
  return false;
12330
12378
  return true;
@@ -12391,7 +12439,7 @@ async function dispatchWebhook2(event, payload, db) {
12391
12439
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
12392
12440
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
12393
12441
  for (const wh of webhooks) {
12394
- if (!matchesScope(wh, payloadObj))
12442
+ if (!matchesScope(wh, payloadObj, d))
12395
12443
  continue;
12396
12444
  const body = JSON.stringify({ event, payload, timestamp: now() });
12397
12445
  deliverWebhook(wh, event, body, 1, d).catch((err) => {
@@ -13463,14 +13511,16 @@ function getNextTask2(agentId, filters, db) {
13463
13511
  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')");
13464
13512
  const where = conditions.join(" AND ");
13465
13513
  let recentProjectIds = [];
13514
+ const assignedAliasParams = [];
13515
+ const assignedInClause = agentId ? lowerInClause("assigned_to", resolveAssignedToAliases(d, agentId), assignedAliasParams) : "";
13466
13516
  if (agentId) {
13467
- 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);
13517
+ 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);
13468
13518
  recentProjectIds = recentRows.map((r) => r.project_id);
13469
13519
  }
13470
13520
  let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
13471
13521
  if (agentId) {
13472
- sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
13473
- params.push(agentId);
13522
+ sql += `CASE WHEN ${assignedInClause} THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
13523
+ params.push(...assignedAliasParams);
13474
13524
  }
13475
13525
  if (recentProjectIds.length > 0) {
13476
13526
  const placeholders = recentProjectIds.map(() => "?").join(",");
@@ -13864,30 +13914,6 @@ function getTaskWithRelations(id, db) {
13864
13914
  checklist
13865
13915
  };
13866
13916
  }
13867
- function resolveAssignedToAliases(db, ref) {
13868
- const aliases = new Set([ref]);
13869
- let agentId;
13870
- try {
13871
- agentId = resolvePartialId(db, "agents", ref);
13872
- } catch (err) {
13873
- if (!(err instanceof IdentityAliasAmbiguousError))
13874
- throw err;
13875
- agentId = null;
13876
- }
13877
- if (agentId) {
13878
- aliases.add(agentId);
13879
- const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
13880
- if (row?.name)
13881
- aliases.add(row.name);
13882
- }
13883
- return [...aliases];
13884
- }
13885
- function lowerInClause(column, values, params) {
13886
- if (values.length === 0)
13887
- return "1=0";
13888
- params.push(...values.map((v) => v.toLowerCase()));
13889
- return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
13890
- }
13891
13917
  function listTasks2(filter = {}, db) {
13892
13918
  const d = db || getDatabase();
13893
13919
  const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
@@ -14173,11 +14199,13 @@ function updateTask2(id, input, db) {
14173
14199
  }
14174
14200
  sets.push("status = ?");
14175
14201
  params.push(input.status);
14202
+ if (isTerminalStatus(input.status)) {
14203
+ sets.push("locked_by = NULL");
14204
+ sets.push("locked_at = NULL");
14205
+ }
14176
14206
  if (input.status === "completed") {
14177
14207
  sets.push("completed_at = ?");
14178
14208
  params.push(completionTimestamp);
14179
- sets.push("locked_by = NULL");
14180
- sets.push("locked_at = NULL");
14181
14209
  } else if (task.status === "completed" && input.completed_at === undefined) {
14182
14210
  sets.push("completed_at = NULL");
14183
14211
  }
@@ -14301,6 +14329,7 @@ function updateTask2(id, input, db) {
14301
14329
  logTaskChange2(id, "approve", "approved_by", null, input.approved_by, agentId, d);
14302
14330
  const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
14303
14331
  const completedNow = input.status === "completed";
14332
+ const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
14304
14333
  const updatedTask = {
14305
14334
  ...task,
14306
14335
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
@@ -14308,8 +14337,8 @@ function updateTask2(id, input, db) {
14308
14337
  metadata: input.metadata ?? task.metadata,
14309
14338
  version: task.version + 1,
14310
14339
  updated_at: timestamp2,
14311
- locked_by: completedNow ? null : task.locked_by,
14312
- locked_at: completedNow ? null : task.locked_at,
14340
+ locked_by: terminalNow ? null : task.locked_by,
14341
+ locked_at: terminalNow ? null : task.locked_at,
14313
14342
  completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
14314
14343
  sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
14315
14344
  actual_minutes: input.actual_minutes ?? task.actual_minutes,
@@ -14360,6 +14389,7 @@ function deleteTask(id, db) {
14360
14389
  var init_task_crud = __esm(() => {
14361
14390
  init_types();
14362
14391
  init_database();
14392
+ init_database();
14363
14393
  init_completion_guard();
14364
14394
  init_event_emission_safety();
14365
14395
  init_event_hooks();
@@ -14522,8 +14552,8 @@ function getTaskStats(filters, db) {
14522
14552
  params.push(filters.task_list_id);
14523
14553
  }
14524
14554
  if (filters?.agent_id) {
14525
- conditions.push("(agent_id = ? OR assigned_to = ?)");
14526
- params.push(filters.agent_id, filters.agent_id);
14555
+ params.push(filters.agent_id);
14556
+ conditions.push(`(agent_id = ? OR ${lowerInClause("assigned_to", resolveAssignedToAliases(d, filters.agent_id), params)})`);
14527
14557
  }
14528
14558
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
14529
14559
  const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
@@ -14706,8 +14736,7 @@ function getEscalatedTasks(opts = {}, db, at = new Date) {
14706
14736
  params.push(opts.project_id);
14707
14737
  }
14708
14738
  if (opts.agent_id) {
14709
- conditions.push("assigned_to = ?");
14710
- params.push(opts.agent_id);
14739
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
14711
14740
  }
14712
14741
  const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC, created_at ASC`).all(...params);
14713
14742
  return rows.map(rowToTask).map((task) => {
@@ -14755,8 +14784,7 @@ function notifyUpcomingDeadlines(opts = {}, db) {
14755
14784
  params.push(opts.project_id);
14756
14785
  }
14757
14786
  if (opts.agent_id) {
14758
- conditions.push("assigned_to = ?");
14759
- params.push(opts.agent_id);
14787
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
14760
14788
  }
14761
14789
  const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC`).all(...params);
14762
14790
  return rows.map(rowToTask);
@@ -15014,13 +15042,14 @@ function getTimeReport(opts, db) {
15014
15042
  params.push(opts.plan_id);
15015
15043
  }
15016
15044
  if (opts?.agent_id) {
15045
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.agent_id), params);
15017
15046
  conditions.push(`(
15018
- t.assigned_to = ?
15047
+ ${assignedInClause}
15019
15048
  OR t.agent_id = ?
15020
15049
  OR EXISTS (SELECT 1 FROM task_time_logs ttl WHERE ttl.task_id = t.id AND ttl.agent_id = ?)
15021
15050
  OR EXISTS (SELECT 1 FROM focus_sessions fs WHERE fs.task_id = t.id AND fs.agent_id = ?)
15022
15051
  )`);
15023
- params.push(opts.agent_id, opts.agent_id, opts.agent_id, opts.agent_id);
15052
+ params.push(opts.agent_id, opts.agent_id, opts.agent_id);
15024
15053
  }
15025
15054
  if (opts?.since) {
15026
15055
  conditions.push("(t.completed_at >= ? OR t.updated_at >= ?)");
@@ -16016,7 +16045,7 @@ function listActiveFiles(db) {
16016
16045
  a.name AS agent_name
16017
16046
  FROM task_files tf
16018
16047
  JOIN tasks t ON tf.task_id = t.id
16019
- LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
16048
+ 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))))
16020
16049
  WHERE t.status = 'in_progress'
16021
16050
  AND tf.status != 'removed'
16022
16051
  ORDER BY tf.updated_at DESC
@@ -17230,6 +17259,96 @@ var init_tasks = __esm(() => {
17230
17259
  init_calendar();
17231
17260
  });
17232
17261
 
17262
+ // src/lib/enum-vocabulary.ts
17263
+ function editDistance(a, b) {
17264
+ if (a === b)
17265
+ return 0;
17266
+ if (!a.length)
17267
+ return b.length;
17268
+ if (!b.length)
17269
+ return a.length;
17270
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
17271
+ for (let i = 1;i <= a.length; i += 1) {
17272
+ const current = [i];
17273
+ for (let j = 1;j <= b.length; j += 1) {
17274
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
17275
+ }
17276
+ previous = current;
17277
+ }
17278
+ return previous[b.length];
17279
+ }
17280
+ function suggestVocabularyMatches(value, vocabulary, limit = 3) {
17281
+ const needle = value.trim().toLowerCase();
17282
+ if (!needle)
17283
+ return [];
17284
+ const scored = [];
17285
+ for (const member of vocabulary) {
17286
+ const candidate = member.toLowerCase();
17287
+ if (candidate.startsWith(needle) || needle.startsWith(candidate)) {
17288
+ scored.push({ member, score: 0 });
17289
+ continue;
17290
+ }
17291
+ if (candidate.includes(needle) || needle.includes(candidate)) {
17292
+ scored.push({ member, score: 1 });
17293
+ continue;
17294
+ }
17295
+ const distance = editDistance(needle, candidate);
17296
+ if (distance <= Math.max(1, Math.floor(candidate.length / 3))) {
17297
+ scored.push({ member, score: 1 + distance });
17298
+ }
17299
+ }
17300
+ return scored.sort((a, b) => a.score - b.score || a.member.localeCompare(b.member)).slice(0, limit).map((entry2) => entry2.member);
17301
+ }
17302
+ function resolveEnumVocabulary(raw, spec) {
17303
+ const allowList = spec.allowList !== false;
17304
+ const rawElements = (allowList ? raw.split(",") : [raw]).map((element) => element.trim()).filter((element) => element.length > 0);
17305
+ if (rawElements.length === 0) {
17306
+ return {
17307
+ ok: false,
17308
+ message: `${spec.name} requires a value. Allowed values: ${spec.vocabulary.join(", ")}.`,
17309
+ invalid: []
17310
+ };
17311
+ }
17312
+ const normalized = rawElements.map((element) => spec.normalize ? spec.normalize(element) : element);
17313
+ const allowed = new Set(spec.vocabulary);
17314
+ const invalid = [];
17315
+ for (let i = 0;i < normalized.length; i += 1) {
17316
+ if (!allowed.has(normalized[i]))
17317
+ invalid.push(rawElements[i]);
17318
+ }
17319
+ if (invalid.length === 0) {
17320
+ return { ok: true, values: [...new Set(normalized)] };
17321
+ }
17322
+ const label = invalid.length === 1 ? "value" : "values";
17323
+ const parts = [
17324
+ `Invalid ${spec.name} ${label}: ${invalid.join(", ")}.`,
17325
+ `Allowed values: ${spec.vocabulary.join(", ")}.`
17326
+ ];
17327
+ const hints = new Set;
17328
+ const suggestions = new Set;
17329
+ for (let i = 0;i < normalized.length; i += 1) {
17330
+ const canonical = normalized[i];
17331
+ if (allowed.has(canonical))
17332
+ continue;
17333
+ const hint = spec.hints?.[canonical.toLowerCase()];
17334
+ if (hint) {
17335
+ hints.add(hint);
17336
+ continue;
17337
+ }
17338
+ for (const match of suggestVocabularyMatches(canonical, spec.vocabulary)) {
17339
+ suggestions.add(match);
17340
+ }
17341
+ }
17342
+ if (suggestions.size > 0)
17343
+ parts.push(`Did you mean ${[...suggestions].join(", ")}?`);
17344
+ for (const hint of hints)
17345
+ parts.push(hint);
17346
+ return { ok: false, message: parts.join(" "), invalid };
17347
+ }
17348
+ function collapseEnumValues(values) {
17349
+ return values.length === 1 ? values[0] : values;
17350
+ }
17351
+
17233
17352
  // src/db/agent-names.ts
17234
17353
  function hasGeneratedNumericSuffix(name) {
17235
17354
  return NUMERIC_SUFFIX_RE.test(normalizeAgentNameInput(name));
@@ -18657,8 +18776,19 @@ function handleStats(_ctx, json2) {
18657
18776
  recurring_tasks: countRecurringTasks()
18658
18777
  });
18659
18778
  }
18779
+ function taskStatusQueryParam(url) {
18780
+ const raw = url.searchParams.get("status");
18781
+ if (!raw)
18782
+ return { ok: true, value: undefined };
18783
+ const result = resolveEnumVocabulary(raw, { name: "status", vocabulary: TASK_STATUSES });
18784
+ if (!result.ok)
18785
+ return { ok: false, message: result.message };
18786
+ return { ok: true, value: collapseEnumValues(result.values) };
18787
+ }
18660
18788
  async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
18661
- const status = url.searchParams.get("status") || undefined;
18789
+ const statusParam = taskStatusQueryParam(url);
18790
+ if (!statusParam.ok)
18791
+ return json2({ error: statusParam.message }, 400);
18662
18792
  const projectId = url.searchParams.get("project_id") || undefined;
18663
18793
  const sessionId = url.searchParams.get("session_id") || undefined;
18664
18794
  const agentId = url.searchParams.get("agent_id") || undefined;
@@ -18666,7 +18796,7 @@ async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
18666
18796
  const offsetParam = url.searchParams.get("offset");
18667
18797
  const fields = parseFieldsParam(url);
18668
18798
  const tasks = listTasks2({
18669
- status,
18799
+ status: statusParam.value,
18670
18800
  project_id: projectId,
18671
18801
  session_id: sessionId,
18672
18802
  agent_id: agentId,
@@ -18731,9 +18861,19 @@ async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
18731
18861
  }
18732
18862
  function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
18733
18863
  const format = url.searchParams.get("format") || "json";
18734
- const status = url.searchParams.get("status") || undefined;
18864
+ const statusParam = taskStatusQueryParam(url);
18865
+ if (!statusParam.ok) {
18866
+ return new Response(JSON.stringify({ error: statusParam.message }), {
18867
+ status: 400,
18868
+ headers: { "Content-Type": "application/json" }
18869
+ });
18870
+ }
18735
18871
  const projectId = url.searchParams.get("project_id") || undefined;
18736
- const tasks = listTasks2({ status, project_id: projectId, limit: 1e4 });
18872
+ const tasks = listTasks2({
18873
+ status: statusParam.value,
18874
+ project_id: projectId,
18875
+ limit: 1e4
18876
+ });
18737
18877
  const summaries = tasks.map((t) => taskToSummary2(t));
18738
18878
  if (format === "csv") {
18739
18879
  const headers = ["id", "short_id", "title", "status", "priority", "project_id", "assigned_to", "agent_id", "created_at", "updated_at", "completed_at", "due_at"];
@@ -19048,8 +19188,9 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
19048
19188
  }
19049
19189
  }
19050
19190
  function handleAgentQueue(agentId, _ctx, json2, taskToSummary2) {
19191
+ const aliasSet = assignedToAliasSet(getDatabase(), agentId);
19051
19192
  const pending = listTasks2({ status: "pending" });
19052
- const queue = pending.filter((t) => t.assigned_to === agentId || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
19193
+ const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
19053
19194
  const order = { critical: 0, high: 1, medium: 2, low: 3 };
19054
19195
  queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
19055
19196
  return json2(queue.map((t) => taskToSummary2(t)));
@@ -19314,6 +19455,7 @@ var init_routes = __esm(() => {
19314
19455
  init_tasks();
19315
19456
  init_database();
19316
19457
  init_types();
19458
+ init_types();
19317
19459
  init_projects();
19318
19460
  init_agents();
19319
19461
  init_plans();
@@ -19441,8 +19583,7 @@ function searchTasks(options, projectId, taskListId, db) {
19441
19583
  }
19442
19584
  }
19443
19585
  if (opts.assigned_to) {
19444
- sql += " AND t.assigned_to = ?";
19445
- params.push(opts.assigned_to);
19586
+ sql += ` AND ${lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.assigned_to), params)}`;
19446
19587
  }
19447
19588
  if (opts.agent_id) {
19448
19589
  sql += " AND t.agent_id = ?";
@@ -21353,8 +21494,38 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
21353
21494
  operationId: "listTasks",
21354
21495
  summary: "List tasks",
21355
21496
  parameters: [
21356
- { name: "status", in: "query", schema: { type: "string" } },
21357
- { name: "priority", in: "query", schema: { type: "string" } },
21497
+ {
21498
+ name: "status",
21499
+ in: "query",
21500
+ description: `Task status, or a comma-separated list of statuses. Allowed values: ${TASK_STATUSES.join(", ")}.`,
21501
+ style: "form",
21502
+ explode: false,
21503
+ schema: {
21504
+ oneOf: [
21505
+ { type: "string", enum: [...TASK_STATUSES] },
21506
+ {
21507
+ type: "array",
21508
+ items: { type: "string", enum: [...TASK_STATUSES] }
21509
+ }
21510
+ ]
21511
+ }
21512
+ },
21513
+ {
21514
+ name: "priority",
21515
+ in: "query",
21516
+ description: `Task priority, or a comma-separated list of priorities. Allowed values: ${TASK_PRIORITIES.join(", ")}.`,
21517
+ style: "form",
21518
+ explode: false,
21519
+ schema: {
21520
+ oneOf: [
21521
+ { type: "string", enum: [...TASK_PRIORITIES] },
21522
+ {
21523
+ type: "array",
21524
+ items: { type: "string", enum: [...TASK_PRIORITIES] }
21525
+ }
21526
+ ]
21527
+ }
21528
+ },
21358
21529
  { name: "project_id", in: "query", schema: { type: "string" } },
21359
21530
  { name: "parent_id", in: "query", schema: { type: "string", nullable: true } },
21360
21531
  { name: "include_subtasks", in: "query", schema: { type: "boolean" } },
@@ -21866,6 +22037,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
21866
22037
  var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
21867
22038
  var init_openapi = __esm(() => {
21868
22039
  init_package_version();
22040
+ init_types();
21869
22041
  taskSchema = {
21870
22042
  type: "object",
21871
22043
  properties: {
@@ -22133,6 +22305,15 @@ function json3(body, status = 200) {
22133
22305
  function error(status, message, extra) {
22134
22306
  return json3({ error: message, ...extra ?? {} }, status);
22135
22307
  }
22308
+ function enumQueryParam(url, name, vocabulary) {
22309
+ const raw = url.searchParams.get(name);
22310
+ if (raw === null || raw === "")
22311
+ return { ok: true, value: undefined };
22312
+ const result = resolveEnumVocabulary(raw, { name, vocabulary });
22313
+ if (!result.ok)
22314
+ return { ok: false, response: error(400, result.message) };
22315
+ return { ok: true, value: collapseEnumValues(result.values) };
22316
+ }
22136
22317
  function validateTaskCompletion(value) {
22137
22318
  if (!value || typeof value !== "object" || Array.isArray(value))
22138
22319
  return { ok: false, message: "completion body must be an object" };
@@ -22553,14 +22734,16 @@ async function handleV1Request(req, url, dependencies = {}) {
22553
22734
  return error(400, "include_subtasks must be true or false");
22554
22735
  }
22555
22736
  const hasParentFilter = url.searchParams.has("parent_id");
22737
+ const statusParam = enumQueryParam(url, "status", TASK_STATUSES);
22738
+ if (!statusParam.ok)
22739
+ return statusParam.response;
22740
+ const priorityParam = enumQueryParam(url, "priority", TASK_PRIORITIES);
22741
+ if (!priorityParam.ok)
22742
+ return priorityParam.response;
22556
22743
  const filter = {
22557
22744
  ...url.searchParams.get("q") ? { query: url.searchParams.get("q") } : {},
22558
- ...url.searchParams.get("status") ? {
22559
- status: url.searchParams.get("status").includes(",") ? url.searchParams.get("status").split(",") : url.searchParams.get("status")
22560
- } : {},
22561
- ...url.searchParams.get("priority") ? {
22562
- priority: url.searchParams.get("priority").includes(",") ? url.searchParams.get("priority").split(",") : url.searchParams.get("priority")
22563
- } : {},
22745
+ ...statusParam.value !== undefined ? { status: statusParam.value } : {},
22746
+ ...priorityParam.value !== undefined ? { priority: priorityParam.value } : {},
22564
22747
  ...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
22565
22748
  ...hasParentFilter ? { parent_id: url.searchParams.get("parent_id") || null, include_subtasks: true } : includeSubtasks !== null ? { include_subtasks: includeSubtasks === "true" } : {},
22566
22749
  ...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {},
@@ -60747,11 +60930,12 @@ function runAlerts(input, db, checkedAt, quieted) {
60747
60930
  if (input.include_runs === false)
60748
60931
  return [];
60749
60932
  const since = Date.parse(input.run_since || new Date(Date.parse(checkedAt) - 24 * 60 * 60000).toISOString());
60933
+ const runAlertAliases = input.agent_id ? assignedToAliasSet(db, input.agent_id) : null;
60750
60934
  return listTaskRuns(undefined, db).filter((run) => run.completed_at && Date.parse(run.completed_at) >= since).map((run) => {
60751
60935
  const task = getTask(run.task_id, db);
60752
60936
  if (input.project_id && task?.project_id !== input.project_id)
60753
60937
  return null;
60754
- if (input.agent_id && run.agent_id !== input.agent_id && task?.assigned_to !== input.agent_id)
60938
+ if (input.agent_id && run.agent_id !== input.agent_id && !(runAlertAliases && runAlertAliases.has((task?.assigned_to ?? "").toLowerCase())))
60755
60939
  return null;
60756
60940
  const failed = ["failed", "cancelled"].includes(run.status);
60757
60941
  return {
@@ -60796,6 +60980,7 @@ async function checkLocalNotifications(input = {}, db) {
60796
60980
  const warnings = [];
60797
60981
  if (Number.isNaN(checkedMs))
60798
60982
  warnings.push("Invalid check timestamp; due-soon window may be incomplete.");
60983
+ const notifyAgentAliases = input.agent_id ? assignedToAliasSet(d, input.agent_id) : null;
60799
60984
  const alerts = [];
60800
60985
  for (const escalation of getEscalatedTasks({ project_id: input.project_id, agent_id: input.agent_id }, d, new Date(checkedAt))) {
60801
60986
  for (const reason of escalation.reasons) {
@@ -60807,7 +60992,7 @@ async function checkLocalNotifications(input = {}, db) {
60807
60992
  for (const task of listTasks2({ project_id: input.project_id, include_archived: false, limit: 5000 }, d)) {
60808
60993
  if (!unfinished(task) || !task.due_at)
60809
60994
  continue;
60810
- if (input.agent_id && task.assigned_to !== input.agent_id && task.agent_id !== input.agent_id)
60995
+ if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
60811
60996
  continue;
60812
60997
  if (task.due_at > checkedAt && task.due_at <= dueSoonUntil) {
60813
60998
  alerts.push(taskAlert(task, "task_due_soon", "task.due_soon", "warning", `Due soon: ${task.title}`, task.due_at, quieted));
@@ -60815,7 +61000,7 @@ async function checkLocalNotifications(input = {}, db) {
60815
61000
  }
60816
61001
  }
60817
61002
  for (const task of getStaleTasks(staleMinutes, { project_id: input.project_id }, d)) {
60818
- if (input.agent_id && task.assigned_to !== input.agent_id && task.agent_id !== input.agent_id)
61003
+ if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
60819
61004
  continue;
60820
61005
  alerts.push(taskAlert(task, "task_stale", "task.stale", "warning", `Stale task: ${task.title}`, checkedAt, quieted, { stale_minutes: staleMinutes }));
60821
61006
  }
@@ -62835,10 +63020,12 @@ function matchesProfile(profile, query) {
62835
63020
  return false;
62836
63021
  return true;
62837
63022
  }
62838
- function taskMatchesAgent(task, agentId) {
63023
+ function taskMatchesAgent(task, agentId, aliasSet) {
62839
63024
  if (!agentId)
62840
63025
  return true;
62841
- return task.assigned_to === agentId || task.agent_id === agentId;
63026
+ if (task.agent_id === agentId)
63027
+ return true;
63028
+ return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
62842
63029
  }
62843
63030
  function estimateRemaining(task) {
62844
63031
  if (task.status === "completed" || task.status === "cancelled")
@@ -62908,7 +63095,8 @@ function removeCapacityProfile(idOrAgent, projectId) {
62908
63095
  }
62909
63096
  function getPlanningForecast(input = {}, db) {
62910
63097
  const startDate = input.start_date ? dateOnly(input.start_date) : todayIso();
62911
- const tasks = listTasks2({ project_id: input.project_id, plan_id: input.plan_id, limit: 1e4 }, db).filter((task) => taskMatchesAgent(task, input.agent_id));
63098
+ const aliasSet = input.agent_id ? assignedToAliasSet(db || getDatabase(), input.agent_id) : undefined;
63099
+ const tasks = listTasks2({ project_id: input.project_id, plan_id: input.plan_id, limit: 1e4 }, db).filter((task) => taskMatchesAgent(task, input.agent_id, aliasSet));
62912
63100
  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((entry2) => [entry2.task_id, entry2]));
62913
63101
  const profiles = listCapacityProfiles({ agent_id: input.agent_id, project_id: input.project_id });
62914
63102
  const globalProfiles = input.project_id ? listCapacityProfiles({ agent_id: input.agent_id }).filter((profile) => profile.project_id === null) : [];
@@ -63006,6 +63194,7 @@ function renderPlanningForecastMarkdown(forecast) {
63006
63194
  }
63007
63195
  var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
63008
63196
  var init_capacity_forecasts = __esm(() => {
63197
+ init_database();
63009
63198
  init_tasks();
63010
63199
  init_task_relations();
63011
63200
  init_config2();
@@ -63560,7 +63749,8 @@ __export(exports_saved_search_views, {
63560
63749
  normalizeScope: () => normalizeScope,
63561
63750
  listSearchViews: () => listSearchViews,
63562
63751
  getSearchView: () => getSearchView,
63563
- deleteSearchView: () => deleteSearchView
63752
+ deleteSearchView: () => deleteSearchView,
63753
+ SAVED_SEARCH_SCOPES: () => SAVED_SEARCH_SCOPES
63564
63754
  });
63565
63755
  function parseFilters(value) {
63566
63756
  if (!value)
@@ -63580,10 +63770,8 @@ function rowToSavedSearchView(row) {
63580
63770
  };
63581
63771
  }
63582
63772
  function normalizeScope(scope) {
63583
- if (scope === "all" || scope === "tasks" || scope === "projects" || scope === "plans" || scope === "runs" || scope === "comments") {
63584
- return scope;
63585
- }
63586
- return "tasks";
63773
+ const candidate = (scope ?? "").trim().toLowerCase();
63774
+ return SAVED_SEARCH_SCOPES.includes(candidate) ? candidate : "tasks";
63587
63775
  }
63588
63776
  function normalizeName4(name) {
63589
63777
  const normalized = name.trim();
@@ -63886,10 +64074,12 @@ function runSearchView(idOrName, db) {
63886
64074
  throw new Error(`Saved search view not found: ${idOrName}`);
63887
64075
  return { ...runSavedSearch(view.filters, view.scope, d), view };
63888
64076
  }
64077
+ var SAVED_SEARCH_SCOPES;
63889
64078
  var init_saved_search_views = __esm(() => {
63890
64079
  init_database();
63891
64080
  init_local_fields();
63892
64081
  init_search();
64082
+ SAVED_SEARCH_SCOPES = ["all", "tasks", "projects", "plans", "runs", "comments"];
63893
64083
  });
63894
64084
 
63895
64085
  // src/mcp/tools/task-project-tools.ts
@@ -66439,8 +66629,10 @@ function getLatestHandoff(agentId, projectId, db) {
66439
66629
  function createSessionRecoveryHandoff(input, db) {
66440
66630
  const d = db || getDatabase();
66441
66631
  const limit = input.limit || 20;
66442
- const conditions = ["status = 'in_progress'", "(assigned_to = ? OR agent_id = ? OR locked_by = ?)"];
66443
- const params = [input.agent_id, input.agent_id, input.agent_id];
66632
+ const params = [];
66633
+ const assignedInClause = lowerInClause("assigned_to", resolveAssignedToAliases(d, input.agent_id), params);
66634
+ const conditions = ["status = 'in_progress'", `(${assignedInClause} OR agent_id = ? OR locked_by = ?)`];
66635
+ params.push(input.agent_id, input.agent_id);
66444
66636
  if (input.session_id) {
66445
66637
  conditions.push("session_id = ?");
66446
66638
  params.push(input.session_id);
@@ -67032,7 +67224,8 @@ function registerTaskAutoTools(server, ctx) {
67032
67224
  const assigned = listTasks4({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
67033
67225
  const now4 = Date.now();
67034
67226
  const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
67035
- const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
67227
+ const blockedAliases = assignedToAliasSet(getDatabase(), effectiveAgentId);
67228
+ const blocked = getBlockedTasks2().filter((t) => blockedAliases.has((t.assigned_to ?? "").toLowerCase()));
67036
67229
  const workload = {
67037
67230
  in_progress: assigned.filter((t) => t.status === "in_progress").length,
67038
67231
  pending: assigned.filter((t) => t.status === "pending").length,
@@ -67069,17 +67262,38 @@ function registerTaskAutoTools(server, ctx) {
67069
67262
  if (agents.length === 0)
67070
67263
  return { content: [{ type: "text", text: "No active agents available for rebalancing." }] };
67071
67264
  const activeTasks = listTasks4({ project_id: resolvedProjectId, status: ["pending", "in_progress"], limit: 1000 }, undefined);
67072
- const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => t.assigned_to === agent.id).length]));
67265
+ const agentKeyByAlias = new Map;
67266
+ const ambiguousAgentAliases = new Set;
67267
+ const indexAgentAlias = (alias, agentId) => {
67268
+ const normalizedAlias2 = alias.toLowerCase();
67269
+ if (ambiguousAgentAliases.has(normalizedAlias2))
67270
+ return;
67271
+ const existingAgentId = agentKeyByAlias.get(normalizedAlias2);
67272
+ if (existingAgentId !== undefined && existingAgentId !== agentId) {
67273
+ agentKeyByAlias.delete(normalizedAlias2);
67274
+ ambiguousAgentAliases.add(normalizedAlias2);
67275
+ return;
67276
+ }
67277
+ agentKeyByAlias.set(normalizedAlias2, agentId);
67278
+ };
67279
+ for (const agent of agents) {
67280
+ indexAgentAlias(String(agent.id), agent.id);
67281
+ if (agent.name)
67282
+ indexAgentAlias(String(agent.name), agent.id);
67283
+ }
67284
+ const canonicalAgentKey = (assignedTo) => assignedTo ? agentKeyByAlias.get(assignedTo.toLowerCase()) : undefined;
67285
+ const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => canonicalAgentKey(t.assigned_to) === agent.id).length]));
67073
67286
  let moved = 0;
67074
67287
  let skipped = 0;
67075
- for (const task of activeTasks.filter((t) => t.status === "pending" && t.assigned_to && (load.get(t.assigned_to) ?? 0) > limit)) {
67076
- const target = agents.filter((agent) => agent.id !== task.assigned_to).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
67288
+ for (const task of activeTasks.filter((t) => t.status === "pending" && canonicalAgentKey(t.assigned_to) && (load.get(canonicalAgentKey(t.assigned_to)) ?? 0) > limit)) {
67289
+ const currentAgentKey = canonicalAgentKey(task.assigned_to);
67290
+ const target = agents.filter((agent) => agent.id !== currentAgentKey).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
67077
67291
  if (!target || (load.get(target.id) ?? 0) >= limit) {
67078
67292
  skipped++;
67079
67293
  continue;
67080
67294
  }
67081
67295
  updateTask3(task.id, { assigned_to: target.id, version: task.version });
67082
- load.set(task.assigned_to, (load.get(task.assigned_to) ?? 1) - 1);
67296
+ load.set(currentAgentKey, (load.get(currentAgentKey) ?? 1) - 1);
67083
67297
  load.set(target.id, (load.get(target.id) ?? 0) + 1);
67084
67298
  moved++;
67085
67299
  }
@@ -67258,6 +67472,7 @@ ${lines.join(`
67258
67472
  var init_task_auto_tools = __esm(() => {
67259
67473
  init_zod();
67260
67474
  init_cloud_router();
67475
+ init_database();
67261
67476
  });
67262
67477
 
67263
67478
  // src/lib/context-packs.ts
@@ -68437,7 +68652,8 @@ ${JSON.stringify(task.metadata, null, 2)}` : null
68437
68652
  }, undefined);
68438
68653
  const completedYesterday = completed.filter((t) => t.completed_at && t.completed_at.startsWith(yesterdayStr));
68439
68654
  const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
68440
- const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => t.assigned_to === effectiveAgentId);
68655
+ const blockedAliases = effectiveAgentId ? assignedToAliasSet(getDatabase(), effectiveAgentId) : null;
68656
+ const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => blockedAliases ? blockedAliases.has((t.assigned_to ?? "").toLowerCase()) : false);
68441
68657
  const lines = [
68442
68658
  `Standup for ${effectiveAgentId} (${effectiveProjectId ? `project: ${effectiveProjectId.slice(0, 8)}` : "all projects"})`,
68443
68659
  inProgress.length > 0 ? `
@@ -68665,6 +68881,7 @@ var init_task_adv_tools = __esm(() => {
68665
68881
  init_zod();
68666
68882
  init_token_utils();
68667
68883
  init_cloud_router();
68884
+ init_database();
68668
68885
  });
68669
68886
 
68670
68887
  // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.cjs
@@ -94356,27 +94573,29 @@ function getAgentMetrics(agentId, opts, db) {
94356
94573
  const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
94357
94574
  if (!agent)
94358
94575
  return null;
94576
+ const assignedAliases = resolveAssignedToAliases(d, agent.id).map((a) => a.toLowerCase());
94577
+ const assignedInClause = `LOWER(assigned_to) IN (${assignedAliases.map(() => "?").join(",")})`;
94359
94578
  let projectFilter = "";
94360
- const params = [agent.id, agent.id];
94579
+ const params = [agent.id, ...assignedAliases];
94361
94580
  if (opts?.project_id) {
94362
94581
  projectFilter = " AND project_id = ?";
94363
94582
  params.push(opts.project_id);
94364
94583
  }
94365
- const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}`).get(...params).count;
94366
- const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'failed'${projectFilter}`).get(...params).count;
94367
- const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'in_progress'${projectFilter}`).get(...params).count;
94584
+ const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}`).get(...params).count;
94585
+ const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'failed'${projectFilter}`).get(...params).count;
94586
+ const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'in_progress'${projectFilter}`).get(...params).count;
94368
94587
  const total = completed + failed;
94369
94588
  const completionRate = total > 0 ? completed / total : 0;
94370
94589
  const avgTime = d.query(`SELECT AVG(
94371
94590
  (julianday(completed_at) - julianday(created_at)) * 24 * 60
94372
94591
  ) as avg_minutes
94373
94592
  FROM tasks
94374
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
94593
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
94375
94594
  const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
94376
94595
  FROM tasks
94377
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
94596
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
94378
94597
  const reviewTasks = d.query(`SELECT metadata FROM tasks
94379
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}
94598
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}
94380
94599
  AND metadata LIKE '%_review_score%'`).all(...params);
94381
94600
  let reviewScoreAvg = null;
94382
94601
  if (reviewTasks.length > 0) {
@@ -94840,14 +95059,15 @@ function millisBetween(start, end) {
94840
95059
  return 0;
94841
95060
  return endMs - startMs;
94842
95061
  }
94843
- function addTaskScope(where, params, options, alias = "t") {
95062
+ function addTaskScope(where, params, options, db, alias = "t") {
94844
95063
  if (options.project_id) {
94845
95064
  where.push(`${alias}.project_id = ?`);
94846
95065
  params.push(options.project_id);
94847
95066
  }
94848
95067
  if (options.agent_id) {
94849
- where.push(`(${alias}.agent_id = ? OR ${alias}.assigned_to = ?)`);
94850
- params.push(options.agent_id, options.agent_id);
95068
+ params.push(options.agent_id);
95069
+ const assignedInClause = lowerInClause(`${alias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
95070
+ where.push(`(${alias}.agent_id = ? OR ${assignedInClause})`);
94851
95071
  }
94852
95072
  if (options.since) {
94853
95073
  where.push(`${alias}.created_at >= ?`);
@@ -94858,14 +95078,15 @@ function addTaskScope(where, params, options, alias = "t") {
94858
95078
  params.push(options.until);
94859
95079
  }
94860
95080
  }
94861
- function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
95081
+ function addRunScope(where, params, options, db, runAlias = "r", taskAlias = "t") {
94862
95082
  if (options.project_id) {
94863
95083
  where.push(`${taskAlias}.project_id = ?`);
94864
95084
  params.push(options.project_id);
94865
95085
  }
94866
95086
  if (options.agent_id) {
94867
- where.push(`(${runAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${taskAlias}.assigned_to = ?)`);
94868
- params.push(options.agent_id, options.agent_id, options.agent_id);
95087
+ params.push(options.agent_id, options.agent_id);
95088
+ const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
95089
+ where.push(`(${runAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
94869
95090
  }
94870
95091
  if (options.since) {
94871
95092
  where.push(`${runAlias}.started_at >= ?`);
@@ -94876,14 +95097,15 @@ function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
94876
95097
  params.push(options.until);
94877
95098
  }
94878
95099
  }
94879
- function addTraceScope(where, params, options, traceAlias = "tr", taskAlias = "t") {
95100
+ function addTraceScope(where, params, options, db, traceAlias = "tr", taskAlias = "t") {
94880
95101
  if (options.project_id) {
94881
95102
  where.push(`${taskAlias}.project_id = ?`);
94882
95103
  params.push(options.project_id);
94883
95104
  }
94884
95105
  if (options.agent_id) {
94885
- where.push(`(${traceAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${taskAlias}.assigned_to = ?)`);
94886
- params.push(options.agent_id, options.agent_id, options.agent_id);
95106
+ params.push(options.agent_id, options.agent_id);
95107
+ const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
95108
+ where.push(`(${traceAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
94887
95109
  }
94888
95110
  if (options.since) {
94889
95111
  where.push(`${traceAlias}.created_at >= ?`);
@@ -94943,7 +95165,7 @@ function createLocalUsageLedger(options = {}, db) {
94943
95165
  const generatedAt = options.generated_at || new Date().toISOString();
94944
95166
  const taskWhere = [];
94945
95167
  const taskParams = [];
94946
- addTaskScope(taskWhere, taskParams, options);
95168
+ addTaskScope(taskWhere, taskParams, options, d);
94947
95169
  const taskClause = taskWhere.length ? `WHERE ${taskWhere.join(" AND ")}` : "";
94948
95170
  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);
94949
95171
  let projectCount = 0;
@@ -94952,14 +95174,14 @@ function createLocalUsageLedger(options = {}, db) {
94952
95174
  } else if (options.agent_id) {
94953
95175
  const projectWhere = ["t.project_id IS NOT NULL"];
94954
95176
  const projectParams = [];
94955
- addTaskScope(projectWhere, projectParams, options);
95177
+ addTaskScope(projectWhere, projectParams, options, d);
94956
95178
  projectCount = queryOne(d, `SELECT COUNT(DISTINCT t.project_id) as count FROM tasks t WHERE ${projectWhere.join(" AND ")}`, projectParams).count;
94957
95179
  } else {
94958
95180
  projectCount = queryOne(d, "SELECT COUNT(*) as count FROM projects", []).count;
94959
95181
  }
94960
95182
  const runWhere = [];
94961
95183
  const runParams = [];
94962
- addRunScope(runWhere, runParams, options);
95184
+ addRunScope(runWhere, runParams, options, d);
94963
95185
  const runClause = runWhere.length ? `WHERE ${runWhere.join(" AND ")}` : "";
94964
95186
  const runs = queryAll(d, `SELECT r.id, r.started_at, r.completed_at, r.metadata
94965
95187
  FROM task_runs r JOIN tasks t ON t.id = r.task_id
@@ -94976,7 +95198,7 @@ function createLocalUsageLedger(options = {}, db) {
94976
95198
  ${runClause}`, runParams);
94977
95199
  const traceWhere = [];
94978
95200
  const traceParams = [];
94979
- addTraceScope(traceWhere, traceParams, options);
95201
+ addTraceScope(traceWhere, traceParams, options, d);
94980
95202
  const traceClause = traceWhere.length ? `WHERE ${traceWhere.join(" AND ")}` : "";
94981
95203
  const traceTotals = queryOne(d, `SELECT COUNT(*) as traces,
94982
95204
  COALESCE(SUM(tr.tokens), 0) as tokens,
@@ -95120,10 +95342,12 @@ function limitValue(value) {
95120
95342
  function isTerminal2(task) {
95121
95343
  return task.status === "completed" || task.status === "failed" || task.status === "cancelled";
95122
95344
  }
95123
- function sameAgent(task, agentId) {
95345
+ function sameAgent(task, agentId, aliasSet) {
95124
95346
  if (!agentId)
95125
95347
  return true;
95126
- return task.assigned_to === agentId || task.agent_id === agentId;
95348
+ if (task.agent_id === agentId)
95349
+ return true;
95350
+ return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
95127
95351
  }
95128
95352
  function withinTaskWindow(task, options) {
95129
95353
  const time3 = Date.parse(task.updated_at);
@@ -95136,11 +95360,12 @@ function withinTaskWindow(task, options) {
95136
95360
  return true;
95137
95361
  }
95138
95362
  function scopedTasks(options, db) {
95363
+ const aliasSet = options.agent_id ? assignedToAliasSet(db, options.agent_id) : undefined;
95139
95364
  return listTasks2({
95140
95365
  project_id: options.project_id,
95141
95366
  plan_id: options.plan_id,
95142
95367
  include_archived: false
95143
- }, db).filter((task) => sameAgent(task, options.agent_id) && withinTaskWindow(task, options));
95368
+ }, db).filter((task) => sameAgent(task, options.agent_id, aliasSet) && withinTaskWindow(task, options));
95144
95369
  }
95145
95370
  function summarizeTask(task) {
95146
95371
  return {
@@ -95206,7 +95431,7 @@ function initialAgentSummary(agentId) {
95206
95431
  }
95207
95432
  };
95208
95433
  }
95209
- function addScopeClauses(where, params, options, timeColumn) {
95434
+ function addScopeClauses(where, params, options, timeColumn, db) {
95210
95435
  if (options.project_id) {
95211
95436
  where.push("t.project_id = ?");
95212
95437
  params.push(options.project_id);
@@ -95216,8 +95441,9 @@ function addScopeClauses(where, params, options, timeColumn) {
95216
95441
  params.push(options.plan_id);
95217
95442
  }
95218
95443
  if (options.agent_id) {
95219
- where.push("(r.agent_id = ? OR t.agent_id = ? OR t.assigned_to = ?)");
95220
- params.push(options.agent_id, options.agent_id, options.agent_id);
95444
+ params.push(options.agent_id, options.agent_id);
95445
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
95446
+ where.push(`(r.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
95221
95447
  }
95222
95448
  if (options.since) {
95223
95449
  where.push(`${timeColumn} >= ?`);
@@ -95231,7 +95457,7 @@ function addScopeClauses(where, params, options, timeColumn) {
95231
95457
  function loadRuns(options, db) {
95232
95458
  const where = ["t.archived_at IS NULL"];
95233
95459
  const params = [];
95234
- addScopeClauses(where, params, options, "r.started_at");
95460
+ addScopeClauses(where, params, options, "r.started_at", db);
95235
95461
  return db.query(`
95236
95462
  SELECT
95237
95463
  r.id,
@@ -95271,8 +95497,9 @@ function loadVerifications(options, db) {
95271
95497
  params.push(options.plan_id);
95272
95498
  }
95273
95499
  if (options.agent_id) {
95274
- where.push("(v.agent_id = ? OR t.agent_id = ? OR t.assigned_to = ?)");
95275
- params.push(options.agent_id, options.agent_id, options.agent_id);
95500
+ params.push(options.agent_id, options.agent_id);
95501
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
95502
+ where.push(`(v.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
95276
95503
  }
95277
95504
  if (options.since) {
95278
95505
  where.push("v.run_at >= ?");
@@ -95494,7 +95721,6 @@ function renderLocalReportMarkdown(report) {
95494
95721
  }
95495
95722
  var LOCAL_REPORT_SCHEMA_VERSION = 1, LOCAL_REPORT_TYPES;
95496
95723
  var init_local_reports = __esm(() => {
95497
- init_database();
95498
95724
  init_database();
95499
95725
  init_plans();
95500
95726
  init_tasks();
@@ -99597,7 +99823,7 @@ ${lines.join(`
99597
99823
  a.name AS agent_name
99598
99824
  FROM task_files tf
99599
99825
  JOIN tasks t ON tf.task_id = t.id
99600
- LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
99826
+ 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))))
99601
99827
  WHERE t.status = 'in_progress'
99602
99828
  AND tf.status != 'removed'
99603
99829
  AND t.project_id = ?
@@ -103359,9 +103585,16 @@ ${text2}` }] };
103359
103585
  return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
103360
103586
  }
103361
103587
  const oldName = agent.name;
103362
- const updated = updateAgent2(agent.id, { name: new_name });
103363
103588
  const db = getDatabase();
103364
- const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
103589
+ let oldNameUniquelyIdentifiesAgent = false;
103590
+ try {
103591
+ oldNameUniquelyIdentifiesAgent = resolvePartialId(db, "agents", oldName) === agent.id;
103592
+ } catch (error3) {
103593
+ if (!(error3 instanceof IdentityAliasAmbiguousError))
103594
+ throw error3;
103595
+ }
103596
+ const updated = updateAgent2(agent.id, { name: new_name });
103597
+ 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]);
103365
103598
  const taskNote = tasksResult.changes > 0 ? `
103366
103599
  Updated assigned_to on ${tasksResult.changes} task(s).` : "";
103367
103600
  return {
@@ -103545,6 +103778,7 @@ var init_agents2 = __esm(() => {
103545
103778
  init_agents();
103546
103779
  init_config2();
103547
103780
  init_database();
103781
+ init_types();
103548
103782
  init_cloud_router();
103549
103783
  });
103550
103784