@hasna/todos 0.11.82 → 0.11.84

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/storage.js CHANGED
@@ -11907,7 +11907,19 @@ function createPostgresTodosStorageAdapter(options) {
11907
11907
  claimNext: (agentId, filters) => claimNextTask2(agentId, filters, store),
11908
11908
  getNext: (_agentId, filters) => getNextTask2(filters, store),
11909
11909
  getActiveWork: (filters) => getActiveWork2(filters, store),
11910
- getChangedSince: (since, filters) => getChangedSince(since, filters, store)
11910
+ getChangedSince: (since, filters) => getChangedSince(since, filters, store),
11911
+ lock: (id, agentId) => lockTask2(id, agentId, store),
11912
+ unlock: (id, agentId) => unlockTask2(id, agentId, store)
11913
+ },
11914
+ dependencies: {
11915
+ add: (taskId, dependsOn, context) => addDependency2(taskId, dependsOn, store, context),
11916
+ remove: (taskId, dependsOn) => removeDependency2(taskId, dependsOn, store),
11917
+ list: (taskId) => listDependencies(taskId, store),
11918
+ listAll: () => store.list("dependencies")
11919
+ },
11920
+ verifications: {
11921
+ add: (input, context) => addVerification(input, store, context),
11922
+ list: (taskId) => listVerifications(taskId, store)
11911
11923
  },
11912
11924
  projects: {
11913
11925
  create: (input, context) => createProject2(input, store, context),
@@ -12362,6 +12374,104 @@ async function patchTask(task, patch, store) {
12362
12374
  await store.upsert("tasks", updated);
12363
12375
  return updated;
12364
12376
  }
12377
+ var CLOUD_LOCK_EXPIRY_MINUTES = 30;
12378
+ function cloudLockExpired(lockedAt) {
12379
+ if (!lockedAt)
12380
+ return true;
12381
+ return new Date(lockedAt).getTime() + CLOUD_LOCK_EXPIRY_MINUTES * 60 * 1000 < Date.now();
12382
+ }
12383
+ function cloudLockExpiresAt(lockedAt) {
12384
+ return new Date(new Date(lockedAt).getTime() + CLOUD_LOCK_EXPIRY_MINUTES * 60 * 1000).toISOString();
12385
+ }
12386
+ async function lockTask2(id, agentId, store) {
12387
+ const task = await requireRecord("tasks", id, store);
12388
+ if (task.status === "completed" || task.status === "cancelled") {
12389
+ return { success: false, error: `Task is ${task.status} and cannot be locked` };
12390
+ }
12391
+ if (task.locked_by && task.locked_by !== agentId && !cloudLockExpired(task.locked_at)) {
12392
+ return { success: false, locked_by: task.locked_by, locked_at: task.locked_at ?? undefined, error: `Task is locked by ${task.locked_by}` };
12393
+ }
12394
+ const timestamp = new Date().toISOString();
12395
+ await patchTask(task, { locked_by: agentId, locked_at: timestamp }, store);
12396
+ return { success: true, locked_by: agentId, locked_at: timestamp, expires_at: cloudLockExpiresAt(timestamp) };
12397
+ }
12398
+ async function unlockTask2(id, agentId, store) {
12399
+ const task = await requireRecord("tasks", id, store);
12400
+ if (agentId && task.locked_by && task.locked_by !== agentId) {
12401
+ throw new Error(`Task ${id} is locked by ${task.locked_by}, not ${agentId}`);
12402
+ }
12403
+ await patchTask(task, { locked_by: null, locked_at: null }, store);
12404
+ return true;
12405
+ }
12406
+ function dependencyId(taskId, dependsOn) {
12407
+ return `${taskId}::${dependsOn}`;
12408
+ }
12409
+ async function addDependency2(taskId, dependsOn, store, context) {
12410
+ if (taskId === dependsOn)
12411
+ throw new Error("A task cannot depend on itself");
12412
+ if (!await store.get("tasks", taskId))
12413
+ throw new Error(`Task not found: ${taskId}`);
12414
+ if (!await store.get("tasks", dependsOn))
12415
+ throw new Error(`Task not found: ${dependsOn}`);
12416
+ const edges = await store.list("dependencies");
12417
+ const adjacency = new Map;
12418
+ for (const edge of edges) {
12419
+ if (!adjacency.has(edge.task_id))
12420
+ adjacency.set(edge.task_id, []);
12421
+ adjacency.get(edge.task_id).push(edge.depends_on);
12422
+ }
12423
+ const queue = [dependsOn];
12424
+ const seen = new Set;
12425
+ while (queue.length) {
12426
+ const node = queue.shift();
12427
+ if (node === taskId)
12428
+ throw new Error(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
12429
+ if (seen.has(node))
12430
+ continue;
12431
+ seen.add(node);
12432
+ for (const next of adjacency.get(node) ?? [])
12433
+ queue.push(next);
12434
+ }
12435
+ const timestamp = new Date().toISOString();
12436
+ const record = { id: dependencyId(taskId, dependsOn), task_id: taskId, depends_on: dependsOn, created_at: timestamp, updated_at: timestamp };
12437
+ await store.upsert("dependencies", record, context);
12438
+ return { task_id: taskId, depends_on: dependsOn };
12439
+ }
12440
+ async function removeDependency2(taskId, dependsOn, store) {
12441
+ const existing = await store.get("dependencies", dependencyId(taskId, dependsOn));
12442
+ if (!existing)
12443
+ return false;
12444
+ await store.delete("dependencies", dependencyId(taskId, dependsOn));
12445
+ return true;
12446
+ }
12447
+ async function listDependencies(taskId, store) {
12448
+ const edges = await store.list("dependencies");
12449
+ return {
12450
+ dependencies: edges.filter((edge) => edge.task_id === taskId).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on })),
12451
+ blocked_by: edges.filter((edge) => edge.depends_on === taskId).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on }))
12452
+ };
12453
+ }
12454
+ async function addVerification(input, store, context) {
12455
+ if (!await store.get("tasks", input.task_id))
12456
+ throw new Error(`Task not found: ${input.task_id}`);
12457
+ const timestamp = new Date().toISOString();
12458
+ const verification = {
12459
+ id: randomUUID3(),
12460
+ task_id: input.task_id,
12461
+ command: input.command,
12462
+ status: input.status ?? "unknown",
12463
+ output_summary: input.output_summary ?? null,
12464
+ artifact_path: input.artifact_path ?? null,
12465
+ agent_id: input.agent_id ?? context?.agentId ?? null,
12466
+ run_at: timestamp,
12467
+ created_at: timestamp
12468
+ };
12469
+ await store.upsert("verifications", { ...verification, updated_at: timestamp }, context);
12470
+ return verification;
12471
+ }
12472
+ async function listVerifications(taskId, store) {
12473
+ return (await store.list("verifications")).filter((verification) => verification.task_id === taskId).sort((a, b) => b.run_at.localeCompare(a.run_at));
12474
+ }
12365
12475
  var TASK_ORDER_BY = "ORDER BY CASE payload->>'priority' WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END ASC, payload->>'created_at' ASC, payload->>'id' ASC";
12366
12476
  function toFilterArray(value) {
12367
12477
  return Array.isArray(value) ? value : [value];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.11.82",
3
+ "version": "0.11.84",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",