@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/cli/index.js CHANGED
@@ -2123,7 +2123,7 @@ var package_default;
2123
2123
  var init_package = __esm(() => {
2124
2124
  package_default = {
2125
2125
  name: "@hasna/todos",
2126
- version: "0.13.11",
2126
+ version: "0.14.0",
2127
2127
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
2128
2128
  type: "module",
2129
2129
  main: "dist/index.js",
@@ -2258,7 +2258,10 @@ var init_package_version = __esm(() => {
2258
2258
  function isBlockingDependencyStatus(status) {
2259
2259
  return status !== "completed" && status !== "cancelled";
2260
2260
  }
2261
- var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
2261
+ function isTerminalStatus(status) {
2262
+ return status === "completed" || status === "failed" || status === "cancelled";
2263
+ }
2264
+ var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
2262
2265
  var init_types = __esm(() => {
2263
2266
  TASK_STATUSES = [
2264
2267
  "pending",
@@ -2427,6 +2430,7 @@ var init_types = __esm(() => {
2427
2430
  this.name = "CompletionGuardError";
2428
2431
  }
2429
2432
  };
2433
+ DISPATCH_STATUSES = ["pending", "sent", "failed", "cancelled"];
2430
2434
  DispatchNotFoundError = class DispatchNotFoundError extends Error {
2431
2435
  dispatchId;
2432
2436
  static code = "DISPATCH_NOT_FOUND";
@@ -6045,8 +6049,10 @@ async function cloudEscalatedTasks(client, opts = {}, at = new Date) {
6045
6049
  const filter = {};
6046
6050
  if (opts.project_id)
6047
6051
  filter.project_id = opts.project_id;
6052
+ if (opts.agent_id)
6053
+ filter.assigned_to = opts.agent_id;
6048
6054
  const active = await cloudActiveTasks(client, filter);
6049
- return active.filter((t) => !t.archived_at && (opts.agent_id ? t.assigned_to === opts.agent_id : true)).map((task) => {
6055
+ return active.filter((t) => !t.archived_at).map((task) => {
6050
6056
  const reasons = [];
6051
6057
  const breachedTimes = [];
6052
6058
  if (task.due_at) {
@@ -6258,7 +6264,11 @@ async function cloudRecap(client, hours, projectId) {
6258
6264
  const blocked = pending.filter((t) => blockedMap.has(t.id));
6259
6265
  const sinceMs = new Date(since).getTime();
6260
6266
  const agentSummaries = agents.map((agent) => {
6261
- const owned = all.filter((t) => t.assigned_to === agent.id || t.agent_id === agent.id);
6267
+ const assignedName = agent.name?.toLowerCase();
6268
+ const owned = all.filter((t) => {
6269
+ const assignedTo = t.assigned_to?.toLowerCase();
6270
+ return assignedTo === agent.id.toLowerCase() || assignedName && assignedTo === assignedName || t.agent_id === agent.id;
6271
+ });
6262
6272
  return {
6263
6273
  name: agent.name,
6264
6274
  completed_count: owned.filter((t) => t.status === "completed" && t.completed_at != null && t.completed_at > since).length,
@@ -10714,14 +10724,17 @@ var exports_database = {};
10714
10724
  __export(exports_database, {
10715
10725
  uuid: () => uuid,
10716
10726
  resolvePartialId: () => resolvePartialId,
10727
+ resolveAssignedToAliases: () => resolveAssignedToAliases,
10717
10728
  resetDatabase: () => resetDatabase,
10718
10729
  now: () => now,
10730
+ lowerInClause: () => lowerInClause,
10719
10731
  lockExpiryCutoff: () => lockExpiryCutoff,
10720
10732
  isLockExpired: () => isLockExpired,
10721
10733
  getDatabasePath: () => getDatabasePath,
10722
10734
  getDatabase: () => getDatabase,
10723
10735
  closeDatabase: () => closeDatabase,
10724
10736
  clearExpiredLocks: () => clearExpiredLocks,
10737
+ assignedToAliasSet: () => assignedToAliasSet,
10725
10738
  LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
10726
10739
  });
10727
10740
  import { Database } from "bun:sqlite";
@@ -10950,6 +10963,33 @@ function resolvePartialId(db, table, partialId) {
10950
10963
  }
10951
10964
  return null;
10952
10965
  }
10966
+ function resolveAssignedToAliases(db, ref) {
10967
+ const aliases = new Set([ref]);
10968
+ let agentId;
10969
+ try {
10970
+ agentId = resolvePartialId(db, "agents", ref);
10971
+ } catch (err) {
10972
+ if (!(err instanceof IdentityAliasAmbiguousError))
10973
+ throw err;
10974
+ agentId = null;
10975
+ }
10976
+ if (agentId) {
10977
+ aliases.add(agentId);
10978
+ const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
10979
+ if (row?.name)
10980
+ aliases.add(row.name);
10981
+ }
10982
+ return [...aliases];
10983
+ }
10984
+ function assignedToAliasSet(db, ref) {
10985
+ return new Set(resolveAssignedToAliases(db, ref).map((a) => a.toLowerCase()));
10986
+ }
10987
+ function lowerInClause(column, values, params) {
10988
+ if (values.length === 0)
10989
+ return "1=0";
10990
+ params.push(...values.map((v) => v.toLowerCase()));
10991
+ return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
10992
+ }
10953
10993
  var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
10954
10994
  var init_database = __esm(() => {
10955
10995
  init_schema();
@@ -11420,6 +11460,96 @@ var init_lock_display = __esm(() => {
11420
11460
  init_database();
11421
11461
  });
11422
11462
 
11463
+ // src/lib/enum-vocabulary.ts
11464
+ function editDistance2(a, b) {
11465
+ if (a === b)
11466
+ return 0;
11467
+ if (!a.length)
11468
+ return b.length;
11469
+ if (!b.length)
11470
+ return a.length;
11471
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
11472
+ for (let i = 1;i <= a.length; i += 1) {
11473
+ const current = [i];
11474
+ for (let j = 1;j <= b.length; j += 1) {
11475
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
11476
+ }
11477
+ previous = current;
11478
+ }
11479
+ return previous[b.length];
11480
+ }
11481
+ function suggestVocabularyMatches(value, vocabulary, limit = 3) {
11482
+ const needle = value.trim().toLowerCase();
11483
+ if (!needle)
11484
+ return [];
11485
+ const scored = [];
11486
+ for (const member of vocabulary) {
11487
+ const candidate = member.toLowerCase();
11488
+ if (candidate.startsWith(needle) || needle.startsWith(candidate)) {
11489
+ scored.push({ member, score: 0 });
11490
+ continue;
11491
+ }
11492
+ if (candidate.includes(needle) || needle.includes(candidate)) {
11493
+ scored.push({ member, score: 1 });
11494
+ continue;
11495
+ }
11496
+ const distance = editDistance2(needle, candidate);
11497
+ if (distance <= Math.max(1, Math.floor(candidate.length / 3))) {
11498
+ scored.push({ member, score: 1 + distance });
11499
+ }
11500
+ }
11501
+ return scored.sort((a, b) => a.score - b.score || a.member.localeCompare(b.member)).slice(0, limit).map((entry) => entry.member);
11502
+ }
11503
+ function resolveEnumVocabulary(raw, spec) {
11504
+ const allowList = spec.allowList !== false;
11505
+ const rawElements = (allowList ? raw.split(",") : [raw]).map((element) => element.trim()).filter((element) => element.length > 0);
11506
+ if (rawElements.length === 0) {
11507
+ return {
11508
+ ok: false,
11509
+ message: `${spec.name} requires a value. Allowed values: ${spec.vocabulary.join(", ")}.`,
11510
+ invalid: []
11511
+ };
11512
+ }
11513
+ const normalized = rawElements.map((element) => spec.normalize ? spec.normalize(element) : element);
11514
+ const allowed = new Set(spec.vocabulary);
11515
+ const invalid2 = [];
11516
+ for (let i = 0;i < normalized.length; i += 1) {
11517
+ if (!allowed.has(normalized[i]))
11518
+ invalid2.push(rawElements[i]);
11519
+ }
11520
+ if (invalid2.length === 0) {
11521
+ return { ok: true, values: [...new Set(normalized)] };
11522
+ }
11523
+ const label = invalid2.length === 1 ? "value" : "values";
11524
+ const parts = [
11525
+ `Invalid ${spec.name} ${label}: ${invalid2.join(", ")}.`,
11526
+ `Allowed values: ${spec.vocabulary.join(", ")}.`
11527
+ ];
11528
+ const hints = new Set;
11529
+ const suggestions = new Set;
11530
+ for (let i = 0;i < normalized.length; i += 1) {
11531
+ const canonical = normalized[i];
11532
+ if (allowed.has(canonical))
11533
+ continue;
11534
+ const hint = spec.hints?.[canonical.toLowerCase()];
11535
+ if (hint) {
11536
+ hints.add(hint);
11537
+ continue;
11538
+ }
11539
+ for (const match of suggestVocabularyMatches(canonical, spec.vocabulary)) {
11540
+ suggestions.add(match);
11541
+ }
11542
+ }
11543
+ if (suggestions.size > 0)
11544
+ parts.push(`Did you mean ${[...suggestions].join(", ")}?`);
11545
+ for (const hint of hints)
11546
+ parts.push(hint);
11547
+ return { ok: false, message: parts.join(" "), invalid: invalid2 };
11548
+ }
11549
+ function collapseEnumValues(values) {
11550
+ return values.length === 1 ? values[0] : values;
11551
+ }
11552
+
11423
11553
  // src/cli/helpers.ts
11424
11554
  var exports_helpers = {};
11425
11555
  __export(exports_helpers, {
@@ -11430,9 +11560,13 @@ __export(exports_helpers, {
11430
11560
  resolveExplicitProject: () => resolveExplicitProject,
11431
11561
  priorityColors: () => priorityColors,
11432
11562
  printJson: () => printJson,
11563
+ parseEnumFlagList: () => parseEnumFlagList,
11564
+ parseEnumFlag: () => parseEnumFlag,
11565
+ outputRecord: () => outputRecord,
11433
11566
  output: () => output,
11434
11567
  normalizeStatusList: () => normalizeStatusList,
11435
11568
  normalizeStatus: () => normalizeStatus,
11569
+ normalizePriority: () => normalizePriority,
11436
11570
  jsonModeRequested: () => jsonModeRequested,
11437
11571
  handleError: () => handleError,
11438
11572
  getPackageVersion: () => getPackageVersion,
@@ -11440,7 +11574,10 @@ __export(exports_helpers, {
11440
11574
  detectGitRoot: () => detectGitRoot,
11441
11575
  cacheCloudTaskForIdResolution: () => cacheCloudTaskForIdResolution,
11442
11576
  autoProject: () => autoProject,
11443
- autoDetectProject: () => autoDetectProject
11577
+ autoDetectProject: () => autoDetectProject,
11578
+ TASK_STATUS_FLAG_HINTS: () => TASK_STATUS_FLAG_HINTS,
11579
+ TASK_STATUS_FLAG: () => TASK_STATUS_FLAG,
11580
+ TASK_PRIORITY_FLAG: () => TASK_PRIORITY_FLAG
11444
11581
  });
11445
11582
  import chalk from "chalk";
11446
11583
  import { execSync } from "child_process";
@@ -11450,8 +11587,19 @@ import { dirname as dirname3, join as join4, resolve as resolve3, sep } from "pa
11450
11587
  function jsonModeRequested(argv = process.argv) {
11451
11588
  return argv.some((arg) => arg === "--json" || /^-[a-z]+$/i.test(arg) && arg.includes("j"));
11452
11589
  }
11590
+ function remoteErrorDetail(e) {
11591
+ if (!e || typeof e !== "object")
11592
+ return null;
11593
+ const body = e.body;
11594
+ if (!body || typeof body !== "object" || Array.isArray(body))
11595
+ return null;
11596
+ const detail = body.error;
11597
+ return typeof detail === "string" && detail.trim() ? detail.trim() : null;
11598
+ }
11453
11599
  function handleError(e) {
11454
- const message = e instanceof Error ? e.message : String(e);
11600
+ const baseMessage = e instanceof Error ? e.message : String(e);
11601
+ const detail = remoteErrorDetail(e);
11602
+ const message = detail && !baseMessage.includes(detail) ? `${baseMessage}: ${detail}` : baseMessage;
11455
11603
  console.error(chalk.red(message));
11456
11604
  if (jsonModeRequested()) {
11457
11605
  console.log(JSON.stringify({ error: message }));
@@ -11629,7 +11777,8 @@ function autoProject(opts) {
11629
11777
  return autoDetectProject(opts)?.id;
11630
11778
  }
11631
11779
  function normalizeStatus(s) {
11632
- switch (s.toLowerCase().trim()) {
11780
+ const folded = s.toLowerCase().trim();
11781
+ switch (folded) {
11633
11782
  case "done":
11634
11783
  return "completed";
11635
11784
  case "complete":
@@ -11643,14 +11792,33 @@ function normalizeStatus(s) {
11643
11792
  case "canceled":
11644
11793
  return "cancelled";
11645
11794
  default:
11646
- return s;
11795
+ return folded;
11647
11796
  }
11648
11797
  }
11798
+ function normalizePriority(p) {
11799
+ return p.toLowerCase().trim();
11800
+ }
11649
11801
  function normalizeStatusList(statuses) {
11650
11802
  if (Array.isArray(statuses))
11651
11803
  return statuses.map(normalizeStatus);
11652
11804
  return normalizeStatus(statuses);
11653
11805
  }
11806
+ function parseEnumFlag(raw, spec) {
11807
+ if (raw === undefined || raw === null)
11808
+ return;
11809
+ const result = resolveEnumVocabulary(String(raw), spec);
11810
+ if (!result.ok)
11811
+ handleError(new Error(result.message));
11812
+ return collapseEnumValues(result.values);
11813
+ }
11814
+ function parseEnumFlagList(raw, spec) {
11815
+ if (raw === undefined || raw === null)
11816
+ return;
11817
+ const result = resolveEnumVocabulary(String(raw), spec);
11818
+ if (!result.ok)
11819
+ handleError(new Error(result.message));
11820
+ return result.values;
11821
+ }
11654
11822
  function writeStdoutSync(text) {
11655
11823
  const buffer = Buffer.from(text);
11656
11824
  let offset = 0;
@@ -11682,6 +11850,41 @@ function output(data, jsonMode) {
11682
11850
  printJson(data);
11683
11851
  }
11684
11852
  }
11853
+ function formatRecordLines(data) {
11854
+ if (data === null || data === undefined)
11855
+ return [];
11856
+ if (typeof data !== "object")
11857
+ return [String(data)];
11858
+ if (Array.isArray(data)) {
11859
+ return data.flatMap((entry, index) => [
11860
+ chalk.dim(`[${index}]`),
11861
+ ...formatRecordLines(entry).map((line) => ` ${line}`)
11862
+ ]);
11863
+ }
11864
+ const lines = [];
11865
+ for (const [key, value] of Object.entries(data)) {
11866
+ if (value === undefined)
11867
+ continue;
11868
+ const rendered = value === null ? chalk.dim("\u2014") : typeof value === "object" ? JSON.stringify(value) : String(value);
11869
+ lines.push(` ${chalk.dim(`${key}:`)} ${rendered}`);
11870
+ }
11871
+ return lines;
11872
+ }
11873
+ function outputRecord(data, jsonMode, heading) {
11874
+ if (jsonMode) {
11875
+ printJson(data);
11876
+ return;
11877
+ }
11878
+ if (heading)
11879
+ console.log(chalk.bold(heading));
11880
+ const lines = formatRecordLines(data);
11881
+ if (lines.length === 0) {
11882
+ console.log(chalk.dim("(empty)"));
11883
+ return;
11884
+ }
11885
+ for (const line of lines)
11886
+ console.log(line);
11887
+ }
11685
11888
  function formatTaskLine(t) {
11686
11889
  const statusFn = statusColors[t.status] || chalk.white;
11687
11890
  const priorityFn = priorityColors[t.priority] || chalk.white;
@@ -11692,16 +11895,33 @@ function formatTaskLine(t) {
11692
11895
  const plan = t.plan_id ? chalk.magenta(` [plan:${t.plan_id.slice(0, 8)}]`) : "";
11693
11896
  return `${chalk.dim(t.id.slice(0, 8))} ${statusFn(t.status.padEnd(11))} ${priorityFn(t.priority.padEnd(8))} ${t.title}${assigned}${lock}${tags}${plan}`;
11694
11897
  }
11695
- var stdoutRetryBuffer, stdoutRetrySignal, TASK_UUID_RE, statusColors, priorityColors;
11898
+ var stdoutRetryBuffer, stdoutRetrySignal, TASK_UUID_RE, TASK_STATUS_FLAG_HINTS, TASK_STATUS_FLAG, TASK_PRIORITY_FLAG, statusColors, priorityColors;
11696
11899
  var init_helpers = __esm(() => {
11697
11900
  init_cloud_router();
11698
11901
  init_database();
11699
11902
  init_projects();
11700
11903
  init_lock_display();
11701
11904
  init_package_version();
11905
+ init_types();
11702
11906
  stdoutRetryBuffer = new SharedArrayBuffer(4);
11703
11907
  stdoutRetrySignal = new Int32Array(stdoutRetryBuffer);
11704
11908
  TASK_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11909
+ TASK_STATUS_FLAG_HINTS = {
11910
+ all: "To include every status, use -a/--all instead of a --status value.",
11911
+ any: "To include every status, use -a/--all instead of a --status value.",
11912
+ open: "Unstarted and running work is pending and in_progress (the default when no --status is given)."
11913
+ };
11914
+ TASK_STATUS_FLAG = {
11915
+ name: "--status",
11916
+ vocabulary: TASK_STATUSES,
11917
+ normalize: normalizeStatus,
11918
+ hints: TASK_STATUS_FLAG_HINTS
11919
+ };
11920
+ TASK_PRIORITY_FLAG = {
11921
+ name: "--priority",
11922
+ vocabulary: TASK_PRIORITIES,
11923
+ normalize: normalizePriority
11924
+ };
11705
11925
  statusColors = {
11706
11926
  pending: chalk.yellow,
11707
11927
  in_progress: chalk.blue,
@@ -11740,17 +11960,19 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
11740
11960
  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);
11741
11961
  }
11742
11962
  }
11963
+ const assignedAliasParams = [];
11964
+ const assignedInClause = agent ? lowerInClause("assigned_to", resolveAssignedToAliases(db, agent), assignedAliasParams) : "";
11743
11965
  if (agent && config.max_completions_per_window && config.window_minutes) {
11744
11966
  const windowStart = new Date(Date.now() - config.window_minutes * 60 * 1000).toISOString();
11745
11967
  const result = db.query(`SELECT COUNT(*) as count FROM tasks
11746
- WHERE completed_at > ? AND (assigned_to = ? OR agent_id = ?)`).get(windowStart, agent, agent);
11968
+ WHERE completed_at > ? AND (${assignedInClause} OR agent_id = ?)`).get(windowStart, ...assignedAliasParams, agent);
11747
11969
  if (result.count >= config.max_completions_per_window) {
11748
11970
  throw new CompletionGuardError(`Rate limit: ${result.count} tasks completed in the last ${config.window_minutes} minutes (max ${config.max_completions_per_window}). Slow down.`);
11749
11971
  }
11750
11972
  }
11751
11973
  if (agent && config.cooldown_seconds) {
11752
11974
  const result = db.query(`SELECT MAX(completed_at) as last_completed FROM tasks
11753
- WHERE completed_at IS NOT NULL AND (assigned_to = ? OR agent_id = ?) AND id != ?`).get(agent, agent, task.id);
11975
+ WHERE completed_at IS NOT NULL AND (${assignedInClause} OR agent_id = ?) AND id != ?`).get(...assignedAliasParams, agent, task.id);
11754
11976
  if (result.last_completed) {
11755
11977
  const elapsedSeconds = (Date.now() - new Date(result.last_completed).getTime()) / 1000;
11756
11978
  if (elapsedSeconds < config.cooldown_seconds) {
@@ -11764,6 +11986,7 @@ var init_completion_guard = __esm(() => {
11764
11986
  init_types();
11765
11987
  init_config();
11766
11988
  init_projects();
11989
+ init_database();
11767
11990
  });
11768
11991
 
11769
11992
  // src/lib/event-emission-safety.ts
@@ -13862,7 +14085,7 @@ function getRecap(hours = 8, projectId, db) {
13862
14085
  const in_progress = projectId ? d.query(`SELECT id, short_id, title, assigned_to, started_at FROM tasks WHERE status = 'in_progress' AND project_id = ? ORDER BY updated_at DESC`).all(projectId) : d.query(`SELECT id, short_id, title, assigned_to, started_at FROM tasks WHERE status = 'in_progress' ORDER BY updated_at DESC`).all();
13863
14086
  const blocked = projectId ? d.query(`SELECT DISTINCT t.id, t.short_id, t.title, t.assigned_to FROM tasks t JOIN task_dependencies td ON td.task_id = t.id JOIN tasks dep ON dep.id = td.depends_on AND dep.status NOT IN ('completed','cancelled') WHERE t.status = 'pending'${tpf}`).all(projectId) : d.query(`SELECT DISTINCT t.id, t.short_id, t.title, t.assigned_to FROM tasks t JOIN task_dependencies td ON td.task_id = t.id JOIN tasks dep ON dep.id = td.depends_on AND dep.status NOT IN ('completed','cancelled') WHERE t.status = 'pending'`).all();
13864
14087
  const stale = projectId ? d.query(`SELECT id, short_id, title, assigned_to, updated_at FROM tasks WHERE status = 'in_progress' AND updated_at < ? AND project_id = ? ORDER BY updated_at ASC`).all(staleWindow, projectId) : d.query(`SELECT id, short_id, title, assigned_to, updated_at FROM tasks WHERE status = 'in_progress' AND updated_at < ? ORDER BY updated_at ASC`).all(staleWindow);
13865
- const agents = projectId ? d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?${tpf}) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'in_progress'${tpf}) as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, projectId, projectId, since) : d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'in_progress') as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, since);
14088
+ const agents = projectId ? d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?${tpf}) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'in_progress'${tpf}) as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, projectId, projectId, since) : d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'in_progress') as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, since);
13866
14089
  return {
13867
14090
  hours,
13868
14091
  since,
@@ -14008,13 +14231,17 @@ function logDelivery(d, webhookId, event, payload, statusCode, response, attempt
14008
14231
  const id = uuid();
14009
14232
  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()]);
14010
14233
  }
14011
- function matchesScope(wh, payload) {
14234
+ function matchesScope(wh, payload, db) {
14012
14235
  if (wh.project_id && payload.project_id !== wh.project_id)
14013
14236
  return false;
14014
14237
  if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
14015
14238
  return false;
14016
- if (wh.agent_id && payload.agent_id !== wh.agent_id && payload.assigned_to !== wh.agent_id)
14017
- return false;
14239
+ if (wh.agent_id && payload.agent_id !== wh.agent_id) {
14240
+ const aliasSet = assignedToAliasSet(db, wh.agent_id);
14241
+ const assignedTo = typeof payload.assigned_to === "string" ? payload.assigned_to.toLowerCase() : "";
14242
+ if (!aliasSet.has(assignedTo))
14243
+ return false;
14244
+ }
14018
14245
  if (wh.task_id && payload.id !== wh.task_id)
14019
14246
  return false;
14020
14247
  return true;
@@ -14081,7 +14308,7 @@ async function dispatchWebhook2(event, payload, db) {
14081
14308
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
14082
14309
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
14083
14310
  for (const wh of webhooks) {
14084
- if (!matchesScope(wh, payloadObj))
14311
+ if (!matchesScope(wh, payloadObj, d))
14085
14312
  continue;
14086
14313
  const body = JSON.stringify({ event, payload, timestamp: now() });
14087
14314
  deliverWebhook(wh, event, body, 1, d).catch((err) => {
@@ -15229,14 +15456,16 @@ function getNextTask(agentId, filters, db) {
15229
15456
  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')");
15230
15457
  const where = conditions.join(" AND ");
15231
15458
  let recentProjectIds = [];
15459
+ const assignedAliasParams = [];
15460
+ const assignedInClause = agentId ? lowerInClause("assigned_to", resolveAssignedToAliases(d, agentId), assignedAliasParams) : "";
15232
15461
  if (agentId) {
15233
- 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);
15462
+ 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);
15234
15463
  recentProjectIds = recentRows.map((r) => r.project_id);
15235
15464
  }
15236
15465
  let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
15237
15466
  if (agentId) {
15238
- sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
15239
- params.push(agentId);
15467
+ sql += `CASE WHEN ${assignedInClause} THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
15468
+ params.push(...assignedAliasParams);
15240
15469
  }
15241
15470
  if (recentProjectIds.length > 0) {
15242
15471
  const placeholders = recentProjectIds.map(() => "?").join(",");
@@ -15630,30 +15859,6 @@ function getTaskWithRelations(id, db) {
15630
15859
  checklist
15631
15860
  };
15632
15861
  }
15633
- function resolveAssignedToAliases(db, ref) {
15634
- const aliases = new Set([ref]);
15635
- let agentId;
15636
- try {
15637
- agentId = resolvePartialId(db, "agents", ref);
15638
- } catch (err) {
15639
- if (!(err instanceof IdentityAliasAmbiguousError))
15640
- throw err;
15641
- agentId = null;
15642
- }
15643
- if (agentId) {
15644
- aliases.add(agentId);
15645
- const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
15646
- if (row?.name)
15647
- aliases.add(row.name);
15648
- }
15649
- return [...aliases];
15650
- }
15651
- function lowerInClause(column, values, params) {
15652
- if (values.length === 0)
15653
- return "1=0";
15654
- params.push(...values.map((v) => v.toLowerCase()));
15655
- return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
15656
- }
15657
15862
  function listTasks(filter = {}, db) {
15658
15863
  const d = db || getDatabase();
15659
15864
  const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
@@ -15939,11 +16144,13 @@ function updateTask(id, input, db) {
15939
16144
  }
15940
16145
  sets.push("status = ?");
15941
16146
  params.push(input.status);
16147
+ if (isTerminalStatus(input.status)) {
16148
+ sets.push("locked_by = NULL");
16149
+ sets.push("locked_at = NULL");
16150
+ }
15942
16151
  if (input.status === "completed") {
15943
16152
  sets.push("completed_at = ?");
15944
16153
  params.push(completionTimestamp);
15945
- sets.push("locked_by = NULL");
15946
- sets.push("locked_at = NULL");
15947
16154
  } else if (task.status === "completed" && input.completed_at === undefined) {
15948
16155
  sets.push("completed_at = NULL");
15949
16156
  }
@@ -16067,6 +16274,7 @@ function updateTask(id, input, db) {
16067
16274
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
16068
16275
  const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
16069
16276
  const completedNow = input.status === "completed";
16277
+ const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
16070
16278
  const updatedTask = {
16071
16279
  ...task,
16072
16280
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
@@ -16074,8 +16282,8 @@ function updateTask(id, input, db) {
16074
16282
  metadata: input.metadata ?? task.metadata,
16075
16283
  version: task.version + 1,
16076
16284
  updated_at: timestamp2,
16077
- locked_by: completedNow ? null : task.locked_by,
16078
- locked_at: completedNow ? null : task.locked_at,
16285
+ locked_by: terminalNow ? null : task.locked_by,
16286
+ locked_at: terminalNow ? null : task.locked_at,
16079
16287
  completed_at: completedNow ? completionTimestamp : reopened ? null : input.completed_at !== undefined ? input.completed_at : task.completed_at,
16080
16288
  sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
16081
16289
  actual_minutes: input.actual_minutes ?? task.actual_minutes,
@@ -16126,6 +16334,7 @@ function deleteTask(id, db) {
16126
16334
  var init_task_crud = __esm(() => {
16127
16335
  init_types();
16128
16336
  init_database();
16337
+ init_database();
16129
16338
  init_completion_guard();
16130
16339
  init_event_emission_safety();
16131
16340
  init_event_hooks();
@@ -16288,8 +16497,8 @@ function getTaskStats(filters, db) {
16288
16497
  params.push(filters.task_list_id);
16289
16498
  }
16290
16499
  if (filters?.agent_id) {
16291
- conditions.push("(agent_id = ? OR assigned_to = ?)");
16292
- params.push(filters.agent_id, filters.agent_id);
16500
+ params.push(filters.agent_id);
16501
+ conditions.push(`(agent_id = ? OR ${lowerInClause("assigned_to", resolveAssignedToAliases(d, filters.agent_id), params)})`);
16293
16502
  }
16294
16503
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
16295
16504
  const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
@@ -16472,8 +16681,7 @@ function getEscalatedTasks(opts = {}, db, at = new Date) {
16472
16681
  params.push(opts.project_id);
16473
16682
  }
16474
16683
  if (opts.agent_id) {
16475
- conditions.push("assigned_to = ?");
16476
- params.push(opts.agent_id);
16684
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
16477
16685
  }
16478
16686
  const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC, created_at ASC`).all(...params);
16479
16687
  return rows.map(rowToTask).map((task) => {
@@ -16521,8 +16729,7 @@ function notifyUpcomingDeadlines(opts = {}, db) {
16521
16729
  params.push(opts.project_id);
16522
16730
  }
16523
16731
  if (opts.agent_id) {
16524
- conditions.push("assigned_to = ?");
16525
- params.push(opts.agent_id);
16732
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
16526
16733
  }
16527
16734
  const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC`).all(...params);
16528
16735
  return rows.map(rowToTask);
@@ -16780,13 +16987,14 @@ function getTimeReport(opts, db) {
16780
16987
  params.push(opts.plan_id);
16781
16988
  }
16782
16989
  if (opts?.agent_id) {
16990
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.agent_id), params);
16783
16991
  conditions.push(`(
16784
- t.assigned_to = ?
16992
+ ${assignedInClause}
16785
16993
  OR t.agent_id = ?
16786
16994
  OR EXISTS (SELECT 1 FROM task_time_logs ttl WHERE ttl.task_id = t.id AND ttl.agent_id = ?)
16787
16995
  OR EXISTS (SELECT 1 FROM focus_sessions fs WHERE fs.task_id = t.id AND fs.agent_id = ?)
16788
16996
  )`);
16789
- params.push(opts.agent_id, opts.agent_id, opts.agent_id, opts.agent_id);
16997
+ params.push(opts.agent_id, opts.agent_id, opts.agent_id);
16790
16998
  }
16791
16999
  if (opts?.since) {
16792
17000
  conditions.push("(t.completed_at >= ? OR t.updated_at >= ?)");
@@ -17799,7 +18007,7 @@ function listActiveFiles(db) {
17799
18007
  a.name AS agent_name
17800
18008
  FROM task_files tf
17801
18009
  JOIN tasks t ON tf.task_id = t.id
17802
- LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
18010
+ 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))))
17803
18011
  WHERE t.status = 'in_progress'
17804
18012
  AND tf.status != 'removed'
17805
18013
  ORDER BY tf.updated_at DESC
@@ -20193,11 +20401,7 @@ function resolveProjectIdOrSlug(input) {
20193
20401
  function parseStatus(value) {
20194
20402
  if (!value)
20195
20403
  return;
20196
- const normalized = normalizeStatus(value);
20197
- if (!TASK_STATUSES.includes(normalized)) {
20198
- handleError(new Error(`--status must be one of: ${TASK_STATUSES.join(", ")}`));
20199
- }
20200
- return normalized;
20404
+ return parseEnumFlagList(value, { ...TASK_STATUS_FLAG, allowList: false })?.[0];
20201
20405
  }
20202
20406
  function parseIntOption(value, flag) {
20203
20407
  if (value === undefined)
@@ -20222,10 +20426,7 @@ function resolvePlanId(input) {
20222
20426
  function parsePriority(value) {
20223
20427
  if (!value)
20224
20428
  return;
20225
- if (!TASK_PRIORITIES.includes(value)) {
20226
- handleError(new Error("--priority must be one of: low, medium, high, critical"));
20227
- }
20228
- return value;
20429
+ return parseEnumFlagList(value, { ...TASK_PRIORITY_FLAG, allowList: false })?.[0];
20229
20430
  }
20230
20431
  function parseJsonObject3(value, flag) {
20231
20432
  if (!value)
@@ -20610,12 +20811,12 @@ function registerTaskCommands(program2) {
20610
20811
  filter["task_list_id"] = listId;
20611
20812
  }
20612
20813
  if (opts.status) {
20613
- filter["status"] = opts.status.includes(",") ? opts.status.split(",").map((s) => normalizeStatus(s.trim())) : normalizeStatus(opts.status);
20814
+ filter["status"] = parseEnumFlag(opts.status, TASK_STATUS_FLAG);
20614
20815
  } else if (!opts.all) {
20615
20816
  filter["status"] = ["pending", "in_progress"];
20616
20817
  }
20617
20818
  if (opts.priority)
20618
- filter["priority"] = opts.priority;
20819
+ filter["priority"] = parseEnumFlag(opts.priority, TASK_PRIORITY_FLAG);
20619
20820
  if (opts.assigned)
20620
20821
  filter["assigned_to"] = opts.assigned;
20621
20822
  if (opts.createdBy)
@@ -21588,7 +21789,6 @@ var init_task_commands = __esm(() => {
21588
21789
  init_assignee_guard();
21589
21790
  init_helpers();
21590
21791
  init_output_redaction();
21591
- init_types();
21592
21792
  });
21593
21793
 
21594
21794
  // src/lib/plan-artifacts.ts
@@ -23368,8 +23568,7 @@ function searchTasks(options, projectId, taskListId, db) {
23368
23568
  }
23369
23569
  }
23370
23570
  if (opts.assigned_to) {
23371
- sql += " AND t.assigned_to = ?";
23372
- params.push(opts.assigned_to);
23571
+ sql += ` AND ${lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.assigned_to), params)}`;
23373
23572
  }
23374
23573
  if (opts.agent_id) {
23375
23574
  sql += " AND t.agent_id = ?";
@@ -23421,7 +23620,8 @@ __export(exports_saved_search_views, {
23421
23620
  normalizeScope: () => normalizeScope,
23422
23621
  listSearchViews: () => listSearchViews,
23423
23622
  getSearchView: () => getSearchView,
23424
- deleteSearchView: () => deleteSearchView
23623
+ deleteSearchView: () => deleteSearchView,
23624
+ SAVED_SEARCH_SCOPES: () => SAVED_SEARCH_SCOPES
23425
23625
  });
23426
23626
  function parseFilters(value) {
23427
23627
  if (!value)
@@ -23441,10 +23641,8 @@ function rowToSavedSearchView(row) {
23441
23641
  };
23442
23642
  }
23443
23643
  function normalizeScope(scope) {
23444
- if (scope === "all" || scope === "tasks" || scope === "projects" || scope === "plans" || scope === "runs" || scope === "comments") {
23445
- return scope;
23446
- }
23447
- return "tasks";
23644
+ const candidate = (scope ?? "").trim().toLowerCase();
23645
+ return SAVED_SEARCH_SCOPES.includes(candidate) ? candidate : "tasks";
23448
23646
  }
23449
23647
  function normalizeName(name) {
23450
23648
  const normalized = name.trim();
@@ -23747,10 +23945,12 @@ function runSearchView(idOrName, db) {
23747
23945
  throw new Error(`Saved search view not found: ${idOrName}`);
23748
23946
  return { ...runSavedSearch(view.filters, view.scope, d), view };
23749
23947
  }
23948
+ var SAVED_SEARCH_SCOPES;
23750
23949
  var init_saved_search_views = __esm(() => {
23751
23950
  init_database();
23752
23951
  init_local_fields();
23753
23952
  init_search();
23953
+ SAVED_SEARCH_SCOPES = ["all", "tasks", "projects", "plans", "runs", "comments"];
23754
23954
  });
23755
23955
 
23756
23956
  // src/lib/claude-tasks.ts
@@ -26320,12 +26520,11 @@ function buildSearchFilters(query, opts, projectId) {
26320
26520
  const customFields = parseJsonObjectOption(opts.fieldCustom, "--field-custom");
26321
26521
  const labels = splitList(opts.fieldLabel);
26322
26522
  const tags = splitList(opts.tag);
26323
- const statuses = splitList(opts.status)?.map(normalizeStatus);
26324
26523
  const filters = {
26325
26524
  query: query || opts.query,
26326
26525
  project_id: opts.allProjects ? undefined : projectId,
26327
- status: statuses,
26328
- priority: splitList(opts.priority),
26526
+ status: parseEnumFlagList(opts.status, TASK_STATUS_FLAG),
26527
+ priority: parseEnumFlagList(opts.priority, TASK_PRIORITY_FLAG),
26329
26528
  assigned_to: opts.assigned,
26330
26529
  agent_id: opts.agentId,
26331
26530
  task_list_id: filterPatch?.task_list_id === undefined ? resolveTaskListFilter(opts.taskList, projectId) : undefined,
@@ -26430,7 +26629,7 @@ function registerProjectCommands(program2) {
26430
26629
  try {
26431
26630
  const cloud = getTodosCloudClient();
26432
26631
  const projectId = opts.allProjects ? undefined : cloud ? undefined : autoProject(globalOpts);
26433
- const scope = normalizeScope(opts.scope);
26632
+ const scope = parseEnumFlag(opts.scope, SEARCH_SCOPE_FLAG) ?? "tasks";
26434
26633
  const searchOpts = buildSearchFilters(query, opts, projectId);
26435
26634
  if (opts.saveAs) {
26436
26635
  const view = saveSearchView({
@@ -26522,7 +26721,7 @@ function registerProjectCommands(program2) {
26522
26721
  const view = saveSearchView({
26523
26722
  name,
26524
26723
  description: opts.description,
26525
- scope: normalizeScope(opts.scope),
26724
+ scope: parseEnumFlag(opts.scope, SEARCH_SCOPE_FLAG) ?? "tasks",
26526
26725
  filters: buildSearchFilters(opts.query, opts, projectId)
26527
26726
  });
26528
26727
  output(view, Boolean(globalOpts.json));
@@ -26535,7 +26734,7 @@ function registerProjectCommands(program2) {
26535
26734
  views.command("list").description("List local saved search views").option("--scope <scope>", "Filter by scope").action((opts) => {
26536
26735
  const globalOpts = program2.opts();
26537
26736
  try {
26538
- const rows = listSearchViews(opts.scope ? normalizeScope(opts.scope) : undefined);
26737
+ const rows = listSearchViews(parseEnumFlag(opts.scope, SEARCH_SCOPE_FLAG));
26539
26738
  output(rows, Boolean(globalOpts.json));
26540
26739
  if (!globalOpts.json) {
26541
26740
  if (rows.length === 0) {
@@ -26731,7 +26930,7 @@ function registerProjectCommands(program2) {
26731
26930
  const cloud = getTodosCloudClient();
26732
26931
  if (opts.show) {
26733
26932
  const project = cloud ? await cloudResolveProject(cloud, opts.show) : resolveExplicitProject(opts.show);
26734
- output(project, Boolean(globalOpts.json));
26933
+ outputRecord(project, Boolean(globalOpts.json), "Project:");
26735
26934
  return;
26736
26935
  }
26737
26936
  if (opts.update) {
@@ -26745,7 +26944,7 @@ function registerProjectCommands(program2) {
26745
26944
  }
26746
26945
  const current = cloud ? await cloudResolveProject(cloud, opts.update) : resolveExplicitProject(opts.update);
26747
26946
  const project = cloud ? await cloudUpdateProject(cloud, current.id, patch) : updateProject(current.id, patch);
26748
- output(project, Boolean(globalOpts.json));
26947
+ outputRecord(project, Boolean(globalOpts.json), "Project updated:");
26749
26948
  return;
26750
26949
  }
26751
26950
  if (opts.deregister) {
@@ -27053,6 +27252,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
27053
27252
  program2.command("export").description("Export tasks").option("-f, --format <format>", "Format: json, md, todos.md, or bridge", "json").option("-o, --output <path>", "Write export output to a file").option("--encrypt", "Encrypt bridge exports with a local encryption profile").option("--encryption-profile <name>", "Encryption profile name", "default").option("--allow-plaintext-sensitive", "Deprecated; bridge exports are redacted unless --encrypt is used").action(async (opts) => {
27054
27253
  const { listTasks: listTasks2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
27055
27254
  const globalOpts = program2.opts();
27255
+ opts.format = parseEnumFlag(opts.format, EXPORT_FORMAT_FLAG) ?? "json";
27056
27256
  const projectId = autoProject(globalOpts);
27057
27257
  const writeOutput = async (content) => {
27058
27258
  if (opts.output) {
@@ -27214,7 +27414,7 @@ function resolveTaskListId(partialId) {
27214
27414
  }
27215
27415
  return id;
27216
27416
  }
27217
- var CLOUD_GRAPH_EDGE_CONCURRENCY = 6;
27417
+ var SEARCH_SCOPE_FLAG, EXPORT_FORMATS, EXPORT_FORMAT_FLAG, CLOUD_GRAPH_EDGE_CONCURRENCY = 6;
27218
27418
  var init_project_commands = __esm(() => {
27219
27419
  init_database();
27220
27420
  init_projects();
@@ -27226,12 +27426,26 @@ var init_project_commands = __esm(() => {
27226
27426
  init_config();
27227
27427
  init_helpers();
27228
27428
  init_output_redaction();
27429
+ SEARCH_SCOPE_FLAG = {
27430
+ name: "--scope",
27431
+ vocabulary: SAVED_SEARCH_SCOPES,
27432
+ normalize: (value) => value.toLowerCase().trim(),
27433
+ allowList: false
27434
+ };
27435
+ EXPORT_FORMATS = ["json", "md", "markdown", "todos.md", "todos-md", "bridge"];
27436
+ EXPORT_FORMAT_FLAG = {
27437
+ name: "--format",
27438
+ vocabulary: EXPORT_FORMATS,
27439
+ normalize: (value) => value.toLowerCase().trim(),
27440
+ allowList: false
27441
+ };
27229
27442
  });
27230
27443
 
27231
27444
  // src/cli/commands/agent-commands.ts
27232
27445
  var exports_agent_commands = {};
27233
27446
  __export(exports_agent_commands, {
27234
- registerAgentCommands: () => registerAgentCommands
27447
+ registerAgentCommands: () => registerAgentCommands,
27448
+ findCaseVariantRows: () => findCaseVariantRows
27235
27449
  });
27236
27450
  import chalk6 from "chalk";
27237
27451
  import { execSync as execSync2 } from "child_process";
@@ -27245,6 +27459,15 @@ function resolveCloudAgentByNameOrId(agents, nameOrId) {
27245
27459
  return null;
27246
27460
  return matches.reduce((freshest, candidate) => new Date(candidate.last_seen_at).getTime() > new Date(freshest.last_seen_at).getTime() ? candidate : freshest);
27247
27461
  }
27462
+ function findCaseVariantRows(agents, requested) {
27463
+ const raw = requested.trim();
27464
+ const target = normalizeAgentNameInput(raw);
27465
+ if (!target)
27466
+ return [];
27467
+ if (agents.some((agent) => agent.name === raw))
27468
+ return [];
27469
+ return agents.filter((agent) => normalizeAgentNameInput(agent.name) === target);
27470
+ }
27248
27471
  function clearIdentityIfMine(agentId, agentName) {
27249
27472
  const persisted = readPersistedIdentity();
27250
27473
  if (!persisted)
@@ -27258,7 +27481,22 @@ function registerAgentCommands(program2) {
27258
27481
  const globalOpts = program2.opts();
27259
27482
  try {
27260
27483
  const cloud = getTodosCloudClient();
27261
- const result = cloud ? await cloudRegisterAgent(cloud, { name, description: opts.description }) : (await Promise.resolve().then(() => (init_agents(), exports_agents))).registerAgent({ name, description: opts.description });
27484
+ const registrationName = name.trim();
27485
+ if (cloud) {
27486
+ let roster = null;
27487
+ try {
27488
+ roster = await cloudListAgents(cloud);
27489
+ } catch {
27490
+ console.error(chalk6.yellow("Warning: could not read the shared roster, so the case-variant check was skipped for this registration."));
27491
+ }
27492
+ const variants = roster ? findCaseVariantRows(roster, registrationName) : [];
27493
+ if (variants.length > 0) {
27494
+ const listed = variants.map((a) => `${a.name} (${a.id})`).join(", ");
27495
+ console.error(chalk6.red(`'${name}' is a case variant of an agent that already exists: ${listed}. ` + `Agent names are ONE case-insensitive identity, so registering this spelling would create a second row ` + `and split the identity in two \u2014 tasks assigned to one spelling are invisible to the other. ` + `Register with the existing spelling instead.`));
27496
+ process.exit(1);
27497
+ }
27498
+ }
27499
+ const result = cloud ? await cloudRegisterAgent(cloud, { name: registrationName, description: opts.description }) : (await Promise.resolve().then(() => (init_agents(), exports_agents))).registerAgent({ name: registrationName, description: opts.description });
27262
27500
  const { isAgentConflict: isAgentConflict2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
27263
27501
  if (isAgentConflict2(result)) {
27264
27502
  console.error(chalk6.red("CONFLICT:"), result.message);
@@ -27603,7 +27841,7 @@ ${isOnline ? chalk6.green("\u25CF") : chalk6.dim("\u25CB")} ${chalk6.bold(agent.
27603
27841
  const list2 = cloud ? await cloudGetTaskList(cloud, resolved) : getTaskList(resolved);
27604
27842
  if (!list2)
27605
27843
  throw new Error(`Task list not found: ${ref}`);
27606
- output(list2, Boolean(globalOpts.json));
27844
+ outputRecord(list2, Boolean(globalOpts.json), "Task list:");
27607
27845
  return;
27608
27846
  }
27609
27847
  const patch = {
@@ -27614,7 +27852,7 @@ ${isOnline ? chalk6.green("\u25CF") : chalk6.dim("\u25CB")} ${chalk6.bold(agent.
27614
27852
  if (Object.keys(patch).length === 0)
27615
27853
  throw new Error("lists --update requires --name, --slug, or --description");
27616
27854
  const list = cloud ? await cloudUpdateTaskList(cloud, resolved, patch) : updateTaskList(resolved, patch);
27617
- output(list, Boolean(globalOpts.json));
27855
+ outputRecord(list, Boolean(globalOpts.json), "Task list updated:");
27618
27856
  return;
27619
27857
  }
27620
27858
  if (opts.delete) {
@@ -32165,9 +32403,11 @@ async function updateTask2(id, input, store) {
32165
32403
  throw new Error(`Task ${id} version conflict: expected ${existing.version}, got ${input.version}`);
32166
32404
  }
32167
32405
  const reopened = existing.status === "completed" && input.status !== undefined && input.status !== "completed" && input.completed_at === undefined;
32406
+ const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
32168
32407
  const task = {
32169
32408
  ...existing,
32170
32409
  ...definedPatch(input),
32410
+ ...terminalNow ? { locked_by: null, locked_at: null } : {},
32171
32411
  version: existing.version + 1,
32172
32412
  updated_at: new Date().toISOString(),
32173
32413
  tags: input.tags ?? existing.tags,
@@ -34508,8 +34748,19 @@ function handleStats(_ctx, json2) {
34508
34748
  recurring_tasks: countRecurringTasks()
34509
34749
  });
34510
34750
  }
34751
+ function taskStatusQueryParam(url) {
34752
+ const raw = url.searchParams.get("status");
34753
+ if (!raw)
34754
+ return { ok: true, value: undefined };
34755
+ const result = resolveEnumVocabulary(raw, { name: "status", vocabulary: TASK_STATUSES });
34756
+ if (!result.ok)
34757
+ return { ok: false, message: result.message };
34758
+ return { ok: true, value: collapseEnumValues(result.values) };
34759
+ }
34511
34760
  async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
34512
- const status = url.searchParams.get("status") || undefined;
34761
+ const statusParam = taskStatusQueryParam(url);
34762
+ if (!statusParam.ok)
34763
+ return json2({ error: statusParam.message }, 400);
34513
34764
  const projectId = url.searchParams.get("project_id") || undefined;
34514
34765
  const sessionId = url.searchParams.get("session_id") || undefined;
34515
34766
  const agentId = url.searchParams.get("agent_id") || undefined;
@@ -34517,7 +34768,7 @@ async function handleListTasks(_req, url, _ctx, json2, taskToSummary2) {
34517
34768
  const offsetParam = url.searchParams.get("offset");
34518
34769
  const fields = parseFieldsParam(url);
34519
34770
  const tasks = listTasks({
34520
- status,
34771
+ status: statusParam.value,
34521
34772
  project_id: projectId,
34522
34773
  session_id: sessionId,
34523
34774
  agent_id: agentId,
@@ -34582,9 +34833,19 @@ async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
34582
34833
  }
34583
34834
  function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
34584
34835
  const format = url.searchParams.get("format") || "json";
34585
- const status = url.searchParams.get("status") || undefined;
34836
+ const statusParam = taskStatusQueryParam(url);
34837
+ if (!statusParam.ok) {
34838
+ return new Response(JSON.stringify({ error: statusParam.message }), {
34839
+ status: 400,
34840
+ headers: { "Content-Type": "application/json" }
34841
+ });
34842
+ }
34586
34843
  const projectId = url.searchParams.get("project_id") || undefined;
34587
- const tasks = listTasks({ status, project_id: projectId, limit: 1e4 });
34844
+ const tasks = listTasks({
34845
+ status: statusParam.value,
34846
+ project_id: projectId,
34847
+ limit: 1e4
34848
+ });
34588
34849
  const summaries = tasks.map((t) => taskToSummary2(t));
34589
34850
  if (format === "csv") {
34590
34851
  const headers = ["id", "short_id", "title", "status", "priority", "project_id", "assigned_to", "agent_id", "created_at", "updated_at", "completed_at", "due_at"];
@@ -34899,8 +35160,9 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
34899
35160
  }
34900
35161
  }
34901
35162
  function handleAgentQueue(agentId, _ctx, json2, taskToSummary2) {
35163
+ const aliasSet = assignedToAliasSet(getDatabase(), agentId);
34902
35164
  const pending = listTasks({ status: "pending" });
34903
- const queue = pending.filter((t) => t.assigned_to === agentId || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
35165
+ const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
34904
35166
  const order = { critical: 0, high: 1, medium: 2, low: 3 };
34905
35167
  queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
34906
35168
  return json2(queue.map((t) => taskToSummary2(t)));
@@ -35165,6 +35427,7 @@ var init_routes = __esm(() => {
35165
35427
  init_tasks();
35166
35428
  init_database();
35167
35429
  init_types();
35430
+ init_types();
35168
35431
  init_projects();
35169
35432
  init_agents();
35170
35433
  init_plans();
@@ -37049,8 +37312,38 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
37049
37312
  operationId: "listTasks",
37050
37313
  summary: "List tasks",
37051
37314
  parameters: [
37052
- { name: "status", in: "query", schema: { type: "string" } },
37053
- { name: "priority", in: "query", schema: { type: "string" } },
37315
+ {
37316
+ name: "status",
37317
+ in: "query",
37318
+ description: `Task status, or a comma-separated list of statuses. Allowed values: ${TASK_STATUSES.join(", ")}.`,
37319
+ style: "form",
37320
+ explode: false,
37321
+ schema: {
37322
+ oneOf: [
37323
+ { type: "string", enum: [...TASK_STATUSES] },
37324
+ {
37325
+ type: "array",
37326
+ items: { type: "string", enum: [...TASK_STATUSES] }
37327
+ }
37328
+ ]
37329
+ }
37330
+ },
37331
+ {
37332
+ name: "priority",
37333
+ in: "query",
37334
+ description: `Task priority, or a comma-separated list of priorities. Allowed values: ${TASK_PRIORITIES.join(", ")}.`,
37335
+ style: "form",
37336
+ explode: false,
37337
+ schema: {
37338
+ oneOf: [
37339
+ { type: "string", enum: [...TASK_PRIORITIES] },
37340
+ {
37341
+ type: "array",
37342
+ items: { type: "string", enum: [...TASK_PRIORITIES] }
37343
+ }
37344
+ ]
37345
+ }
37346
+ },
37054
37347
  { name: "project_id", in: "query", schema: { type: "string" } },
37055
37348
  { name: "parent_id", in: "query", schema: { type: "string", nullable: true } },
37056
37349
  { name: "include_subtasks", in: "query", schema: { type: "boolean" } },
@@ -37562,6 +37855,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
37562
37855
  var taskSchema, projectSchema, taskListSchema, taskCommentSchema, planSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
37563
37856
  var init_openapi = __esm(() => {
37564
37857
  init_package_version();
37858
+ init_types();
37565
37859
  taskSchema = {
37566
37860
  type: "object",
37567
37861
  properties: {
@@ -37829,6 +38123,15 @@ function json3(body, status = 200) {
37829
38123
  function error(status, message, extra) {
37830
38124
  return json3({ error: message, ...extra ?? {} }, status);
37831
38125
  }
38126
+ function enumQueryParam(url, name, vocabulary) {
38127
+ const raw = url.searchParams.get(name);
38128
+ if (raw === null || raw === "")
38129
+ return { ok: true, value: undefined };
38130
+ const result = resolveEnumVocabulary(raw, { name, vocabulary });
38131
+ if (!result.ok)
38132
+ return { ok: false, response: error(400, result.message) };
38133
+ return { ok: true, value: collapseEnumValues(result.values) };
38134
+ }
37832
38135
  function validateTaskCompletion(value) {
37833
38136
  if (!value || typeof value !== "object" || Array.isArray(value))
37834
38137
  return { ok: false, message: "completion body must be an object" };
@@ -38249,14 +38552,16 @@ async function handleV1Request(req, url, dependencies = {}) {
38249
38552
  return error(400, "include_subtasks must be true or false");
38250
38553
  }
38251
38554
  const hasParentFilter = url.searchParams.has("parent_id");
38555
+ const statusParam = enumQueryParam(url, "status", TASK_STATUSES);
38556
+ if (!statusParam.ok)
38557
+ return statusParam.response;
38558
+ const priorityParam = enumQueryParam(url, "priority", TASK_PRIORITIES);
38559
+ if (!priorityParam.ok)
38560
+ return priorityParam.response;
38252
38561
  const filter = {
38253
38562
  ...url.searchParams.get("q") ? { query: url.searchParams.get("q") } : {},
38254
- ...url.searchParams.get("status") ? {
38255
- status: url.searchParams.get("status").includes(",") ? url.searchParams.get("status").split(",") : url.searchParams.get("status")
38256
- } : {},
38257
- ...url.searchParams.get("priority") ? {
38258
- priority: url.searchParams.get("priority").includes(",") ? url.searchParams.get("priority").split(",") : url.searchParams.get("priority")
38259
- } : {},
38563
+ ...statusParam.value !== undefined ? { status: statusParam.value } : {},
38564
+ ...priorityParam.value !== undefined ? { priority: priorityParam.value } : {},
38260
38565
  ...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
38261
38566
  ...hasParentFilter ? { parent_id: url.searchParams.get("parent_id") || null, include_subtasks: true } : includeSubtasks !== null ? { include_subtasks: includeSubtasks === "true" } : {},
38262
38567
  ...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {},
@@ -44587,11 +44892,12 @@ function runAlerts(input, db, checkedAt, quieted) {
44587
44892
  if (input.include_runs === false)
44588
44893
  return [];
44589
44894
  const since = Date.parse(input.run_since || new Date(Date.parse(checkedAt) - 24 * 60 * 60000).toISOString());
44895
+ const runAlertAliases = input.agent_id ? assignedToAliasSet(db, input.agent_id) : null;
44590
44896
  return listTaskRuns(undefined, db).filter((run) => run.completed_at && Date.parse(run.completed_at) >= since).map((run) => {
44591
44897
  const task = getTask(run.task_id, db);
44592
44898
  if (input.project_id && task?.project_id !== input.project_id)
44593
44899
  return null;
44594
- if (input.agent_id && run.agent_id !== input.agent_id && task?.assigned_to !== input.agent_id)
44900
+ if (input.agent_id && run.agent_id !== input.agent_id && !(runAlertAliases && runAlertAliases.has((task?.assigned_to ?? "").toLowerCase())))
44595
44901
  return null;
44596
44902
  const failed = ["failed", "cancelled"].includes(run.status);
44597
44903
  return {
@@ -44636,6 +44942,7 @@ async function checkLocalNotifications(input = {}, db) {
44636
44942
  const warnings = [];
44637
44943
  if (Number.isNaN(checkedMs))
44638
44944
  warnings.push("Invalid check timestamp; due-soon window may be incomplete.");
44945
+ const notifyAgentAliases = input.agent_id ? assignedToAliasSet(d, input.agent_id) : null;
44639
44946
  const alerts = [];
44640
44947
  for (const escalation of getEscalatedTasks({ project_id: input.project_id, agent_id: input.agent_id }, d, new Date(checkedAt))) {
44641
44948
  for (const reason of escalation.reasons) {
@@ -44647,7 +44954,7 @@ async function checkLocalNotifications(input = {}, db) {
44647
44954
  for (const task of listTasks({ project_id: input.project_id, include_archived: false, limit: 5000 }, d)) {
44648
44955
  if (!unfinished(task) || !task.due_at)
44649
44956
  continue;
44650
- if (input.agent_id && task.assigned_to !== input.agent_id && task.agent_id !== input.agent_id)
44957
+ if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
44651
44958
  continue;
44652
44959
  if (task.due_at > checkedAt && task.due_at <= dueSoonUntil) {
44653
44960
  alerts.push(taskAlert(task, "task_due_soon", "task.due_soon", "warning", `Due soon: ${task.title}`, task.due_at, quieted));
@@ -44655,7 +44962,7 @@ async function checkLocalNotifications(input = {}, db) {
44655
44962
  }
44656
44963
  }
44657
44964
  for (const task of getStaleTasks(staleMinutes, { project_id: input.project_id }, d)) {
44658
- if (input.agent_id && task.assigned_to !== input.agent_id && task.agent_id !== input.agent_id)
44965
+ if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
44659
44966
  continue;
44660
44967
  alerts.push(taskAlert(task, "task_stale", "task.stale", "warning", `Stale task: ${task.title}`, checkedAt, quieted, { stale_minutes: staleMinutes }));
44661
44968
  }
@@ -46480,10 +46787,12 @@ function matchesProfile(profile, query) {
46480
46787
  return false;
46481
46788
  return true;
46482
46789
  }
46483
- function taskMatchesAgent(task, agentId) {
46790
+ function taskMatchesAgent(task, agentId, aliasSet) {
46484
46791
  if (!agentId)
46485
46792
  return true;
46486
- return task.assigned_to === agentId || task.agent_id === agentId;
46793
+ if (task.agent_id === agentId)
46794
+ return true;
46795
+ return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
46487
46796
  }
46488
46797
  function estimateRemaining(task) {
46489
46798
  if (task.status === "completed" || task.status === "cancelled")
@@ -46553,7 +46862,8 @@ function removeCapacityProfile(idOrAgent, projectId) {
46553
46862
  }
46554
46863
  function getPlanningForecast(input = {}, db) {
46555
46864
  const startDate = input.start_date ? dateOnly(input.start_date) : todayIso();
46556
- const tasks = listTasks({ project_id: input.project_id, plan_id: input.plan_id, limit: 1e4 }, db).filter((task) => taskMatchesAgent(task, input.agent_id));
46865
+ const aliasSet = input.agent_id ? assignedToAliasSet(db || getDatabase(), input.agent_id) : undefined;
46866
+ const tasks = listTasks({ project_id: input.project_id, plan_id: input.plan_id, limit: 1e4 }, db).filter((task) => taskMatchesAgent(task, input.agent_id, aliasSet));
46557
46867
  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]));
46558
46868
  const profiles = listCapacityProfiles({ agent_id: input.agent_id, project_id: input.project_id });
46559
46869
  const globalProfiles = input.project_id ? listCapacityProfiles({ agent_id: input.agent_id }).filter((profile) => profile.project_id === null) : [];
@@ -46651,6 +46961,7 @@ function renderPlanningForecastMarkdown(forecast) {
46651
46961
  }
46652
46962
  var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
46653
46963
  var init_capacity_forecasts = __esm(() => {
46964
+ init_database();
46654
46965
  init_tasks();
46655
46966
  init_task_relations();
46656
46967
  init_config();
@@ -49760,8 +50071,10 @@ function getLatestHandoff(agentId, projectId, db) {
49760
50071
  function createSessionRecoveryHandoff(input, db) {
49761
50072
  const d = db || getDatabase();
49762
50073
  const limit = input.limit || 20;
49763
- const conditions = ["status = 'in_progress'", "(assigned_to = ? OR agent_id = ? OR locked_by = ?)"];
49764
- const params = [input.agent_id, input.agent_id, input.agent_id];
50074
+ const params = [];
50075
+ const assignedInClause = lowerInClause("assigned_to", resolveAssignedToAliases(d, input.agent_id), params);
50076
+ const conditions = ["status = 'in_progress'", `(${assignedInClause} OR agent_id = ? OR locked_by = ?)`];
50077
+ params.push(input.agent_id, input.agent_id);
49765
50078
  if (input.session_id) {
49766
50079
  conditions.push("session_id = ?");
49767
50080
  params.push(input.session_id);
@@ -50353,7 +50666,8 @@ function registerTaskAutoTools(server, ctx) {
50353
50666
  const assigned = listTasks4({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
50354
50667
  const now4 = Date.now();
50355
50668
  const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
50356
- const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
50669
+ const blockedAliases = assignedToAliasSet(getDatabase(), effectiveAgentId);
50670
+ const blocked = getBlockedTasks2().filter((t) => blockedAliases.has((t.assigned_to ?? "").toLowerCase()));
50357
50671
  const workload = {
50358
50672
  in_progress: assigned.filter((t) => t.status === "in_progress").length,
50359
50673
  pending: assigned.filter((t) => t.status === "pending").length,
@@ -50390,17 +50704,38 @@ function registerTaskAutoTools(server, ctx) {
50390
50704
  if (agents.length === 0)
50391
50705
  return { content: [{ type: "text", text: "No active agents available for rebalancing." }] };
50392
50706
  const activeTasks = listTasks4({ project_id: resolvedProjectId, status: ["pending", "in_progress"], limit: 1000 }, undefined);
50393
- const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => t.assigned_to === agent.id).length]));
50707
+ const agentKeyByAlias = new Map;
50708
+ const ambiguousAgentAliases = new Set;
50709
+ const indexAgentAlias = (alias, agentId) => {
50710
+ const normalizedAlias2 = alias.toLowerCase();
50711
+ if (ambiguousAgentAliases.has(normalizedAlias2))
50712
+ return;
50713
+ const existingAgentId = agentKeyByAlias.get(normalizedAlias2);
50714
+ if (existingAgentId !== undefined && existingAgentId !== agentId) {
50715
+ agentKeyByAlias.delete(normalizedAlias2);
50716
+ ambiguousAgentAliases.add(normalizedAlias2);
50717
+ return;
50718
+ }
50719
+ agentKeyByAlias.set(normalizedAlias2, agentId);
50720
+ };
50721
+ for (const agent of agents) {
50722
+ indexAgentAlias(String(agent.id), agent.id);
50723
+ if (agent.name)
50724
+ indexAgentAlias(String(agent.name), agent.id);
50725
+ }
50726
+ const canonicalAgentKey = (assignedTo) => assignedTo ? agentKeyByAlias.get(assignedTo.toLowerCase()) : undefined;
50727
+ const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => canonicalAgentKey(t.assigned_to) === agent.id).length]));
50394
50728
  let moved = 0;
50395
50729
  let skipped = 0;
50396
- for (const task of activeTasks.filter((t) => t.status === "pending" && t.assigned_to && (load.get(t.assigned_to) ?? 0) > limit)) {
50397
- const target = agents.filter((agent) => agent.id !== task.assigned_to).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
50730
+ for (const task of activeTasks.filter((t) => t.status === "pending" && canonicalAgentKey(t.assigned_to) && (load.get(canonicalAgentKey(t.assigned_to)) ?? 0) > limit)) {
50731
+ const currentAgentKey = canonicalAgentKey(task.assigned_to);
50732
+ const target = agents.filter((agent) => agent.id !== currentAgentKey).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
50398
50733
  if (!target || (load.get(target.id) ?? 0) >= limit) {
50399
50734
  skipped++;
50400
50735
  continue;
50401
50736
  }
50402
50737
  updateTask3(task.id, { assigned_to: target.id, version: task.version });
50403
- load.set(task.assigned_to, (load.get(task.assigned_to) ?? 1) - 1);
50738
+ load.set(currentAgentKey, (load.get(currentAgentKey) ?? 1) - 1);
50404
50739
  load.set(target.id, (load.get(target.id) ?? 0) + 1);
50405
50740
  moved++;
50406
50741
  }
@@ -50579,6 +50914,7 @@ ${lines.join(`
50579
50914
  var init_task_auto_tools = __esm(() => {
50580
50915
  init_zod();
50581
50916
  init_cloud_router();
50917
+ init_database();
50582
50918
  });
50583
50919
 
50584
50920
  // src/lib/context-packs.ts
@@ -51758,7 +52094,8 @@ ${JSON.stringify(task.metadata, null, 2)}` : null
51758
52094
  }, undefined);
51759
52095
  const completedYesterday = completed.filter((t) => t.completed_at && t.completed_at.startsWith(yesterdayStr));
51760
52096
  const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
51761
- const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => t.assigned_to === effectiveAgentId);
52097
+ const blockedAliases = effectiveAgentId ? assignedToAliasSet(getDatabase(), effectiveAgentId) : null;
52098
+ const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => blockedAliases ? blockedAliases.has((t.assigned_to ?? "").toLowerCase()) : false);
51762
52099
  const lines = [
51763
52100
  `Standup for ${effectiveAgentId} (${effectiveProjectId ? `project: ${effectiveProjectId.slice(0, 8)}` : "all projects"})`,
51764
52101
  inProgress.length > 0 ? `
@@ -51986,6 +52323,7 @@ var init_task_adv_tools = __esm(() => {
51986
52323
  init_zod();
51987
52324
  init_token_utils();
51988
52325
  init_cloud_router();
52326
+ init_database();
51989
52327
  });
51990
52328
 
51991
52329
  // src/mcp/tools/task-meta-tools.ts
@@ -54182,27 +54520,29 @@ function getAgentMetrics(agentId, opts, db) {
54182
54520
  const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
54183
54521
  if (!agent)
54184
54522
  return null;
54523
+ const assignedAliases = resolveAssignedToAliases(d, agent.id).map((a) => a.toLowerCase());
54524
+ const assignedInClause = `LOWER(assigned_to) IN (${assignedAliases.map(() => "?").join(",")})`;
54185
54525
  let projectFilter = "";
54186
- const params = [agent.id, agent.id];
54526
+ const params = [agent.id, ...assignedAliases];
54187
54527
  if (opts?.project_id) {
54188
54528
  projectFilter = " AND project_id = ?";
54189
54529
  params.push(opts.project_id);
54190
54530
  }
54191
- const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}`).get(...params).count;
54192
- const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'failed'${projectFilter}`).get(...params).count;
54193
- const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR assigned_to = ?) AND status = 'in_progress'${projectFilter}`).get(...params).count;
54531
+ const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}`).get(...params).count;
54532
+ const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'failed'${projectFilter}`).get(...params).count;
54533
+ const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'in_progress'${projectFilter}`).get(...params).count;
54194
54534
  const total = completed + failed;
54195
54535
  const completionRate = total > 0 ? completed / total : 0;
54196
54536
  const avgTime = d.query(`SELECT AVG(
54197
54537
  (julianday(completed_at) - julianday(created_at)) * 24 * 60
54198
54538
  ) as avg_minutes
54199
54539
  FROM tasks
54200
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
54540
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
54201
54541
  const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
54202
54542
  FROM tasks
54203
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
54543
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
54204
54544
  const reviewTasks = d.query(`SELECT metadata FROM tasks
54205
- WHERE (agent_id = ? OR assigned_to = ?) AND status = 'completed'${projectFilter}
54545
+ WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}
54206
54546
  AND metadata LIKE '%_review_score%'`).all(...params);
54207
54547
  let reviewScoreAvg = null;
54208
54548
  if (reviewTasks.length > 0) {
@@ -54672,14 +55012,15 @@ function millisBetween(start, end) {
54672
55012
  return 0;
54673
55013
  return endMs - startMs;
54674
55014
  }
54675
- function addTaskScope(where, params, options, alias = "t") {
55015
+ function addTaskScope(where, params, options, db, alias = "t") {
54676
55016
  if (options.project_id) {
54677
55017
  where.push(`${alias}.project_id = ?`);
54678
55018
  params.push(options.project_id);
54679
55019
  }
54680
55020
  if (options.agent_id) {
54681
- where.push(`(${alias}.agent_id = ? OR ${alias}.assigned_to = ?)`);
54682
- params.push(options.agent_id, options.agent_id);
55021
+ params.push(options.agent_id);
55022
+ const assignedInClause = lowerInClause(`${alias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
55023
+ where.push(`(${alias}.agent_id = ? OR ${assignedInClause})`);
54683
55024
  }
54684
55025
  if (options.since) {
54685
55026
  where.push(`${alias}.created_at >= ?`);
@@ -54690,14 +55031,15 @@ function addTaskScope(where, params, options, alias = "t") {
54690
55031
  params.push(options.until);
54691
55032
  }
54692
55033
  }
54693
- function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
55034
+ function addRunScope(where, params, options, db, runAlias = "r", taskAlias = "t") {
54694
55035
  if (options.project_id) {
54695
55036
  where.push(`${taskAlias}.project_id = ?`);
54696
55037
  params.push(options.project_id);
54697
55038
  }
54698
55039
  if (options.agent_id) {
54699
- where.push(`(${runAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${taskAlias}.assigned_to = ?)`);
54700
- params.push(options.agent_id, options.agent_id, options.agent_id);
55040
+ params.push(options.agent_id, options.agent_id);
55041
+ const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
55042
+ where.push(`(${runAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
54701
55043
  }
54702
55044
  if (options.since) {
54703
55045
  where.push(`${runAlias}.started_at >= ?`);
@@ -54708,14 +55050,15 @@ function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
54708
55050
  params.push(options.until);
54709
55051
  }
54710
55052
  }
54711
- function addTraceScope(where, params, options, traceAlias = "tr", taskAlias = "t") {
55053
+ function addTraceScope(where, params, options, db, traceAlias = "tr", taskAlias = "t") {
54712
55054
  if (options.project_id) {
54713
55055
  where.push(`${taskAlias}.project_id = ?`);
54714
55056
  params.push(options.project_id);
54715
55057
  }
54716
55058
  if (options.agent_id) {
54717
- where.push(`(${traceAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${taskAlias}.assigned_to = ?)`);
54718
- params.push(options.agent_id, options.agent_id, options.agent_id);
55059
+ params.push(options.agent_id, options.agent_id);
55060
+ const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
55061
+ where.push(`(${traceAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
54719
55062
  }
54720
55063
  if (options.since) {
54721
55064
  where.push(`${traceAlias}.created_at >= ?`);
@@ -54775,7 +55118,7 @@ function createLocalUsageLedger(options = {}, db) {
54775
55118
  const generatedAt = options.generated_at || new Date().toISOString();
54776
55119
  const taskWhere = [];
54777
55120
  const taskParams = [];
54778
- addTaskScope(taskWhere, taskParams, options);
55121
+ addTaskScope(taskWhere, taskParams, options, d);
54779
55122
  const taskClause = taskWhere.length ? `WHERE ${taskWhere.join(" AND ")}` : "";
54780
55123
  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);
54781
55124
  let projectCount = 0;
@@ -54784,14 +55127,14 @@ function createLocalUsageLedger(options = {}, db) {
54784
55127
  } else if (options.agent_id) {
54785
55128
  const projectWhere = ["t.project_id IS NOT NULL"];
54786
55129
  const projectParams = [];
54787
- addTaskScope(projectWhere, projectParams, options);
55130
+ addTaskScope(projectWhere, projectParams, options, d);
54788
55131
  projectCount = queryOne(d, `SELECT COUNT(DISTINCT t.project_id) as count FROM tasks t WHERE ${projectWhere.join(" AND ")}`, projectParams).count;
54789
55132
  } else {
54790
55133
  projectCount = queryOne(d, "SELECT COUNT(*) as count FROM projects", []).count;
54791
55134
  }
54792
55135
  const runWhere = [];
54793
55136
  const runParams = [];
54794
- addRunScope(runWhere, runParams, options);
55137
+ addRunScope(runWhere, runParams, options, d);
54795
55138
  const runClause = runWhere.length ? `WHERE ${runWhere.join(" AND ")}` : "";
54796
55139
  const runs = queryAll(d, `SELECT r.id, r.started_at, r.completed_at, r.metadata
54797
55140
  FROM task_runs r JOIN tasks t ON t.id = r.task_id
@@ -54808,7 +55151,7 @@ function createLocalUsageLedger(options = {}, db) {
54808
55151
  ${runClause}`, runParams);
54809
55152
  const traceWhere = [];
54810
55153
  const traceParams = [];
54811
- addTraceScope(traceWhere, traceParams, options);
55154
+ addTraceScope(traceWhere, traceParams, options, d);
54812
55155
  const traceClause = traceWhere.length ? `WHERE ${traceWhere.join(" AND ")}` : "";
54813
55156
  const traceTotals = queryOne(d, `SELECT COUNT(*) as traces,
54814
55157
  COALESCE(SUM(tr.tokens), 0) as tokens,
@@ -54952,10 +55295,12 @@ function limitValue(value) {
54952
55295
  function isTerminal2(task) {
54953
55296
  return task.status === "completed" || task.status === "failed" || task.status === "cancelled";
54954
55297
  }
54955
- function sameAgent(task, agentId) {
55298
+ function sameAgent(task, agentId, aliasSet) {
54956
55299
  if (!agentId)
54957
55300
  return true;
54958
- return task.assigned_to === agentId || task.agent_id === agentId;
55301
+ if (task.agent_id === agentId)
55302
+ return true;
55303
+ return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
54959
55304
  }
54960
55305
  function withinTaskWindow(task, options) {
54961
55306
  const time = Date.parse(task.updated_at);
@@ -54968,11 +55313,12 @@ function withinTaskWindow(task, options) {
54968
55313
  return true;
54969
55314
  }
54970
55315
  function scopedTasks(options, db) {
55316
+ const aliasSet = options.agent_id ? assignedToAliasSet(db, options.agent_id) : undefined;
54971
55317
  return listTasks({
54972
55318
  project_id: options.project_id,
54973
55319
  plan_id: options.plan_id,
54974
55320
  include_archived: false
54975
- }, db).filter((task) => sameAgent(task, options.agent_id) && withinTaskWindow(task, options));
55321
+ }, db).filter((task) => sameAgent(task, options.agent_id, aliasSet) && withinTaskWindow(task, options));
54976
55322
  }
54977
55323
  function summarizeTask(task) {
54978
55324
  return {
@@ -55038,7 +55384,7 @@ function initialAgentSummary(agentId) {
55038
55384
  }
55039
55385
  };
55040
55386
  }
55041
- function addScopeClauses(where, params, options, timeColumn) {
55387
+ function addScopeClauses(where, params, options, timeColumn, db) {
55042
55388
  if (options.project_id) {
55043
55389
  where.push("t.project_id = ?");
55044
55390
  params.push(options.project_id);
@@ -55048,8 +55394,9 @@ function addScopeClauses(where, params, options, timeColumn) {
55048
55394
  params.push(options.plan_id);
55049
55395
  }
55050
55396
  if (options.agent_id) {
55051
- where.push("(r.agent_id = ? OR t.agent_id = ? OR t.assigned_to = ?)");
55052
- params.push(options.agent_id, options.agent_id, options.agent_id);
55397
+ params.push(options.agent_id, options.agent_id);
55398
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
55399
+ where.push(`(r.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
55053
55400
  }
55054
55401
  if (options.since) {
55055
55402
  where.push(`${timeColumn} >= ?`);
@@ -55063,7 +55410,7 @@ function addScopeClauses(where, params, options, timeColumn) {
55063
55410
  function loadRuns(options, db) {
55064
55411
  const where = ["t.archived_at IS NULL"];
55065
55412
  const params = [];
55066
- addScopeClauses(where, params, options, "r.started_at");
55413
+ addScopeClauses(where, params, options, "r.started_at", db);
55067
55414
  return db.query(`
55068
55415
  SELECT
55069
55416
  r.id,
@@ -55103,8 +55450,9 @@ function loadVerifications(options, db) {
55103
55450
  params.push(options.plan_id);
55104
55451
  }
55105
55452
  if (options.agent_id) {
55106
- where.push("(v.agent_id = ? OR t.agent_id = ? OR t.assigned_to = ?)");
55107
- params.push(options.agent_id, options.agent_id, options.agent_id);
55453
+ params.push(options.agent_id, options.agent_id);
55454
+ const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
55455
+ where.push(`(v.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
55108
55456
  }
55109
55457
  if (options.since) {
55110
55458
  where.push("v.run_at >= ?");
@@ -55326,7 +55674,6 @@ function renderLocalReportMarkdown(report) {
55326
55674
  }
55327
55675
  var LOCAL_REPORT_SCHEMA_VERSION = 1, LOCAL_REPORT_TYPES;
55328
55676
  var init_local_reports = __esm(() => {
55329
- init_database();
55330
55677
  init_database();
55331
55678
  init_plans();
55332
55679
  init_tasks();
@@ -58382,7 +58729,7 @@ ${lines.join(`
58382
58729
  a.name AS agent_name
58383
58730
  FROM task_files tf
58384
58731
  JOIN tasks t ON tf.task_id = t.id
58385
- LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
58732
+ 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))))
58386
58733
  WHERE t.status = 'in_progress'
58387
58734
  AND tf.status != 'removed'
58388
58735
  AND t.project_id = ?
@@ -61760,9 +62107,16 @@ ${text2}` }] };
61760
62107
  return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
61761
62108
  }
61762
62109
  const oldName = agent.name;
61763
- const updated = updateAgent(agent.id, { name: new_name });
61764
62110
  const db = getDatabase();
61765
- const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
62111
+ let oldNameUniquelyIdentifiesAgent = false;
62112
+ try {
62113
+ oldNameUniquelyIdentifiesAgent = resolvePartialId(db, "agents", oldName) === agent.id;
62114
+ } catch (error2) {
62115
+ if (!(error2 instanceof IdentityAliasAmbiguousError))
62116
+ throw error2;
62117
+ }
62118
+ const updated = updateAgent(agent.id, { name: new_name });
62119
+ 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]);
61766
62120
  const taskNote = tasksResult.changes > 0 ? `
61767
62121
  Updated assigned_to on ${tasksResult.changes} task(s).` : "";
61768
62122
  return {
@@ -61946,6 +62300,7 @@ var init_agents2 = __esm(() => {
61946
62300
  init_agents();
61947
62301
  init_config();
61948
62302
  init_database();
62303
+ init_types();
61949
62304
  init_cloud_router();
61950
62305
  });
61951
62306
 
@@ -72111,7 +72466,11 @@ function registerDispatchCommands(program2) {
72111
72466
  process.exit(1);
72112
72467
  }
72113
72468
  }
72114
- const statuses = opts.status ? opts.status.split(",").map((s) => s.trim()) : undefined;
72469
+ const statuses = parseEnumFlagList(opts.status, {
72470
+ name: "--status",
72471
+ vocabulary: DISPATCH_STATUSES,
72472
+ normalize: (value) => value.toLowerCase().trim()
72473
+ });
72115
72474
  const dispatches = listDispatches({ status: statuses, limit: opts.limit ?? 20 });
72116
72475
  if (useJson) {
72117
72476
  console.log(JSON.stringify(dispatches, null, 2));
@@ -72144,6 +72503,8 @@ var init_dispatch3 = __esm(() => {
72144
72503
  init_dispatch();
72145
72504
  init_dispatch_formatter();
72146
72505
  init_tmux();
72506
+ init_types();
72507
+ init_helpers();
72147
72508
  });
72148
72509
 
72149
72510
  // src/cli/commands/machines.tsx