@hasna/todos 0.13.9 → 0.13.11
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/assignee-guard.d.ts +8 -1
- package/dist/cli/assignee-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/query-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 +224 -50
- 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/assignee-validation.d.ts +22 -0
- package/dist/lib/assignee-validation.d.ts.map +1 -1
- 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 -27
- package/dist/mcp.js +3 -3
- package/dist/registry.js +46 -10
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +14015 -1225
- 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/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.
|
|
2126
|
+
version: "0.13.11",
|
|
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",
|
|
@@ -2173,8 +2173,8 @@ var init_package = __esm(() => {
|
|
|
2173
2173
|
"README.md"
|
|
2174
2174
|
],
|
|
2175
2175
|
scripts: {
|
|
2176
|
-
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
|
|
2177
|
-
"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
|
|
2176
|
+
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",
|
|
2177
|
+
"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/*'",
|
|
2178
2178
|
migrate: "bun run src/server/index.ts migrate",
|
|
2179
2179
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
2180
2180
|
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
@@ -2258,7 +2258,7 @@ 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, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
|
|
2261
|
+
var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
|
|
2262
2262
|
var init_types = __esm(() => {
|
|
2263
2263
|
TASK_STATUSES = [
|
|
2264
2264
|
"pending",
|
|
@@ -2297,6 +2297,20 @@ var init_types = __esm(() => {
|
|
|
2297
2297
|
this.name = "TaskNotFoundError";
|
|
2298
2298
|
}
|
|
2299
2299
|
};
|
|
2300
|
+
TaskNotStartableError = class TaskNotStartableError extends Error {
|
|
2301
|
+
taskId;
|
|
2302
|
+
status;
|
|
2303
|
+
agentId;
|
|
2304
|
+
static code = "TASK_NOT_STARTABLE";
|
|
2305
|
+
static suggestion = "Reset the task status to pending before starting it again.";
|
|
2306
|
+
constructor(taskId, status, agentId) {
|
|
2307
|
+
super(`Task ${taskId} is ${status} and cannot be started by ${agentId}; ` + "reset the task status to pending before starting it again");
|
|
2308
|
+
this.taskId = taskId;
|
|
2309
|
+
this.status = status;
|
|
2310
|
+
this.agentId = agentId;
|
|
2311
|
+
this.name = "TaskNotStartableError";
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2300
2314
|
TaskReferenceAmbiguousError = class TaskReferenceAmbiguousError extends Error {
|
|
2301
2315
|
reference;
|
|
2302
2316
|
static code = "TASK_REFERENCE_AMBIGUOUS";
|
|
@@ -5174,6 +5188,16 @@ function classifyRemoteRequestError(baseUrl, route, error) {
|
|
|
5174
5188
|
if (status === 403) {
|
|
5175
5189
|
throw new Error(`REMOTE_API_FORBIDDEN: configured Todos authority ${baseUrl} denied ${route}; local SQLite fallback is disabled`, { cause: error });
|
|
5176
5190
|
}
|
|
5191
|
+
if (status === 409) {
|
|
5192
|
+
const body = error && typeof error === "object" ? error.body : undefined;
|
|
5193
|
+
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
5194
|
+
const remoteError = body;
|
|
5195
|
+
if (remoteError.code === "TASK_NOT_STARTABLE" && typeof remoteError.error === "string" && remoteError.error.length > 0) {
|
|
5196
|
+
throw new Error(`${remoteError.code}: ${remoteError.error}`, { cause: error });
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
throw error;
|
|
5200
|
+
}
|
|
5177
5201
|
if (typeof status === "number" && status >= 300 && status < 400) {
|
|
5178
5202
|
throw new Error(`REMOTE_API_REDIRECT_REJECTED: configured Todos authority ${baseUrl} redirected ${route}; ` + "authenticated redirects are disabled to prevent credential leakage", { cause: error });
|
|
5179
5203
|
}
|
|
@@ -5608,7 +5632,10 @@ function resolveCloudProjectRef(projects, ref) {
|
|
|
5608
5632
|
if (matches.length > 1)
|
|
5609
5633
|
throw new Error(`Project reference is ambiguous: "${input}"`);
|
|
5610
5634
|
}
|
|
5611
|
-
|
|
5635
|
+
if (UUID_RE.test(input) || /^[0-9a-f]{4,}$/i.test(input)) {
|
|
5636
|
+
throw new Error(`Project not found: "${input}"`);
|
|
5637
|
+
}
|
|
5638
|
+
throw new Error(`Project not found: "${input}" \u2014 no project in todos's own project registry matches this ` + `name, path, or slug. This does not know about projects registered elsewhere (for example ` + `a Hasna Projects CLI workspace) \u2014 resolve it there first (e.g. "projects show ${input} ` + `--json") and pass the resulting id or task-list slug, or run "todos projects create" to ` + `register it here. Do not assume "${input}" has never been created.`);
|
|
5612
5639
|
}
|
|
5613
5640
|
async function cloudResolveProjectRef(client, ref) {
|
|
5614
5641
|
return resolveCloudProjectRef(await cloudListProjects(client), ref);
|
|
@@ -5625,6 +5652,16 @@ async function cloudUpdateProject(client, id, patch) {
|
|
|
5625
5652
|
}
|
|
5626
5653
|
return raw;
|
|
5627
5654
|
}
|
|
5655
|
+
async function cloudDeleteProject(client, id) {
|
|
5656
|
+
try {
|
|
5657
|
+
await client.transport.del(`/projects/${encodeURIComponent(id)}`);
|
|
5658
|
+
} catch (error) {
|
|
5659
|
+
if (error && typeof error === "object" && error.status === 404)
|
|
5660
|
+
return false;
|
|
5661
|
+
throw error;
|
|
5662
|
+
}
|
|
5663
|
+
return true;
|
|
5664
|
+
}
|
|
5628
5665
|
async function cloudListPlans(client, projectId) {
|
|
5629
5666
|
const query = projectId ? { project_id: projectId } : {};
|
|
5630
5667
|
const res = await requiredRemoteRoute(client, "/v1/plans", () => client.list("plans", { query }));
|
|
@@ -6412,7 +6449,9 @@ function disqualifyingArgument(invocation) {
|
|
|
6412
6449
|
return dropIt("the `routing` subcommand");
|
|
6413
6450
|
return firstPresentOption(args, ["--apply", "--fix"]);
|
|
6414
6451
|
case "projects":
|
|
6415
|
-
|
|
6452
|
+
if (hasOption(args, "--deregister"))
|
|
6453
|
+
return null;
|
|
6454
|
+
return firstPresentOption(args, ["--path-prefix", "--dry-run"]);
|
|
6416
6455
|
case "plans":
|
|
6417
6456
|
return firstPresentOption(args, ["--artifact", "--write-artifacts"]);
|
|
6418
6457
|
case "list":
|
|
@@ -11363,6 +11402,24 @@ var init_projects = __esm(() => {
|
|
|
11363
11402
|
init_storage_tombstones();
|
|
11364
11403
|
});
|
|
11365
11404
|
|
|
11405
|
+
// src/lib/lock-display.ts
|
|
11406
|
+
function lockDisplayState(lockedBy, lockedAt, nowMs = Date.now()) {
|
|
11407
|
+
const holder = lockedBy ?? null;
|
|
11408
|
+
const at = lockedAt ?? null;
|
|
11409
|
+
if (!holder) {
|
|
11410
|
+
return { present: false, held: false, expired: false, holder: null, lockedAt: at };
|
|
11411
|
+
}
|
|
11412
|
+
const expired = isLockExpired(at, nowMs);
|
|
11413
|
+
return { present: true, held: !expired, expired, holder, lockedAt: at };
|
|
11414
|
+
}
|
|
11415
|
+
function formatExpiredLock(state) {
|
|
11416
|
+
const at = state.lockedAt ? ` at ${state.lockedAt}` : "";
|
|
11417
|
+
return `expired (last held by ${state.holder}${at})`;
|
|
11418
|
+
}
|
|
11419
|
+
var init_lock_display = __esm(() => {
|
|
11420
|
+
init_database();
|
|
11421
|
+
});
|
|
11422
|
+
|
|
11366
11423
|
// src/cli/helpers.ts
|
|
11367
11424
|
var exports_helpers = {};
|
|
11368
11425
|
__export(exports_helpers, {
|
|
@@ -11628,7 +11685,8 @@ function output(data, jsonMode) {
|
|
|
11628
11685
|
function formatTaskLine(t) {
|
|
11629
11686
|
const statusFn = statusColors[t.status] || chalk.white;
|
|
11630
11687
|
const priorityFn = priorityColors[t.priority] || chalk.white;
|
|
11631
|
-
const
|
|
11688
|
+
const lockState = lockDisplayState(t.locked_by, t.locked_at);
|
|
11689
|
+
const lock = lockState.held ? chalk.magenta(` [locked:${lockState.holder}]`) : "";
|
|
11632
11690
|
const assigned = t.assigned_to ? chalk.cyan(` -> ${t.assigned_to}`) : "";
|
|
11633
11691
|
const tags = t.tags.length > 0 ? chalk.dim(` [${t.tags.join(",")}]`) : "";
|
|
11634
11692
|
const plan = t.plan_id ? chalk.magenta(` [plan:${t.plan_id.slice(0, 8)}]`) : "";
|
|
@@ -11639,6 +11697,7 @@ var init_helpers = __esm(() => {
|
|
|
11639
11697
|
init_cloud_router();
|
|
11640
11698
|
init_database();
|
|
11641
11699
|
init_projects();
|
|
11700
|
+
init_lock_display();
|
|
11642
11701
|
init_package_version();
|
|
11643
11702
|
stdoutRetryBuffer = new SharedArrayBuffer(4);
|
|
11644
11703
|
stdoutRetrySignal = new Int32Array(stdoutRetryBuffer);
|
|
@@ -14865,7 +14924,7 @@ function assertStartable(task, agentId) {
|
|
|
14865
14924
|
return;
|
|
14866
14925
|
if (task.status === "in_progress")
|
|
14867
14926
|
return;
|
|
14868
|
-
throw new
|
|
14927
|
+
throw new TaskNotStartableError(task.id, task.status, agentId);
|
|
14869
14928
|
}
|
|
14870
14929
|
function getBlockingDeps(id, db) {
|
|
14871
14930
|
const d = db || getDatabase();
|
|
@@ -14936,7 +14995,7 @@ function completeTask(id, agentId, db, options) {
|
|
|
14936
14995
|
if (task.status === "cancelled") {
|
|
14937
14996
|
throw new Error(`Task ${id} is cancelled and cannot be completed`);
|
|
14938
14997
|
}
|
|
14939
|
-
if (
|
|
14998
|
+
if (task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
|
|
14940
14999
|
throw new LockError(id, task.locked_by);
|
|
14941
15000
|
}
|
|
14942
15001
|
checkCompletionGuard(task, agentId || null, d);
|
|
@@ -15571,6 +15630,30 @@ function getTaskWithRelations(id, db) {
|
|
|
15571
15630
|
checklist
|
|
15572
15631
|
};
|
|
15573
15632
|
}
|
|
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
|
+
}
|
|
15574
15657
|
function listTasks(filter = {}, db) {
|
|
15575
15658
|
const d = db || getDatabase();
|
|
15576
15659
|
const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
|
|
@@ -15612,8 +15695,7 @@ function listTasks(filter = {}, db) {
|
|
|
15612
15695
|
}
|
|
15613
15696
|
}
|
|
15614
15697
|
if (filter.assigned_to) {
|
|
15615
|
-
conditions.push("assigned_to
|
|
15616
|
-
params.push(filter.assigned_to);
|
|
15698
|
+
conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, filter.assigned_to), params));
|
|
15617
15699
|
}
|
|
15618
15700
|
if (filter.agent_id) {
|
|
15619
15701
|
conditions.push("agent_id = ?");
|
|
@@ -15773,8 +15855,7 @@ function countTasks(filter = {}, db) {
|
|
|
15773
15855
|
}
|
|
15774
15856
|
}
|
|
15775
15857
|
if (filter.assigned_to) {
|
|
15776
|
-
conditions.push("assigned_to
|
|
15777
|
-
params.push(filter.assigned_to);
|
|
15858
|
+
conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, filter.assigned_to), params));
|
|
15778
15859
|
}
|
|
15779
15860
|
if (filter.agent_id) {
|
|
15780
15861
|
conditions.push("agent_id = ?");
|
|
@@ -19009,10 +19090,11 @@ function validateAssignee(input, ctx) {
|
|
|
19009
19090
|
if (ctx.allowSeat) {
|
|
19010
19091
|
return { ok: true, assignee: byId ? byId.name : raw, agentId: byId?.id, isSeat: true };
|
|
19011
19092
|
}
|
|
19093
|
+
const hint = ctx.seatHint?.(raw);
|
|
19012
19094
|
return {
|
|
19013
19095
|
ok: false,
|
|
19014
19096
|
reason: "seat",
|
|
19015
|
-
message: `'${raw}' is a durable SEAT, and a task assigned to a seat is assigned to nobody \u2014 no session is watching that queue. ` + `Assign a specific agent, use --unassigned to file it with no owner on purpose, or
|
|
19097
|
+
message: `'${raw}' is a durable SEAT, and a task assigned to a seat is assigned to nobody \u2014 no session is watching that queue. ` + `Assign a specific agent, use --unassigned to file it with no owner on purpose, or repeat the command with --assign-seat added to confirm the seat is deliberate` + (hint ? `: ${hint}` : " (see --help for this command's exact form).")
|
|
19016
19098
|
};
|
|
19017
19099
|
}
|
|
19018
19100
|
if (byId) {
|
|
@@ -19740,10 +19822,10 @@ var init_agents = __esm(() => {
|
|
|
19740
19822
|
|
|
19741
19823
|
// src/cli/assignee-guard.ts
|
|
19742
19824
|
import chalk2 from "chalk";
|
|
19743
|
-
async function resolveValidatedAssignee(value, allowSeat) {
|
|
19825
|
+
async function resolveValidatedAssignee(value, allowSeat, buildSeatHint) {
|
|
19744
19826
|
const cloud = getTodosCloudClient();
|
|
19745
19827
|
const ctx = await loadAssigneeContext(() => cloud ? cloudListAgents(cloud) : listAgents(), allowSeat);
|
|
19746
|
-
const verdict = validateAssignee(value, ctx);
|
|
19828
|
+
const verdict = validateAssignee(value, { ...ctx, seatHint: buildSeatHint });
|
|
19747
19829
|
if (!verdict.ok) {
|
|
19748
19830
|
handleError(new Error(`Cannot assign to '${value}'. ${verdict.message}`));
|
|
19749
19831
|
}
|
|
@@ -20263,7 +20345,7 @@ function registerTaskCommands(program2) {
|
|
|
20263
20345
|
opts.list = opts.list || opts.taskList;
|
|
20264
20346
|
const creator = resolveCreatorIdentity(opts.createdBy || globalOpts.agent);
|
|
20265
20347
|
const router = resolveWritableIdentity(opts.createdBy || globalOpts.agent);
|
|
20266
|
-
const requestedAssign = opts.assign ? await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat)) : undefined;
|
|
20348
|
+
const requestedAssign = opts.assign ? await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`) : undefined;
|
|
20267
20349
|
const assignee = requestedAssign || (opts.unassigned ? undefined : router.agent_id || undefined);
|
|
20268
20350
|
if (!assignee && !opts.unassigned) {
|
|
20269
20351
|
console.error(chalk3.yellow("Warning: task is ownerless and unattributable \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
|
|
@@ -20366,7 +20448,7 @@ function registerTaskCommands(program2) {
|
|
|
20366
20448
|
opts.tags = opts.tags || opts.tag;
|
|
20367
20449
|
opts.list = opts.list || opts.taskList;
|
|
20368
20450
|
if (opts.assign) {
|
|
20369
|
-
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat));
|
|
20451
|
+
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`);
|
|
20370
20452
|
}
|
|
20371
20453
|
const explicitProject = opts.project || globalOpts.project;
|
|
20372
20454
|
const cloud = getTodosCloudClient();
|
|
@@ -20736,8 +20818,11 @@ function registerTaskCommands(program2) {
|
|
|
20736
20818
|
console.log(` ${chalk3.dim("Agent:")} ${task2.agent_id}`);
|
|
20737
20819
|
if (task2.session_id)
|
|
20738
20820
|
console.log(` ${chalk3.dim("Session:")} ${task2.session_id}`);
|
|
20739
|
-
|
|
20740
|
-
|
|
20821
|
+
const showLock = lockDisplayState(task2.locked_by, task2.locked_at);
|
|
20822
|
+
if (showLock.held)
|
|
20823
|
+
console.log(` ${chalk3.dim("Locked:")} ${showLock.holder} (at ${showLock.lockedAt})`);
|
|
20824
|
+
else if (showLock.expired)
|
|
20825
|
+
console.log(` ${chalk3.dim("Lock:")} ${chalk3.dim(formatExpiredLock(showLock))}`);
|
|
20741
20826
|
if (task2.requires_approval) {
|
|
20742
20827
|
const approvalStatus = task2.approved_by ? chalk3.green(`approved by ${task2.approved_by}`) : chalk3.yellow("pending approval");
|
|
20743
20828
|
console.log(` ${chalk3.dim("Approval:")} ${approvalStatus}`);
|
|
@@ -20880,8 +20965,11 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
20880
20965
|
}
|
|
20881
20966
|
if (task2.assigned_to)
|
|
20882
20967
|
console.log(` ${chalk3.dim("Assigned:")} ${task2.assigned_to}`);
|
|
20883
|
-
|
|
20884
|
-
|
|
20968
|
+
const inspectLock = lockDisplayState(task2.locked_by, task2.locked_at);
|
|
20969
|
+
if (inspectLock.held)
|
|
20970
|
+
console.log(` ${chalk3.dim("Locked by:")} ${inspectLock.holder}`);
|
|
20971
|
+
else if (inspectLock.expired)
|
|
20972
|
+
console.log(` ${chalk3.dim("Lock:")} ${chalk3.dim(formatExpiredLock(inspectLock))}`);
|
|
20885
20973
|
if (task2.project_id)
|
|
20886
20974
|
console.log(` ${chalk3.dim("Project:")} ${task2.project_id}`);
|
|
20887
20975
|
if (task2.plan_id)
|
|
@@ -21021,7 +21109,7 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21021
21109
|
handleError(new Error("Use either --working-dir or --clear-working-dir, not both."));
|
|
21022
21110
|
}
|
|
21023
21111
|
if (opts.assign) {
|
|
21024
|
-
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat));
|
|
21112
|
+
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`);
|
|
21025
21113
|
}
|
|
21026
21114
|
const cloud = getTodosCloudClient();
|
|
21027
21115
|
if (cloud) {
|
|
@@ -21189,10 +21277,12 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21189
21277
|
};
|
|
21190
21278
|
const cloud = getTodosCloudClient();
|
|
21191
21279
|
if (cloud) {
|
|
21280
|
+
const resolvedId2 = await resolveTaskIdForCommand(id, cloud);
|
|
21281
|
+
const agentId2 = resolveClaimIdentity("complete", globalOpts.agent);
|
|
21192
21282
|
let task3;
|
|
21193
21283
|
try {
|
|
21194
|
-
task3 = await cloudCompleteTask(cloud,
|
|
21195
|
-
|
|
21284
|
+
task3 = await cloudCompleteTask(cloud, resolvedId2, {
|
|
21285
|
+
agent_id: agentId2,
|
|
21196
21286
|
...completionOptions
|
|
21197
21287
|
});
|
|
21198
21288
|
} catch (e) {
|
|
@@ -21207,9 +21297,10 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21207
21297
|
return;
|
|
21208
21298
|
}
|
|
21209
21299
|
const resolvedId = resolveTaskId(id);
|
|
21300
|
+
const agentId = resolveClaimIdentity("complete", globalOpts.agent);
|
|
21210
21301
|
let task2;
|
|
21211
21302
|
try {
|
|
21212
|
-
task2 = completeTask(resolvedId,
|
|
21303
|
+
task2 = completeTask(resolvedId, agentId, undefined, completionOptions);
|
|
21213
21304
|
} catch (e) {
|
|
21214
21305
|
handleError(e);
|
|
21215
21306
|
}
|
|
@@ -21492,6 +21583,7 @@ var init_task_commands = __esm(() => {
|
|
|
21492
21583
|
init_task_lists();
|
|
21493
21584
|
init_cloud_router();
|
|
21494
21585
|
init_creator_identity();
|
|
21586
|
+
init_lock_display();
|
|
21495
21587
|
init_claim_guard();
|
|
21496
21588
|
init_assignee_guard();
|
|
21497
21589
|
init_helpers();
|
|
@@ -26176,6 +26268,12 @@ function countProjectTasks(projectId) {
|
|
|
26176
26268
|
FROM tasks
|
|
26177
26269
|
WHERE project_id = ?`).get(projectId);
|
|
26178
26270
|
}
|
|
26271
|
+
function countTasksForDeregistration(tasks) {
|
|
26272
|
+
return {
|
|
26273
|
+
total: tasks.length,
|
|
26274
|
+
incomplete: tasks.filter((task) => task.status !== "completed" && task.status !== "cancelled").length
|
|
26275
|
+
};
|
|
26276
|
+
}
|
|
26179
26277
|
function pathIsWithinPrefix(projectPath, prefix) {
|
|
26180
26278
|
const normalizedPath = resolve13(projectPath);
|
|
26181
26279
|
const normalizedPrefix = resolve13(prefix);
|
|
@@ -26651,11 +26749,8 @@ function registerProjectCommands(program2) {
|
|
|
26651
26749
|
return;
|
|
26652
26750
|
}
|
|
26653
26751
|
if (opts.deregister) {
|
|
26654
|
-
|
|
26655
|
-
|
|
26656
|
-
}
|
|
26657
|
-
const project = resolveExplicitProject(opts.deregister);
|
|
26658
|
-
const counts = countProjectTasks(project.id);
|
|
26752
|
+
const project = cloud ? await cloudResolveProject(cloud, opts.deregister) : resolveExplicitProject(opts.deregister);
|
|
26753
|
+
const counts = cloud ? countTasksForDeregistration(await cloudListTasks(cloud, { project_id: project.id, include_subtasks: true })) : countProjectTasks(project.id);
|
|
26659
26754
|
if (opts.pathPrefix && !pathIsWithinPrefix(project.path, opts.pathPrefix)) {
|
|
26660
26755
|
handleError(new Error(`Refusing to deregister ${project.name}: path ${project.path} is not within ${opts.pathPrefix}`));
|
|
26661
26756
|
}
|
|
@@ -26672,7 +26767,10 @@ function registerProjectCommands(program2) {
|
|
|
26672
26767
|
tasks_preserved: true
|
|
26673
26768
|
};
|
|
26674
26769
|
if (!opts.dryRun) {
|
|
26675
|
-
|
|
26770
|
+
if (cloud)
|
|
26771
|
+
await cloudDeleteProject(cloud, project.id);
|
|
26772
|
+
else
|
|
26773
|
+
deleteProject(project.id);
|
|
26676
26774
|
}
|
|
26677
26775
|
if (globalOpts.json) {
|
|
26678
26776
|
output(result, true);
|
|
@@ -31438,7 +31536,16 @@ class PostgresJsonRecordStore {
|
|
|
31438
31536
|
updatedAt: stringValue2(row.updated_at) ?? new Date().toISOString()
|
|
31439
31537
|
}));
|
|
31440
31538
|
}
|
|
31441
|
-
|
|
31539
|
+
async resolveAssignedToAliases(ref) {
|
|
31540
|
+
const agent = await resolveAgentForAssignedFilter(ref, this);
|
|
31541
|
+
const aliases = new Set([ref]);
|
|
31542
|
+
if (agent) {
|
|
31543
|
+
aliases.add(agent.id);
|
|
31544
|
+
aliases.add(agent.name);
|
|
31545
|
+
}
|
|
31546
|
+
return [...aliases];
|
|
31547
|
+
}
|
|
31548
|
+
async buildTaskFilterSql(filter) {
|
|
31442
31549
|
const params = [this.service, "tasks"];
|
|
31443
31550
|
const conds = ["service = $1", "object_type = $2", "deleted_at IS NULL"];
|
|
31444
31551
|
const p = (value) => {
|
|
@@ -31464,8 +31571,10 @@ class PostgresJsonRecordStore {
|
|
|
31464
31571
|
conds.push(inClause("payload->>'status'", toFilterArray(filter.status)));
|
|
31465
31572
|
if (filter.priority !== undefined)
|
|
31466
31573
|
conds.push(inClause("payload->>'priority'", toFilterArray(filter.priority)));
|
|
31467
|
-
if (filter.assigned_to !== undefined)
|
|
31468
|
-
|
|
31574
|
+
if (filter.assigned_to !== undefined) {
|
|
31575
|
+
const aliases = [...new Set((await this.resolveAssignedToAliases(filter.assigned_to)).map((a) => a.toLowerCase()))];
|
|
31576
|
+
conds.push(inClause("LOWER(payload->>'assigned_to')", aliases));
|
|
31577
|
+
}
|
|
31469
31578
|
if (filter.agent_id !== undefined)
|
|
31470
31579
|
conds.push(`payload->>'agent_id' = ${p(filter.agent_id)}`);
|
|
31471
31580
|
if (filter.created_by !== undefined)
|
|
@@ -31499,7 +31608,7 @@ class PostgresJsonRecordStore {
|
|
|
31499
31608
|
}
|
|
31500
31609
|
async listTasks(filter) {
|
|
31501
31610
|
await this.ensureSchema();
|
|
31502
|
-
const { where, params, queryRef } = this.buildTaskFilterSql(filter);
|
|
31611
|
+
const { where, params, queryRef } = await this.buildTaskFilterSql(filter);
|
|
31503
31612
|
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;
|
|
31504
31613
|
let sql = `/* todos:list-tasks */ SELECT payload FROM ${this.tableName} WHERE ${where} ${orderBy}`;
|
|
31505
31614
|
if (filter.limit !== undefined) {
|
|
@@ -31559,7 +31668,7 @@ class PostgresJsonRecordStore {
|
|
|
31559
31668
|
}
|
|
31560
31669
|
async countTasks(filter) {
|
|
31561
31670
|
await this.ensureSchema();
|
|
31562
|
-
const { where, params } = this.buildTaskFilterSql(filter);
|
|
31671
|
+
const { where, params } = await this.buildTaskFilterSql(filter);
|
|
31563
31672
|
const sql = `/* todos:count-tasks */ SELECT COUNT(*)::int AS count FROM ${this.tableName} WHERE ${where}`;
|
|
31564
31673
|
const result = await this.options.client.query(sql, params);
|
|
31565
31674
|
return Number(result.rows[0]?.count ?? 0);
|
|
@@ -31720,9 +31829,13 @@ class PostgresJsonRecordStore {
|
|
|
31720
31829
|
} : {};
|
|
31721
31830
|
const hasEvidence = Object.keys(evidence).length > 0;
|
|
31722
31831
|
const hasConfidence = options?.confidence !== undefined;
|
|
31723
|
-
const
|
|
31832
|
+
const lockExpiryCutoff2 = new Date(new Date(operationTimestamp).getTime() - CLOUD_LOCK_EXPIRY_MINUTES * 60 * 1000).toISOString();
|
|
31833
|
+
const result = await this.options.client.query(`/* todos:complete-task-atomic todos:complete-task-lock-guard todos:complete-task-clears-lock */
|
|
31834
|
+
UPDATE ${this.tableName}
|
|
31724
31835
|
SET payload = payload || jsonb_build_object(
|
|
31725
31836
|
'status', 'completed',
|
|
31837
|
+
'locked_by', 'null'::jsonb,
|
|
31838
|
+
'locked_at', 'null'::jsonb,
|
|
31726
31839
|
'assigned_to', CASE
|
|
31727
31840
|
WHEN jsonb_typeof(payload->'assigned_to') = 'string' THEN payload->'assigned_to'
|
|
31728
31841
|
ELSE COALESCE(to_jsonb($3::text), 'null'::jsonb)
|
|
@@ -31750,6 +31863,16 @@ class PostgresJsonRecordStore {
|
|
|
31750
31863
|
updated_at = $9::timestamptz,
|
|
31751
31864
|
version = COALESCE(version, 0) + 1
|
|
31752
31865
|
WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL
|
|
31866
|
+
AND (
|
|
31867
|
+
payload->>'locked_by' IS NULL
|
|
31868
|
+
OR BTRIM(payload->>'locked_by') = ''
|
|
31869
|
+
OR payload->>'locked_at' IS NULL
|
|
31870
|
+
OR payload->>'locked_at' < $10::text
|
|
31871
|
+
OR (
|
|
31872
|
+
$3::text IS NOT NULL
|
|
31873
|
+
AND LOWER(BTRIM(payload->>'locked_by')) = LOWER(BTRIM($3::text))
|
|
31874
|
+
)
|
|
31875
|
+
)
|
|
31753
31876
|
RETURNING payload`, [
|
|
31754
31877
|
this.service,
|
|
31755
31878
|
id,
|
|
@@ -31759,7 +31882,8 @@ class PostgresJsonRecordStore {
|
|
|
31759
31882
|
jsonbParam(evidence),
|
|
31760
31883
|
hasConfidence,
|
|
31761
31884
|
options?.confidence ?? null,
|
|
31762
|
-
operationTimestamp
|
|
31885
|
+
operationTimestamp,
|
|
31886
|
+
lockExpiryCutoff2
|
|
31763
31887
|
]);
|
|
31764
31888
|
return result.rows[0] ? payloadRecord2(result.rows[0].payload) : null;
|
|
31765
31889
|
}
|
|
@@ -32040,6 +32164,7 @@ async function updateTask2(id, input, store) {
|
|
|
32040
32164
|
if (existing.version !== input.version) {
|
|
32041
32165
|
throw new Error(`Task ${id} version conflict: expected ${existing.version}, got ${input.version}`);
|
|
32042
32166
|
}
|
|
32167
|
+
const reopened = existing.status === "completed" && input.status !== undefined && input.status !== "completed" && input.completed_at === undefined;
|
|
32043
32168
|
const task = {
|
|
32044
32169
|
...existing,
|
|
32045
32170
|
...definedPatch(input),
|
|
@@ -32049,7 +32174,8 @@ async function updateTask2(id, input, store) {
|
|
|
32049
32174
|
metadata: input.metadata ?? existing.metadata,
|
|
32050
32175
|
requires_approval: input.requires_approval ?? existing.requires_approval,
|
|
32051
32176
|
task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
|
|
32052
|
-
created_by: existing.created_by
|
|
32177
|
+
created_by: existing.created_by,
|
|
32178
|
+
completed_at: reopened ? null : input.completed_at !== undefined ? input.completed_at : existing.completed_at
|
|
32053
32179
|
};
|
|
32054
32180
|
await store.upsert("tasks", task);
|
|
32055
32181
|
return task;
|
|
@@ -32057,11 +32183,11 @@ async function updateTask2(id, input, store) {
|
|
|
32057
32183
|
async function startTask2(id, agentId, store) {
|
|
32058
32184
|
const task = await requireRecord("tasks", id, store);
|
|
32059
32185
|
if (task.status !== "pending" && task.status !== "in_progress") {
|
|
32060
|
-
throw new
|
|
32186
|
+
throw new TaskNotStartableError(task.id, task.status, agentId);
|
|
32061
32187
|
}
|
|
32062
32188
|
const started = await patchTask(task, {
|
|
32063
32189
|
status: "in_progress",
|
|
32064
|
-
assigned_to: task.assigned_to
|
|
32190
|
+
assigned_to: task.assigned_to || agentId,
|
|
32065
32191
|
agent_id: task.agent_id ?? agentId,
|
|
32066
32192
|
locked_by: agentId,
|
|
32067
32193
|
locked_at: new Date().toISOString(),
|
|
@@ -32072,8 +32198,13 @@ async function startTask2(id, agentId, store) {
|
|
|
32072
32198
|
}
|
|
32073
32199
|
async function completeTask2(id, agentId, options, store) {
|
|
32074
32200
|
const task = await store.completeTask(id, agentId, options);
|
|
32075
|
-
if (!task)
|
|
32201
|
+
if (!task) {
|
|
32202
|
+
const current = await store.get("tasks", id);
|
|
32203
|
+
if (current?.locked_by && !cloudLockExpired(current.locked_at) && !sameCloudLockHolder(current.locked_by, agentId)) {
|
|
32204
|
+
throw new LockError(id, current.locked_by);
|
|
32205
|
+
}
|
|
32076
32206
|
throw new Error(`tasks record not found: ${id}`);
|
|
32207
|
+
}
|
|
32077
32208
|
return task;
|
|
32078
32209
|
}
|
|
32079
32210
|
async function failTask2(id, agentId, reason, options, store) {
|
|
@@ -32114,6 +32245,11 @@ async function patchTask(task, patch, store) {
|
|
|
32114
32245
|
await store.upsert("tasks", updated);
|
|
32115
32246
|
return updated;
|
|
32116
32247
|
}
|
|
32248
|
+
function sameCloudLockHolder(stored, incoming) {
|
|
32249
|
+
if (!stored || !incoming)
|
|
32250
|
+
return false;
|
|
32251
|
+
return canonicalAgentRef(stored) === canonicalAgentRef(incoming);
|
|
32252
|
+
}
|
|
32117
32253
|
function cloudLockExpired(lockedAt) {
|
|
32118
32254
|
if (!lockedAt)
|
|
32119
32255
|
return true;
|
|
@@ -32431,6 +32567,16 @@ async function resolveAgent(idOrName, store) {
|
|
|
32431
32567
|
return byId;
|
|
32432
32568
|
return matchAgentByName(await store.list("agents"), idOrName);
|
|
32433
32569
|
}
|
|
32570
|
+
async function resolveAgentForAssignedFilter(idOrName, store) {
|
|
32571
|
+
const byId = await store.get("agents", idOrName);
|
|
32572
|
+
if (byId)
|
|
32573
|
+
return byId;
|
|
32574
|
+
const target = normalizeAgentNameInput(idOrName);
|
|
32575
|
+
if (!target)
|
|
32576
|
+
return null;
|
|
32577
|
+
const matches = (await store.list("agents")).filter((agent) => normalizeAgentNameInput(agent.name) === target);
|
|
32578
|
+
return matches.length === 1 ? matches[0] : null;
|
|
32579
|
+
}
|
|
32434
32580
|
async function heartbeatAgent(idOrName, store, context) {
|
|
32435
32581
|
const agent = await resolveAgent(idOrName, store);
|
|
32436
32582
|
if (!agent)
|
|
@@ -32746,6 +32892,7 @@ function postgresConstraintName(error) {
|
|
|
32746
32892
|
var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_TIEBREAK = "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", TASK_ORDER_BY;
|
|
32747
32893
|
var init_postgres_adapter = __esm(() => {
|
|
32748
32894
|
init_types();
|
|
32895
|
+
init_creator_identity();
|
|
32749
32896
|
init_postgres_sync();
|
|
32750
32897
|
init_integrity();
|
|
32751
32898
|
init_redaction();
|
|
@@ -34235,7 +34382,10 @@ function mapTaskError(e, json2) {
|
|
|
34235
34382
|
retry_after: e.retryAfterSeconds ?? null
|
|
34236
34383
|
}, 409);
|
|
34237
34384
|
}
|
|
34238
|
-
if (e instanceof
|
|
34385
|
+
if (e instanceof TaskNotStartableError) {
|
|
34386
|
+
return json2({ error: e.message, code: TaskNotStartableError.code }, 409);
|
|
34387
|
+
}
|
|
34388
|
+
if (e instanceof Error && / is blocked by /.test(e.message)) {
|
|
34239
34389
|
return json2({ error: e.message, code: "TASK_NOT_STARTABLE" }, 409);
|
|
34240
34390
|
}
|
|
34241
34391
|
return null;
|
|
@@ -38800,6 +38950,9 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
38800
38950
|
}
|
|
38801
38951
|
if (e instanceof LockError)
|
|
38802
38952
|
return error(409, e.message, { code: LockError.code });
|
|
38953
|
+
if (e instanceof TaskNotStartableError) {
|
|
38954
|
+
return error(409, e.message, { code: TaskNotStartableError.code });
|
|
38955
|
+
}
|
|
38803
38956
|
if (e instanceof ResourceConflictError)
|
|
38804
38957
|
return error(409, e.message, { code: e.code, conflict: true });
|
|
38805
38958
|
if (e instanceof ProjectNotFoundError)
|
|
@@ -62408,6 +62561,8 @@ var init_workflow_prompts2 = __esm(() => {
|
|
|
62408
62561
|
// src/mcp/index.ts
|
|
62409
62562
|
var exports_mcp = {};
|
|
62410
62563
|
__export(exports_mcp, {
|
|
62564
|
+
formatTaskDetail: () => formatTaskDetail,
|
|
62565
|
+
formatTask: () => formatTask,
|
|
62411
62566
|
buildServer: () => buildServer,
|
|
62412
62567
|
applyFocus: () => applyFocus
|
|
62413
62568
|
});
|
|
@@ -62556,7 +62711,8 @@ function resolveId(partialId, table = "tasks") {
|
|
|
62556
62711
|
function formatTask(task2) {
|
|
62557
62712
|
const id = task2.short_id || task2.id.slice(0, 8);
|
|
62558
62713
|
const assigned = task2.assigned_to ? ` -> ${task2.assigned_to}` : "";
|
|
62559
|
-
const
|
|
62714
|
+
const lockState = lockDisplayState(task2.locked_by, task2.locked_at);
|
|
62715
|
+
const lock = lockState.held ? ` [locked:${lockState.holder}]` : "";
|
|
62560
62716
|
const recur = task2.recurrence_rule ? ` [\u21BB]` : "";
|
|
62561
62717
|
return `${id} ${task2.status.padEnd(11)} ${task2.priority.padEnd(8)} ${task2.title}${assigned}${lock}${recur}`;
|
|
62562
62718
|
}
|
|
@@ -62575,8 +62731,11 @@ function formatTaskDetail(task2, maxDescriptionChars) {
|
|
|
62575
62731
|
parts.push(`Assigned to: ${task2.assigned_to}`);
|
|
62576
62732
|
if (task2.agent_id)
|
|
62577
62733
|
parts.push(`Agent: ${task2.agent_id}`);
|
|
62578
|
-
|
|
62579
|
-
|
|
62734
|
+
const detailLock = lockDisplayState(task2.locked_by, task2.locked_at);
|
|
62735
|
+
if (detailLock.held)
|
|
62736
|
+
parts.push(`Locked by: ${detailLock.holder}`);
|
|
62737
|
+
else if (detailLock.expired)
|
|
62738
|
+
parts.push(`Lock: ${formatExpiredLock(detailLock)}`);
|
|
62580
62739
|
if (task2.parent_id)
|
|
62581
62740
|
parts.push(`Parent: ${task2.parent_id}`);
|
|
62582
62741
|
if (task2.project_id)
|
|
@@ -62660,6 +62819,7 @@ var agentFocusMap, isDirectRun;
|
|
|
62660
62819
|
var init_mcp2 = __esm(() => {
|
|
62661
62820
|
init_agents();
|
|
62662
62821
|
init_database();
|
|
62822
|
+
init_lock_display();
|
|
62663
62823
|
init_types();
|
|
62664
62824
|
init_dispatch2();
|
|
62665
62825
|
init_task_crud2();
|
|
@@ -63705,7 +63865,7 @@ function TaskList({ tasks, selectedIndex }) {
|
|
|
63705
63865
|
task2.assigned_to
|
|
63706
63866
|
]
|
|
63707
63867
|
}, undefined, true, undefined, this),
|
|
63708
|
-
task2.locked_by && /* @__PURE__ */ jsxDEV2(Text2, {
|
|
63868
|
+
lockDisplayState(task2.locked_by, task2.locked_at).held && /* @__PURE__ */ jsxDEV2(Text2, {
|
|
63709
63869
|
color: "magenta",
|
|
63710
63870
|
children: [
|
|
63711
63871
|
" \uD83D\uDD12",
|
|
@@ -63727,6 +63887,7 @@ function TaskList({ tasks, selectedIndex }) {
|
|
|
63727
63887
|
}
|
|
63728
63888
|
var statusIcons, statusColors2, priorityLabels, priorityColors2;
|
|
63729
63889
|
var init_TaskList = __esm(() => {
|
|
63890
|
+
init_lock_display();
|
|
63730
63891
|
statusIcons = {
|
|
63731
63892
|
pending: "\u25CB",
|
|
63732
63893
|
in_progress: "\u25D0",
|
|
@@ -63889,7 +64050,7 @@ function TaskDetail({ task: task2 }) {
|
|
|
63889
64050
|
}, undefined, false, undefined, this)
|
|
63890
64051
|
]
|
|
63891
64052
|
}, undefined, true, undefined, this),
|
|
63892
|
-
task2.locked_by && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
64053
|
+
lockDisplayState(task2.locked_by, task2.locked_at).held && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
63893
64054
|
children: [
|
|
63894
64055
|
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
63895
64056
|
dimColor: true,
|
|
@@ -63906,6 +64067,18 @@ function TaskDetail({ task: task2 }) {
|
|
|
63906
64067
|
}, undefined, true, undefined, this)
|
|
63907
64068
|
]
|
|
63908
64069
|
}, undefined, true, undefined, this),
|
|
64070
|
+
lockDisplayState(task2.locked_by, task2.locked_at).expired && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
64071
|
+
children: [
|
|
64072
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
64073
|
+
dimColor: true,
|
|
64074
|
+
children: "Lock: "
|
|
64075
|
+
}, undefined, false, undefined, this),
|
|
64076
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
64077
|
+
dimColor: true,
|
|
64078
|
+
children: formatExpiredLock(lockDisplayState(task2.locked_by, task2.locked_at))
|
|
64079
|
+
}, undefined, false, undefined, this)
|
|
64080
|
+
]
|
|
64081
|
+
}, undefined, true, undefined, this),
|
|
63909
64082
|
task2.tags.length > 0 && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
63910
64083
|
children: [
|
|
63911
64084
|
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
@@ -64108,6 +64281,7 @@ function TaskDetail({ task: task2 }) {
|
|
|
64108
64281
|
}
|
|
64109
64282
|
var statusColors3;
|
|
64110
64283
|
var init_TaskDetail = __esm(() => {
|
|
64284
|
+
init_lock_display();
|
|
64111
64285
|
statusColors3 = {
|
|
64112
64286
|
pending: "yellow",
|
|
64113
64287
|
in_progress: "blue",
|
|
@@ -68182,7 +68356,7 @@ No task claimed (nothing available).`));
|
|
|
68182
68356
|
});
|
|
68183
68357
|
program2.command("assign <id> <agent>").description("Assign a task to an agent").option("-j, --json", "Output as JSON").option("--assign-seat", "Allow <agent> to name a durable seat (a seat queue has no session watching it)").action(async (id, agentInput, opts) => {
|
|
68184
68358
|
const globalOpts = program2.opts();
|
|
68185
|
-
const agent = await resolveValidatedAssignee(agentInput, Boolean(opts.assignSeat));
|
|
68359
|
+
const agent = await resolveValidatedAssignee(agentInput, Boolean(opts.assignSeat), (v) => `${id} ${v} --assign-seat`);
|
|
68186
68360
|
const cloud = getTodosCloudClient();
|
|
68187
68361
|
if (cloud) {
|
|
68188
68362
|
try {
|