@hasna/todos 0.13.8 → 0.13.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/contracts.js CHANGED
@@ -3430,7 +3430,7 @@ var init_machines = __esm(() => {
3430
3430
  function isBlockingDependencyStatus(status) {
3431
3431
  return status !== "completed" && status !== "cancelled";
3432
3432
  }
3433
- var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
3433
+ var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
3434
3434
  var init_types = __esm(() => {
3435
3435
  TASK_STATUSES = [
3436
3436
  "pending",
@@ -3470,6 +3470,20 @@ var init_types = __esm(() => {
3470
3470
  this.name = "TaskNotFoundError";
3471
3471
  }
3472
3472
  };
3473
+ TaskNotStartableError = class TaskNotStartableError extends Error {
3474
+ taskId;
3475
+ status;
3476
+ agentId;
3477
+ static code = "TASK_NOT_STARTABLE";
3478
+ static suggestion = "Reset the task status to pending before starting it again.";
3479
+ constructor(taskId, status, agentId) {
3480
+ super(`Task ${taskId} is ${status} and cannot be started by ${agentId}; ` + "reset the task status to pending before starting it again");
3481
+ this.taskId = taskId;
3482
+ this.status = status;
3483
+ this.agentId = agentId;
3484
+ this.name = "TaskNotStartableError";
3485
+ }
3486
+ };
3473
3487
  TaskReferenceAmbiguousError = class TaskReferenceAmbiguousError extends Error {
3474
3488
  reference;
3475
3489
  static code = "TASK_REFERENCE_AMBIGUOUS";
@@ -8242,7 +8256,7 @@ function assertStartable(task, agentId) {
8242
8256
  return;
8243
8257
  if (task.status === "in_progress")
8244
8258
  return;
8245
- throw new Error(`Task is ${task.status} and cannot be started by ${agentId}`);
8259
+ throw new TaskNotStartableError(task.id, task.status, agentId);
8246
8260
  }
8247
8261
  function getBlockingDeps(id, db) {
8248
8262
  const d = db || getDatabase();
@@ -8313,7 +8327,7 @@ function completeTask(id, agentId, db, options) {
8313
8327
  if (task.status === "cancelled") {
8314
8328
  throw new Error(`Task ${id} is cancelled and cannot be completed`);
8315
8329
  }
8316
- if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
8330
+ if (task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
8317
8331
  throw new LockError(id, task.locked_by);
8318
8332
  }
8319
8333
  checkCompletionGuard(task, agentId || null, d);
@@ -8948,6 +8962,30 @@ function getTaskWithRelations(id, db) {
8948
8962
  checklist
8949
8963
  };
8950
8964
  }
8965
+ function resolveAssignedToAliases(db, ref) {
8966
+ const aliases = new Set([ref]);
8967
+ let agentId;
8968
+ try {
8969
+ agentId = resolvePartialId(db, "agents", ref);
8970
+ } catch (err) {
8971
+ if (!(err instanceof IdentityAliasAmbiguousError))
8972
+ throw err;
8973
+ agentId = null;
8974
+ }
8975
+ if (agentId) {
8976
+ aliases.add(agentId);
8977
+ const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
8978
+ if (row?.name)
8979
+ aliases.add(row.name);
8980
+ }
8981
+ return [...aliases];
8982
+ }
8983
+ function lowerInClause(column, values, params) {
8984
+ if (values.length === 0)
8985
+ return "1=0";
8986
+ params.push(...values.map((v) => v.toLowerCase()));
8987
+ return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
8988
+ }
8951
8989
  function listTasks(filter = {}, db) {
8952
8990
  const d = db || getDatabase();
8953
8991
  const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
@@ -8989,8 +9027,7 @@ function listTasks(filter = {}, db) {
8989
9027
  }
8990
9028
  }
8991
9029
  if (filter.assigned_to) {
8992
- conditions.push("assigned_to = ?");
8993
- params.push(filter.assigned_to);
9030
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, filter.assigned_to), params));
8994
9031
  }
8995
9032
  if (filter.agent_id) {
8996
9033
  conditions.push("agent_id = ?");
@@ -9150,8 +9187,7 @@ function countTasks(filter = {}, db) {
9150
9187
  }
9151
9188
  }
9152
9189
  if (filter.assigned_to) {
9153
- conditions.push("assigned_to = ?");
9154
- params.push(filter.assigned_to);
9190
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, filter.assigned_to), params));
9155
9191
  }
9156
9192
  if (filter.agent_id) {
9157
9193
  conditions.push("agent_id = ?");
@@ -11973,7 +12009,7 @@ var init_tasks = __esm(() => {
11973
12009
  // package.json
11974
12010
  var package_default = {
11975
12011
  name: "@hasna/todos",
11976
- version: "0.13.8",
12012
+ version: "0.13.10",
11977
12013
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
11978
12014
  type: "module",
11979
12015
  main: "dist/index.js",
@@ -12023,8 +12059,8 @@ var package_default = {
12023
12059
  "README.md"
12024
12060
  ],
12025
12061
  scripts: {
12026
- build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
12027
- "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
12062
+ build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
12063
+ "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
12028
12064
  migrate: "bun run src/server/index.ts migrate",
12029
12065
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
12030
12066
  "generate:sdk": "bun run scripts/generate-sdk.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"task-crud.d.ts","sourceRoot":"","sources":["../../src/db/task-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,IAAI,EACJ,UAAU,EACV,OAAO,EACP,iBAAiB,EACjB,eAAe,EACf,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,mBAAmB,CAAC;AAkB3B,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAS5C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAMjF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGlF;AAwCD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAwFtE;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAK9D;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,EACV,EAAE,CAAC,EAAE,QAAQ,GACZ,iBAAiB,GAAG,IAAI,CA4D1B;AAED,wBAAgB,SAAS,CAAC,MAAM,GAAE,UAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,EAAE,CA2JxE;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,GAAG,IAAI,CAKb;AAcD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,4BAA4B,EACnC,EAAE,CAAC,EAAE,QAAQ,GACZ,6BAA6B,CAmD/B;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,QAAQ,CAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CA2HnG;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,eAAe,EACtB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,CAkPN;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAY7D"}
1
+ {"version":3,"file":"task-crud.d.ts","sourceRoot":"","sources":["../../src/db/task-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,IAAI,EACJ,UAAU,EACV,OAAO,EACP,iBAAiB,EACjB,eAAe,EACf,4BAA4B,EAC5B,6BAA6B,EAC9B,MAAM,mBAAmB,CAAC;AAmB3B,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAS5C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAMjF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGlF;AAwCD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAwFtE;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAK9D;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,EACV,EAAE,CAAC,EAAE,QAAQ,GACZ,iBAAiB,GAAG,IAAI,CA4D1B;AAiDD,wBAAgB,SAAS,CAAC,MAAM,GAAE,UAAe,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,EAAE,CA0JxE;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,GAAG,IAAI,CAKb;AAcD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,4BAA4B,EACnC,EAAE,CAAC,EAAE,QAAQ,GACZ,6BAA6B,CAmD/B;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,QAAQ,CAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CA0HnG;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,eAAe,EACtB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,CAkPN;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAY7D"}
@@ -1 +1 @@
1
- {"version":3,"file":"task-lifecycle.d.ts","sourceRoot":"","sources":["../../src/db/task-lifecycle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-lifecycle.d.ts","sourceRoot":"","sources":["../../src/db/task-lifecycle.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -3430,7 +3430,7 @@ var init_machines = __esm(() => {
3430
3430
  function isBlockingDependencyStatus(status) {
3431
3431
  return status !== "completed" && status !== "cancelled";
3432
3432
  }
3433
- var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
3433
+ var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
3434
3434
  var init_types = __esm(() => {
3435
3435
  TASK_STATUSES = [
3436
3436
  "pending",
@@ -3470,6 +3470,20 @@ var init_types = __esm(() => {
3470
3470
  this.name = "TaskNotFoundError";
3471
3471
  }
3472
3472
  };
3473
+ TaskNotStartableError = class TaskNotStartableError extends Error {
3474
+ taskId;
3475
+ status;
3476
+ agentId;
3477
+ static code = "TASK_NOT_STARTABLE";
3478
+ static suggestion = "Reset the task status to pending before starting it again.";
3479
+ constructor(taskId, status, agentId) {
3480
+ super(`Task ${taskId} is ${status} and cannot be started by ${agentId}; ` + "reset the task status to pending before starting it again");
3481
+ this.taskId = taskId;
3482
+ this.status = status;
3483
+ this.agentId = agentId;
3484
+ this.name = "TaskNotStartableError";
3485
+ }
3486
+ };
3473
3487
  TaskReferenceAmbiguousError = class TaskReferenceAmbiguousError extends Error {
3474
3488
  reference;
3475
3489
  static code = "TASK_REFERENCE_AMBIGUOUS";
@@ -8242,7 +8256,7 @@ function assertStartable(task, agentId) {
8242
8256
  return;
8243
8257
  if (task.status === "in_progress")
8244
8258
  return;
8245
- throw new Error(`Task is ${task.status} and cannot be started by ${agentId}`);
8259
+ throw new TaskNotStartableError(task.id, task.status, agentId);
8246
8260
  }
8247
8261
  function getBlockingDeps(id, db) {
8248
8262
  const d = db || getDatabase();
@@ -8313,7 +8327,7 @@ function completeTask(id, agentId, db, options) {
8313
8327
  if (task.status === "cancelled") {
8314
8328
  throw new Error(`Task ${id} is cancelled and cannot be completed`);
8315
8329
  }
8316
- if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
8330
+ if (task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
8317
8331
  throw new LockError(id, task.locked_by);
8318
8332
  }
8319
8333
  checkCompletionGuard(task, agentId || null, d);
@@ -8948,6 +8962,30 @@ function getTaskWithRelations(id, db) {
8948
8962
  checklist
8949
8963
  };
8950
8964
  }
8965
+ function resolveAssignedToAliases(db, ref) {
8966
+ const aliases = new Set([ref]);
8967
+ let agentId;
8968
+ try {
8969
+ agentId = resolvePartialId(db, "agents", ref);
8970
+ } catch (err) {
8971
+ if (!(err instanceof IdentityAliasAmbiguousError))
8972
+ throw err;
8973
+ agentId = null;
8974
+ }
8975
+ if (agentId) {
8976
+ aliases.add(agentId);
8977
+ const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
8978
+ if (row?.name)
8979
+ aliases.add(row.name);
8980
+ }
8981
+ return [...aliases];
8982
+ }
8983
+ function lowerInClause(column, values, params) {
8984
+ if (values.length === 0)
8985
+ return "1=0";
8986
+ params.push(...values.map((v) => v.toLowerCase()));
8987
+ return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
8988
+ }
8951
8989
  function listTasks(filter = {}, db) {
8952
8990
  const d = db || getDatabase();
8953
8991
  const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
@@ -8989,8 +9027,7 @@ function listTasks(filter = {}, db) {
8989
9027
  }
8990
9028
  }
8991
9029
  if (filter.assigned_to) {
8992
- conditions.push("assigned_to = ?");
8993
- params.push(filter.assigned_to);
9030
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, filter.assigned_to), params));
8994
9031
  }
8995
9032
  if (filter.agent_id) {
8996
9033
  conditions.push("agent_id = ?");
@@ -9150,8 +9187,7 @@ function countTasks(filter = {}, db) {
9150
9187
  }
9151
9188
  }
9152
9189
  if (filter.assigned_to) {
9153
- conditions.push("assigned_to = ?");
9154
- params.push(filter.assigned_to);
9190
+ conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, filter.assigned_to), params));
9155
9191
  }
9156
9192
  if (filter.agent_id) {
9157
9193
  conditions.push("agent_id = ?");
@@ -12086,7 +12122,7 @@ var init_dispatches = __esm(() => {
12086
12122
  // package.json
12087
12123
  var package_default = {
12088
12124
  name: "@hasna/todos",
12089
- version: "0.13.8",
12125
+ version: "0.13.10",
12090
12126
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12091
12127
  type: "module",
12092
12128
  main: "dist/index.js",
@@ -12136,8 +12172,8 @@ var package_default = {
12136
12172
  "README.md"
12137
12173
  ],
12138
12174
  scripts: {
12139
- build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
12140
- "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
12175
+ build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
12176
+ "build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
12141
12177
  migrate: "bun run src/server/index.ts migrate",
12142
12178
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
12143
12179
  "generate:sdk": "bun run scripts/generate-sdk.ts",
@@ -25450,6 +25486,7 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
25450
25486
  // src/storage/postgres-adapter.ts
25451
25487
  init_types();
25452
25488
  import { randomUUID as randomUUID3 } from "crypto";
25489
+ init_creator_identity();
25453
25490
 
25454
25491
  // src/storage/postgres-sync.ts
25455
25492
  var DEFAULT_TODOS_POSTGRES_SYNC_TABLE = "todos_sync_records";
@@ -26045,7 +26082,16 @@ class PostgresJsonRecordStore {
26045
26082
  updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
26046
26083
  }));
26047
26084
  }
26048
- buildTaskFilterSql(filter) {
26085
+ async resolveAssignedToAliases(ref) {
26086
+ const agent = await resolveAgentForAssignedFilter(ref, this);
26087
+ const aliases = new Set([ref]);
26088
+ if (agent) {
26089
+ aliases.add(agent.id);
26090
+ aliases.add(agent.name);
26091
+ }
26092
+ return [...aliases];
26093
+ }
26094
+ async buildTaskFilterSql(filter) {
26049
26095
  const params = [this.service, "tasks"];
26050
26096
  const conds = ["service = $1", "object_type = $2", "deleted_at IS NULL"];
26051
26097
  const p = (value) => {
@@ -26071,8 +26117,10 @@ class PostgresJsonRecordStore {
26071
26117
  conds.push(inClause("payload->>'status'", toFilterArray(filter.status)));
26072
26118
  if (filter.priority !== undefined)
26073
26119
  conds.push(inClause("payload->>'priority'", toFilterArray(filter.priority)));
26074
- if (filter.assigned_to !== undefined)
26075
- conds.push(`payload->>'assigned_to' = ${p(filter.assigned_to)}`);
26120
+ if (filter.assigned_to !== undefined) {
26121
+ const aliases = [...new Set((await this.resolveAssignedToAliases(filter.assigned_to)).map((a) => a.toLowerCase()))];
26122
+ conds.push(inClause("LOWER(payload->>'assigned_to')", aliases));
26123
+ }
26076
26124
  if (filter.agent_id !== undefined)
26077
26125
  conds.push(`payload->>'agent_id' = ${p(filter.agent_id)}`);
26078
26126
  if (filter.created_by !== undefined)
@@ -26106,7 +26154,7 @@ class PostgresJsonRecordStore {
26106
26154
  }
26107
26155
  async listTasks(filter) {
26108
26156
  await this.ensureSchema();
26109
- const { where, params, queryRef } = this.buildTaskFilterSql(filter);
26157
+ const { where, params, queryRef } = await this.buildTaskFilterSql(filter);
26110
26158
  const orderBy = queryRef ? `ORDER BY ts_rank_cd(task_search_tsv, websearch_to_tsquery('simple', todos_immutable_unaccent(${queryRef}))) DESC, ${TASK_ORDER_TIEBREAK}` : TASK_ORDER_BY;
26111
26159
  let sql = `/* todos:list-tasks */ SELECT payload FROM ${this.tableName} WHERE ${where} ${orderBy}`;
26112
26160
  if (filter.limit !== undefined) {
@@ -26166,7 +26214,7 @@ class PostgresJsonRecordStore {
26166
26214
  }
26167
26215
  async countTasks(filter) {
26168
26216
  await this.ensureSchema();
26169
- const { where, params } = this.buildTaskFilterSql(filter);
26217
+ const { where, params } = await this.buildTaskFilterSql(filter);
26170
26218
  const sql = `/* todos:count-tasks */ SELECT COUNT(*)::int AS count FROM ${this.tableName} WHERE ${where}`;
26171
26219
  const result = await this.options.client.query(sql, params);
26172
26220
  return Number(result.rows[0]?.count ?? 0);
@@ -26327,9 +26375,13 @@ class PostgresJsonRecordStore {
26327
26375
  } : {};
26328
26376
  const hasEvidence = Object.keys(evidence).length > 0;
26329
26377
  const hasConfidence = options?.confidence !== undefined;
26330
- const result = await this.options.client.query(`/* todos:complete-task-atomic */ UPDATE ${this.tableName}
26378
+ const lockExpiryCutoff2 = new Date(new Date(operationTimestamp).getTime() - CLOUD_LOCK_EXPIRY_MINUTES * 60 * 1000).toISOString();
26379
+ const result = await this.options.client.query(`/* todos:complete-task-atomic todos:complete-task-lock-guard todos:complete-task-clears-lock */
26380
+ UPDATE ${this.tableName}
26331
26381
  SET payload = payload || jsonb_build_object(
26332
26382
  'status', 'completed',
26383
+ 'locked_by', 'null'::jsonb,
26384
+ 'locked_at', 'null'::jsonb,
26333
26385
  'assigned_to', CASE
26334
26386
  WHEN jsonb_typeof(payload->'assigned_to') = 'string' THEN payload->'assigned_to'
26335
26387
  ELSE COALESCE(to_jsonb($3::text), 'null'::jsonb)
@@ -26357,6 +26409,16 @@ class PostgresJsonRecordStore {
26357
26409
  updated_at = $9::timestamptz,
26358
26410
  version = COALESCE(version, 0) + 1
26359
26411
  WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL
26412
+ AND (
26413
+ payload->>'locked_by' IS NULL
26414
+ OR BTRIM(payload->>'locked_by') = ''
26415
+ OR payload->>'locked_at' IS NULL
26416
+ OR payload->>'locked_at' < $10::text
26417
+ OR (
26418
+ $3::text IS NOT NULL
26419
+ AND LOWER(BTRIM(payload->>'locked_by')) = LOWER(BTRIM($3::text))
26420
+ )
26421
+ )
26360
26422
  RETURNING payload`, [
26361
26423
  this.service,
26362
26424
  id,
@@ -26366,7 +26428,8 @@ class PostgresJsonRecordStore {
26366
26428
  jsonbParam(evidence),
26367
26429
  hasConfidence,
26368
26430
  options?.confidence ?? null,
26369
- operationTimestamp
26431
+ operationTimestamp,
26432
+ lockExpiryCutoff2
26370
26433
  ]);
26371
26434
  return result.rows[0] ? payloadRecord2(result.rows[0].payload) : null;
26372
26435
  }
@@ -26647,6 +26710,7 @@ async function updateTask2(id, input, store) {
26647
26710
  if (existing.version !== input.version) {
26648
26711
  throw new Error(`Task ${id} version conflict: expected ${existing.version}, got ${input.version}`);
26649
26712
  }
26713
+ const reopened = existing.status === "completed" && input.status !== undefined && input.status !== "completed" && input.completed_at === undefined;
26650
26714
  const task2 = {
26651
26715
  ...existing,
26652
26716
  ...definedPatch(input),
@@ -26656,7 +26720,8 @@ async function updateTask2(id, input, store) {
26656
26720
  metadata: input.metadata ?? existing.metadata,
26657
26721
  requires_approval: input.requires_approval ?? existing.requires_approval,
26658
26722
  task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
26659
- created_by: existing.created_by
26723
+ created_by: existing.created_by,
26724
+ completed_at: reopened ? null : input.completed_at !== undefined ? input.completed_at : existing.completed_at
26660
26725
  };
26661
26726
  await store.upsert("tasks", task2);
26662
26727
  return task2;
@@ -26664,11 +26729,11 @@ async function updateTask2(id, input, store) {
26664
26729
  async function startTask2(id, agentId, store) {
26665
26730
  const task2 = await requireRecord("tasks", id, store);
26666
26731
  if (task2.status !== "pending" && task2.status !== "in_progress") {
26667
- throw new Error(`Task is ${task2.status} and cannot be started by ${agentId}`);
26732
+ throw new TaskNotStartableError(task2.id, task2.status, agentId);
26668
26733
  }
26669
26734
  const started = await patchTask(task2, {
26670
26735
  status: "in_progress",
26671
- assigned_to: task2.assigned_to ?? agentId,
26736
+ assigned_to: task2.assigned_to || agentId,
26672
26737
  agent_id: task2.agent_id ?? agentId,
26673
26738
  locked_by: agentId,
26674
26739
  locked_at: new Date().toISOString(),
@@ -26679,8 +26744,13 @@ async function startTask2(id, agentId, store) {
26679
26744
  }
26680
26745
  async function completeTask2(id, agentId, options, store) {
26681
26746
  const task2 = await store.completeTask(id, agentId, options);
26682
- if (!task2)
26747
+ if (!task2) {
26748
+ const current = await store.get("tasks", id);
26749
+ if (current?.locked_by && !cloudLockExpired(current.locked_at) && !sameCloudLockHolder(current.locked_by, agentId)) {
26750
+ throw new LockError(id, current.locked_by);
26751
+ }
26683
26752
  throw new Error(`tasks record not found: ${id}`);
26753
+ }
26684
26754
  return task2;
26685
26755
  }
26686
26756
  async function failTask2(id, agentId, reason, options, store) {
@@ -26722,6 +26792,11 @@ async function patchTask(task2, patch, store) {
26722
26792
  return updated;
26723
26793
  }
26724
26794
  var CLOUD_LOCK_EXPIRY_MINUTES = 30;
26795
+ function sameCloudLockHolder(stored, incoming) {
26796
+ if (!stored || !incoming)
26797
+ return false;
26798
+ return canonicalAgentRef(stored) === canonicalAgentRef(incoming);
26799
+ }
26725
26800
  function cloudLockExpired(lockedAt) {
26726
26801
  if (!lockedAt)
26727
26802
  return true;
@@ -27041,6 +27116,16 @@ async function resolveAgent(idOrName, store) {
27041
27116
  return byId;
27042
27117
  return matchAgentByName(await store.list("agents"), idOrName);
27043
27118
  }
27119
+ async function resolveAgentForAssignedFilter(idOrName, store) {
27120
+ const byId = await store.get("agents", idOrName);
27121
+ if (byId)
27122
+ return byId;
27123
+ const target = normalizeAgentNameInput(idOrName);
27124
+ if (!target)
27125
+ return null;
27126
+ const matches = (await store.list("agents")).filter((agent) => normalizeAgentNameInput(agent.name) === target);
27127
+ return matches.length === 1 ? matches[0] : null;
27128
+ }
27044
27129
  async function heartbeatAgent(idOrName, store, context) {
27045
27130
  const agent = await resolveAgent(idOrName, store);
27046
27131
  if (!agent)
@@ -0,0 +1,52 @@
1
+ /**
2
+ * How a task's lock should be PRESENTED to a reader.
3
+ *
4
+ * The scheduler and the renderer had drifted apart: every claim path already
5
+ * gates on `isLockExpired` (see `task-lifecycle.ts`, `task-route-sources.ts`,
6
+ * `local-reports.ts`), while every user-facing surface rendered a bare
7
+ * `locked_by` and so showed a lapsed lock as if it were held. `getTaskLockStatus`
8
+ * has computed an `expired` field the whole time and no renderer consulted it.
9
+ *
10
+ * The measured consequence: tasks the scheduler was handing out freely read as
11
+ * owned in `todos list`, so humans and agents skipped work that was available.
12
+ *
13
+ * This is the single predicate every renderer now shares, so the display can no
14
+ * longer disagree with the claim path. It deliberately reuses `isLockExpired`
15
+ * rather than recomputing a window: the local expiry (`LOCK_EXPIRY_MINUTES`) and
16
+ * the cloud expiry (`CLOUD_LOCK_EXPIRY_MINUTES`) are both 30 minutes, so one
17
+ * predicate is correct against either backend. If those two ever diverge, this
18
+ * is the one place that has to learn the difference.
19
+ */
20
+ export interface LockDisplayState {
21
+ /** A holder name is recorded on the row, regardless of whether it still counts. */
22
+ present: boolean;
23
+ /** Recorded AND still inside its expiry window — genuinely held right now. */
24
+ held: boolean;
25
+ /** Recorded but lapsed. The scheduler already ignores it; the reader must too. */
26
+ expired: boolean;
27
+ /** The recorded holder, kept even when expired so detail views can explain it. */
28
+ holder: string | null;
29
+ /** When the lock was taken, for detail views. */
30
+ lockedAt: string | null;
31
+ }
32
+ /**
33
+ * Classify a task's lock for rendering.
34
+ *
35
+ * `nowMs` is injectable so tests can pin the clock instead of sleeping.
36
+ *
37
+ * Note `isLockExpired(null)` is `true`: a row carrying a holder but no timestamp
38
+ * cannot be shown to be live, and the claim path treats it as free, so the
39
+ * display treats it as expired too rather than inventing a live lock.
40
+ */
41
+ export declare function lockDisplayState(lockedBy: string | null | undefined, lockedAt: string | null | undefined, nowMs?: number): LockDisplayState;
42
+ /**
43
+ * One-line description of a lapsed lock for DETAIL surfaces.
44
+ *
45
+ * Compact list surfaces omit an expired lock entirely — on a dense scanning
46
+ * surface the holder is noise, and 2584 rows currently carry a stale lock.
47
+ * Detail surfaces keep it, because someone reading a single task wants to know
48
+ * who last held it and when. Both forms are unmistakable: neither can be read
49
+ * as a live claim.
50
+ */
51
+ export declare function formatExpiredLock(state: LockDisplayState): string;
52
+ //# sourceMappingURL=lock-display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-display.d.ts","sourceRoot":"","sources":["../../src/lib/lock-display.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,OAAO,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,KAAK,GAAE,MAAmB,GACzB,gBAAgB,CAQlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAGjE"}
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import type { Task } from "../types/index.js";
3
4
  export declare function applyFocus(params: Record<string, any>, agentId?: string): void;
5
+ /** Compact single-line task summary for mutation responses (create/update/start/complete). */
6
+ export declare function formatTask(task: Task): string;
7
+ /** Full multi-line task detail for get_task responses. */
8
+ export declare function formatTaskDetail(task: Task, maxDescriptionChars?: number): string;
4
9
  export declare function buildServer(): McpServer;
5
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA6GpE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAO9E;AAmID,wBAAgB,WAAW,cAsD1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmBpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AA2F9C,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAO9E;AA0FD,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAO7C;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,MAAM,CA6BjF;AAID,wBAAgB,WAAW,cAsD1B"}