@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/cli/claim-guard.d.ts.map +1 -1
- package/dist/cli/cloud-router.d.ts +2 -0
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/components/TaskDetail.d.ts.map +1 -1
- package/dist/cli/components/TaskList.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +222 -48
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.js +46 -10
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/index.js +106 -21
- package/dist/lib/lock-display.d.ts +52 -0
- package/dist/lib/lock-display.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +159 -28
- package/dist/mcp.js +3 -3
- package/dist/registry.js +46 -10
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +14016 -1227
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage.js +103 -18
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -3
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
|
|
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 (
|
|
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.
|
|
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
|
|
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
|
|
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;
|
|
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":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,UAAU,EACV,IAAI,EAEL,MAAM,mBAAmB,CAAC;
|
|
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
|
|
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 (
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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"}
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -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
|
package/dist/mcp/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|