@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/cli/cloud-router.d.ts +190 -1
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/mcp-hooks-commands.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/index.js +754 -84
- package/dist/index.js +114 -4
- package/dist/mcp/index.js +271 -5
- package/dist/mcp/tools/agents.d.ts.map +1 -1
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +289 -23
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +58 -1
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage.js +111 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -8580,9 +8580,277 @@ async function cloudCountTasks(client, filter = {}) {
|
|
|
8580
8580
|
const tasks = await cloudListTasks(client, rest);
|
|
8581
8581
|
return tasks.length;
|
|
8582
8582
|
}
|
|
8583
|
-
|
|
8583
|
+
async function cloudRegisterAgent(client, input) {
|
|
8584
|
+
const raw = await client.transport.post("/agents", input);
|
|
8585
|
+
if (raw && typeof raw === "object" && "agent" in raw) {
|
|
8586
|
+
return raw.agent;
|
|
8587
|
+
}
|
|
8588
|
+
return raw;
|
|
8589
|
+
}
|
|
8590
|
+
async function cloudLockTask(client, id, agentId) {
|
|
8591
|
+
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/lock`, { agent_id: agentId });
|
|
8592
|
+
if (raw && typeof raw === "object" && "result" in raw) {
|
|
8593
|
+
return raw.result;
|
|
8594
|
+
}
|
|
8595
|
+
return raw ?? { success: true };
|
|
8596
|
+
}
|
|
8597
|
+
async function cloudUnlockTask(client, id, agentId) {
|
|
8598
|
+
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/unlock`, agentId ? { agent_id: agentId } : {});
|
|
8599
|
+
if (raw && typeof raw === "object" && "success" in raw) {
|
|
8600
|
+
return Boolean(raw.success);
|
|
8601
|
+
}
|
|
8602
|
+
return true;
|
|
8603
|
+
}
|
|
8604
|
+
async function cloudGetDependencies(client, id) {
|
|
8605
|
+
const raw = await client.transport.get(`/tasks/${encodeURIComponent(id)}/dependencies`);
|
|
8606
|
+
const env = raw ?? {};
|
|
8607
|
+
return { dependencies: env.dependencies ?? [], blocked_by: env.blocked_by ?? [] };
|
|
8608
|
+
}
|
|
8609
|
+
async function cloudAddDependency(client, id, dependsOn) {
|
|
8610
|
+
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/dependencies`, { depends_on: dependsOn });
|
|
8611
|
+
if (raw && typeof raw === "object" && "dependency" in raw) {
|
|
8612
|
+
return raw.dependency;
|
|
8613
|
+
}
|
|
8614
|
+
return raw;
|
|
8615
|
+
}
|
|
8616
|
+
async function cloudRemoveDependency(client, id, dependsOn) {
|
|
8617
|
+
const raw = await client.transport.del(`/tasks/${encodeURIComponent(id)}/dependencies/${encodeURIComponent(dependsOn)}`);
|
|
8618
|
+
if (raw && typeof raw === "object" && "removed" in raw) {
|
|
8619
|
+
return Boolean(raw.removed);
|
|
8620
|
+
}
|
|
8621
|
+
return true;
|
|
8622
|
+
}
|
|
8623
|
+
async function cloudRecordVerification(client, id, input) {
|
|
8624
|
+
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/verifications`, input);
|
|
8625
|
+
if (raw && typeof raw === "object" && "verification" in raw) {
|
|
8626
|
+
return raw.verification;
|
|
8627
|
+
}
|
|
8628
|
+
return raw;
|
|
8629
|
+
}
|
|
8630
|
+
function priorityRank(priority) {
|
|
8631
|
+
return PRIORITY_RANK[priority ?? ""] ?? 4;
|
|
8632
|
+
}
|
|
8633
|
+
async function cloudActiveTasks(client, filter = {}) {
|
|
8634
|
+
const [pending, inProgress] = await Promise.all([
|
|
8635
|
+
cloudListTasks(client, { ...filter, status: "pending" }),
|
|
8636
|
+
cloudListTasks(client, { ...filter, status: "in_progress" })
|
|
8637
|
+
]);
|
|
8638
|
+
return [...pending, ...inProgress];
|
|
8639
|
+
}
|
|
8640
|
+
async function cloudActiveWork(client, filter = {}) {
|
|
8641
|
+
const tasks = await cloudListTasks(client, { ...filter, status: "in_progress" });
|
|
8642
|
+
return tasks.sort((a, b) => priorityRank(a.priority) - priorityRank(b.priority) || (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
|
|
8643
|
+
}
|
|
8644
|
+
async function cloudStaleTasks(client, minutes, filter = {}) {
|
|
8645
|
+
const cutoff = new Date(Date.now() - minutes * 60 * 1000).toISOString();
|
|
8646
|
+
const tasks = await cloudListTasks(client, { ...filter, status: "in_progress" });
|
|
8647
|
+
return tasks.filter((t) => (t.updated_at ?? "") < cutoff || t.locked_at != null && t.locked_at < cutoff).sort((a, b) => (a.updated_at ?? "").localeCompare(b.updated_at ?? ""));
|
|
8648
|
+
}
|
|
8649
|
+
async function cloudOverdueTasks(client, projectId, at = new Date) {
|
|
8650
|
+
const nowStr = at.toISOString();
|
|
8651
|
+
const filter = projectId ? { project_id: projectId } : {};
|
|
8652
|
+
const active = await cloudActiveTasks(client, filter);
|
|
8653
|
+
return active.filter((t) => !t.archived_at && t.due_at != null && t.due_at < nowStr).sort((a, b) => (a.due_at ?? "").localeCompare(b.due_at ?? ""));
|
|
8654
|
+
}
|
|
8655
|
+
async function cloudEscalatedTasks(client, opts = {}, at = new Date) {
|
|
8656
|
+
const nowMs = at.getTime();
|
|
8657
|
+
const filter = {};
|
|
8658
|
+
if (opts.project_id)
|
|
8659
|
+
filter.project_id = opts.project_id;
|
|
8660
|
+
const active = await cloudActiveTasks(client, filter);
|
|
8661
|
+
return active.filter((t) => !t.archived_at && (opts.agent_id ? t.assigned_to === opts.agent_id : true)).map((task) => {
|
|
8662
|
+
const reasons = [];
|
|
8663
|
+
const breachedTimes = [];
|
|
8664
|
+
if (task.due_at) {
|
|
8665
|
+
const dueMs = new Date(task.due_at).getTime();
|
|
8666
|
+
if (Number.isFinite(dueMs) && dueMs < nowMs) {
|
|
8667
|
+
reasons.push("overdue");
|
|
8668
|
+
breachedTimes.push(dueMs);
|
|
8669
|
+
}
|
|
8670
|
+
}
|
|
8671
|
+
if (task.sla_minutes != null) {
|
|
8672
|
+
const startMs = new Date(task.started_at ?? task.created_at).getTime();
|
|
8673
|
+
const breachedMs = startMs + task.sla_minutes * 60000;
|
|
8674
|
+
if (Number.isFinite(breachedMs) && breachedMs < nowMs) {
|
|
8675
|
+
reasons.push("sla_breached");
|
|
8676
|
+
breachedTimes.push(breachedMs);
|
|
8677
|
+
}
|
|
8678
|
+
}
|
|
8679
|
+
if (reasons.length === 0)
|
|
8680
|
+
return null;
|
|
8681
|
+
return { task, reasons, breached_at: new Date(Math.min(...breachedTimes)).toISOString() };
|
|
8682
|
+
}).filter((item) => item !== null).sort((a, b) => (a.task.due_at ?? "").localeCompare(b.task.due_at ?? "") || a.task.created_at.localeCompare(b.task.created_at));
|
|
8683
|
+
}
|
|
8684
|
+
async function cloudChangedSince(client, since, filter = {}) {
|
|
8685
|
+
const tasks = await cloudListTasks(client, filter);
|
|
8686
|
+
return tasks.filter((t) => (t.updated_at ?? "") > since).sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
|
|
8687
|
+
}
|
|
8688
|
+
async function cloudTaskStats(client, filter = {}) {
|
|
8689
|
+
const tasks = await cloudListTasks(client, filter);
|
|
8690
|
+
const by_status = {};
|
|
8691
|
+
const by_priority = {};
|
|
8692
|
+
const by_agent = {};
|
|
8693
|
+
for (const t of tasks) {
|
|
8694
|
+
by_status[t.status] = (by_status[t.status] ?? 0) + 1;
|
|
8695
|
+
by_priority[t.priority] = (by_priority[t.priority] ?? 0) + 1;
|
|
8696
|
+
const agent = t.assigned_to ?? t.agent_id ?? "unassigned";
|
|
8697
|
+
by_agent[agent] = (by_agent[agent] ?? 0) + 1;
|
|
8698
|
+
}
|
|
8699
|
+
const completed = by_status["completed"] ?? 0;
|
|
8700
|
+
return {
|
|
8701
|
+
total: tasks.length,
|
|
8702
|
+
by_status,
|
|
8703
|
+
by_priority,
|
|
8704
|
+
by_agent,
|
|
8705
|
+
completion_rate: tasks.length > 0 ? Math.round(completed / tasks.length * 100) : 0
|
|
8706
|
+
};
|
|
8707
|
+
}
|
|
8708
|
+
async function cloudRecentActivity(client, limit = 50) {
|
|
8709
|
+
const raw = await client.transport.get("/activity", { query: { limit } });
|
|
8710
|
+
const envelope = raw ?? {};
|
|
8711
|
+
if (Array.isArray(envelope.activity))
|
|
8712
|
+
return envelope.activity;
|
|
8713
|
+
if (Array.isArray(envelope.entries))
|
|
8714
|
+
return envelope.entries;
|
|
8715
|
+
return Array.isArray(raw) ? raw : [];
|
|
8716
|
+
}
|
|
8717
|
+
async function cloudListTaskLists(client, projectId) {
|
|
8718
|
+
const query = projectId ? { project_id: projectId } : {};
|
|
8719
|
+
const raw = await client.transport.get("/task-lists", { query });
|
|
8720
|
+
const envelope = raw ?? {};
|
|
8721
|
+
if (Array.isArray(envelope.task_lists))
|
|
8722
|
+
return envelope.task_lists;
|
|
8723
|
+
if (Array.isArray(envelope.taskLists))
|
|
8724
|
+
return envelope.taskLists;
|
|
8725
|
+
return Array.isArray(raw) ? raw : [];
|
|
8726
|
+
}
|
|
8727
|
+
async function cloudNextTask(client, agent, filters) {
|
|
8728
|
+
const query = {};
|
|
8729
|
+
if (agent)
|
|
8730
|
+
query["agent"] = agent;
|
|
8731
|
+
if (filters?.project_id)
|
|
8732
|
+
query["project_id"] = filters.project_id;
|
|
8733
|
+
if (filters?.task_list_id)
|
|
8734
|
+
query["task_list_id"] = filters.task_list_id;
|
|
8735
|
+
if (filters?.plan_id)
|
|
8736
|
+
query["plan_id"] = filters.plan_id;
|
|
8737
|
+
const raw = await client.transport.get("/next", { query });
|
|
8738
|
+
if (raw == null)
|
|
8739
|
+
return null;
|
|
8740
|
+
const task = unwrapTask(raw);
|
|
8741
|
+
return task && task.id ? task : null;
|
|
8742
|
+
}
|
|
8743
|
+
async function cloudAllDependencies(client) {
|
|
8744
|
+
const raw = await client.transport.get("/dependencies");
|
|
8745
|
+
const envelope = raw ?? {};
|
|
8746
|
+
if (Array.isArray(envelope.dependencies))
|
|
8747
|
+
return envelope.dependencies;
|
|
8748
|
+
return Array.isArray(raw) ? raw : [];
|
|
8749
|
+
}
|
|
8750
|
+
async function cloudGetTasksByIds(client, ids) {
|
|
8751
|
+
const unique = Array.from(new Set(ids));
|
|
8752
|
+
const map = new Map;
|
|
8753
|
+
const CONCURRENCY = 8;
|
|
8754
|
+
for (let i = 0;i < unique.length; i += CONCURRENCY) {
|
|
8755
|
+
const batch = unique.slice(i, i + CONCURRENCY);
|
|
8756
|
+
const tasks = await Promise.all(batch.map((id) => cloudGetTask(client, id)));
|
|
8757
|
+
for (const task of tasks)
|
|
8758
|
+
if (task && task.id)
|
|
8759
|
+
map.set(task.id, task);
|
|
8760
|
+
}
|
|
8761
|
+
return map;
|
|
8762
|
+
}
|
|
8763
|
+
async function cloudBlockingDepsMap(client, candidates) {
|
|
8764
|
+
const result = new Map;
|
|
8765
|
+
if (candidates.length === 0)
|
|
8766
|
+
return result;
|
|
8767
|
+
const edges = await cloudAllDependencies(client);
|
|
8768
|
+
const dependsByTask = new Map;
|
|
8769
|
+
for (const edge of edges) {
|
|
8770
|
+
if (!edge.task_id || !edge.depends_on)
|
|
8771
|
+
continue;
|
|
8772
|
+
const arr = dependsByTask.get(edge.task_id) ?? [];
|
|
8773
|
+
arr.push(edge.depends_on);
|
|
8774
|
+
dependsByTask.set(edge.task_id, arr);
|
|
8775
|
+
}
|
|
8776
|
+
const candidateIds = new Set(candidates.map((t) => t.id));
|
|
8777
|
+
const blockerIds = new Set;
|
|
8778
|
+
for (const id of candidateIds)
|
|
8779
|
+
for (const dep of dependsByTask.get(id) ?? [])
|
|
8780
|
+
blockerIds.add(dep);
|
|
8781
|
+
const blockers = await cloudGetTasksByIds(client, Array.from(blockerIds));
|
|
8782
|
+
for (const task of candidates) {
|
|
8783
|
+
const deps = dependsByTask.get(task.id) ?? [];
|
|
8784
|
+
const incomplete = deps.map((depId) => blockers.get(depId)).filter((b) => b != null && b.status !== "completed");
|
|
8785
|
+
if (incomplete.length > 0)
|
|
8786
|
+
result.set(task.id, incomplete);
|
|
8787
|
+
}
|
|
8788
|
+
return result;
|
|
8789
|
+
}
|
|
8790
|
+
async function cloudRecap(client, hours, projectId) {
|
|
8791
|
+
const since = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
|
|
8792
|
+
const staleWindow = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
8793
|
+
const filter = projectId ? { project_id: projectId } : {};
|
|
8794
|
+
const [all, agents] = await Promise.all([cloudListTasks(client, filter), cloudListAgents(client)]);
|
|
8795
|
+
const completed = all.filter((t) => t.status === "completed" && t.completed_at != null && t.completed_at > since).sort((a, b) => (b.completed_at ?? "").localeCompare(a.completed_at ?? "")).map((t) => ({
|
|
8796
|
+
...t,
|
|
8797
|
+
duration_minutes: t.started_at && t.completed_at ? Math.round((new Date(t.completed_at).getTime() - new Date(t.started_at).getTime()) / 60000) : null
|
|
8798
|
+
}));
|
|
8799
|
+
const created = all.filter((t) => t.created_at > since).sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
8800
|
+
const in_progress = all.filter((t) => t.status === "in_progress").sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
|
|
8801
|
+
const stale = in_progress.filter((t) => (t.updated_at ?? "") < staleWindow).sort((a, b) => (a.updated_at ?? "").localeCompare(b.updated_at ?? ""));
|
|
8802
|
+
const pending = all.filter((t) => t.status === "pending");
|
|
8803
|
+
const blockedMap = await cloudBlockingDepsMap(client, pending);
|
|
8804
|
+
const blocked = pending.filter((t) => blockedMap.has(t.id));
|
|
8805
|
+
const sinceMs = new Date(since).getTime();
|
|
8806
|
+
const agentSummaries = agents.map((agent) => {
|
|
8807
|
+
const owned = all.filter((t) => t.assigned_to === agent.id || t.agent_id === agent.id);
|
|
8808
|
+
return {
|
|
8809
|
+
name: agent.name,
|
|
8810
|
+
completed_count: owned.filter((t) => t.status === "completed" && t.completed_at != null && t.completed_at > since).length,
|
|
8811
|
+
in_progress_count: owned.filter((t) => t.status === "in_progress").length,
|
|
8812
|
+
last_seen_at: agent.last_seen_at
|
|
8813
|
+
};
|
|
8814
|
+
}).filter((a) => a.last_seen_at != null && new Date(a.last_seen_at).getTime() > sinceMs).sort((a, b) => b.completed_count - a.completed_count);
|
|
8815
|
+
return { hours, since, completed, created, in_progress, blocked, stale, agents: agentSummaries };
|
|
8816
|
+
}
|
|
8817
|
+
async function cloudTimeline(client, options = {}) {
|
|
8818
|
+
const activity = await cloudRecentActivity(client, 5000);
|
|
8819
|
+
let entries = activity.map((h) => ({
|
|
8820
|
+
id: h.id,
|
|
8821
|
+
source: "task_history",
|
|
8822
|
+
event_type: h.action,
|
|
8823
|
+
entity_type: "task",
|
|
8824
|
+
entity_id: h.task_id,
|
|
8825
|
+
task_id: h.task_id,
|
|
8826
|
+
project_id: null,
|
|
8827
|
+
plan_id: null,
|
|
8828
|
+
run_id: null,
|
|
8829
|
+
agent_id: h.agent_id ?? null,
|
|
8830
|
+
created_at: h.created_at,
|
|
8831
|
+
title: "",
|
|
8832
|
+
message: h.field ? `${h.field}: ${h.old_value ?? ""}${h.new_value != null ? ` -> ${h.new_value}` : ""}`.trim() : null,
|
|
8833
|
+
metadata: {}
|
|
8834
|
+
}));
|
|
8835
|
+
if (options.entity_type && options.entity_type !== "task") {
|
|
8836
|
+
entries = [];
|
|
8837
|
+
} else if (options.entity_type === "task" && options.entity_id) {
|
|
8838
|
+
entries = entries.filter((e) => e.task_id === options.entity_id);
|
|
8839
|
+
}
|
|
8840
|
+
if (options.since)
|
|
8841
|
+
entries = entries.filter((e) => e.created_at >= options.since);
|
|
8842
|
+
if (options.until)
|
|
8843
|
+
entries = entries.filter((e) => e.created_at <= options.until);
|
|
8844
|
+
entries.sort((a, b) => options.order === "asc" ? a.created_at.localeCompare(b.created_at) : b.created_at.localeCompare(a.created_at));
|
|
8845
|
+
const total = entries.length;
|
|
8846
|
+
const offset = options.offset ?? 0;
|
|
8847
|
+
const limit = options.limit ?? 50;
|
|
8848
|
+
return { entries: entries.slice(offset, offset + limit), total, limit, offset };
|
|
8849
|
+
}
|
|
8850
|
+
var _cache, PRIORITY_RANK;
|
|
8584
8851
|
var init_cloud_router = __esm(() => {
|
|
8585
8852
|
init_storage();
|
|
8853
|
+
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
8586
8854
|
});
|
|
8587
8855
|
|
|
8588
8856
|
// src/db/migrations.ts
|
|
@@ -16391,11 +16659,11 @@ function listTasks(filter = {}, db) {
|
|
|
16391
16659
|
}
|
|
16392
16660
|
}
|
|
16393
16661
|
addMetadataConditions(filter.metadata, conditions, params);
|
|
16394
|
-
const
|
|
16662
|
+
const PRIORITY_RANK2 = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
16395
16663
|
if (filter.cursor) {
|
|
16396
16664
|
try {
|
|
16397
16665
|
const decoded = JSON.parse(Buffer.from(filter.cursor, "base64").toString("utf8"));
|
|
16398
|
-
conditions.push(`(${
|
|
16666
|
+
conditions.push(`(${PRIORITY_RANK2} > ? OR (${PRIORITY_RANK2} = ? AND created_at < ?) OR (${PRIORITY_RANK2} = ? AND created_at = ? AND id > ?))`);
|
|
16399
16667
|
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
16400
16668
|
} catch {}
|
|
16401
16669
|
}
|
|
@@ -16412,7 +16680,7 @@ function listTasks(filter = {}, db) {
|
|
|
16412
16680
|
params.push(filter.offset);
|
|
16413
16681
|
}
|
|
16414
16682
|
}
|
|
16415
|
-
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${
|
|
16683
|
+
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK2}, created_at DESC, id ASC${limitClause}`).all(...params);
|
|
16416
16684
|
return rows.map(rowToTask);
|
|
16417
16685
|
}
|
|
16418
16686
|
function getTaskByFingerprint(fingerprint, db) {
|
|
@@ -20886,13 +21154,14 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
20886
21154
|
console.log(formatTaskLine(task2));
|
|
20887
21155
|
}
|
|
20888
21156
|
});
|
|
20889
|
-
program2.command("lock <id>").description("Acquire exclusive lock on a task").action((id) => {
|
|
21157
|
+
program2.command("lock <id>").description("Acquire exclusive lock on a task").action(async (id) => {
|
|
20890
21158
|
const globalOpts = program2.opts();
|
|
20891
21159
|
const agentId = globalOpts.agent || "cli";
|
|
21160
|
+
const cloud = getTodosCloudClient();
|
|
20892
21161
|
const resolvedId = resolveTaskId(id);
|
|
20893
21162
|
let result;
|
|
20894
21163
|
try {
|
|
20895
|
-
result = lockTask(resolvedId, agentId);
|
|
21164
|
+
result = cloud ? await cloudLockTask(cloud, resolvedId, agentId) : lockTask(resolvedId, agentId);
|
|
20896
21165
|
} catch (e) {
|
|
20897
21166
|
handleError(e);
|
|
20898
21167
|
}
|
|
@@ -20905,11 +21174,15 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
20905
21174
|
process.exit(1);
|
|
20906
21175
|
}
|
|
20907
21176
|
});
|
|
20908
|
-
program2.command("unlock <id>").description("Release lock on a task").action((id) => {
|
|
21177
|
+
program2.command("unlock <id>").description("Release lock on a task").action(async (id) => {
|
|
20909
21178
|
const globalOpts = program2.opts();
|
|
21179
|
+
const cloud = getTodosCloudClient();
|
|
20910
21180
|
const resolvedId = resolveTaskId(id);
|
|
20911
21181
|
try {
|
|
20912
|
-
|
|
21182
|
+
if (cloud)
|
|
21183
|
+
await cloudUnlockTask(cloud, resolvedId, globalOpts.agent);
|
|
21184
|
+
else
|
|
21185
|
+
unlockTask(resolvedId, globalOpts.agent);
|
|
20913
21186
|
} catch (e) {
|
|
20914
21187
|
handleError(e);
|
|
20915
21188
|
}
|
|
@@ -29655,11 +29928,11 @@ function taskSummary(task, blockers) {
|
|
|
29655
29928
|
const firstLine = task.description?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
29656
29929
|
return firstLine || undefined;
|
|
29657
29930
|
}
|
|
29658
|
-
function
|
|
29931
|
+
function priorityRank2(priority) {
|
|
29659
29932
|
return { critical: 0, high: 1, medium: 2, low: 3 }[priority];
|
|
29660
29933
|
}
|
|
29661
29934
|
function sortActionableTasks(a, b) {
|
|
29662
|
-
const priority =
|
|
29935
|
+
const priority = priorityRank2(a.priority) - priorityRank2(b.priority);
|
|
29663
29936
|
if (priority !== 0)
|
|
29664
29937
|
return priority;
|
|
29665
29938
|
return b.updated_at.localeCompare(a.updated_at);
|
|
@@ -31573,6 +31846,49 @@ function registerProjectCommands(program2) {
|
|
|
31573
31846
|
});
|
|
31574
31847
|
program2.command("deps <id>").description("Manage task dependencies").option("--needs <dep-id>", "Add dependency (this task needs dep-id)").option("--remove <dep-id>", "Remove dependency").option("--graph", "Show the dependency graph instead of direct edges").option("--direction <direction>", "Graph direction: up, down, or both", "both").action(async (id, opts) => {
|
|
31575
31848
|
const globalOpts = program2.opts();
|
|
31849
|
+
const cloud = getTodosCloudClient();
|
|
31850
|
+
if (cloud) {
|
|
31851
|
+
const cloudId = resolveTaskId(id);
|
|
31852
|
+
if (opts.needs) {
|
|
31853
|
+
try {
|
|
31854
|
+
const dep = await cloudAddDependency(cloud, cloudId, resolveTaskId(opts.needs));
|
|
31855
|
+
if (globalOpts.json)
|
|
31856
|
+
output(dep, true);
|
|
31857
|
+
else
|
|
31858
|
+
console.log(chalk4.green("Dependency added."));
|
|
31859
|
+
} catch (e) {
|
|
31860
|
+
handleError(e);
|
|
31861
|
+
}
|
|
31862
|
+
return;
|
|
31863
|
+
}
|
|
31864
|
+
if (opts.remove) {
|
|
31865
|
+
const removed = await cloudRemoveDependency(cloud, cloudId, resolveTaskId(opts.remove));
|
|
31866
|
+
if (globalOpts.json)
|
|
31867
|
+
output({ removed }, true);
|
|
31868
|
+
else
|
|
31869
|
+
console.log(removed ? chalk4.green("Dependency removed.") : chalk4.red("Dependency not found."));
|
|
31870
|
+
return;
|
|
31871
|
+
}
|
|
31872
|
+
const edges = await cloudGetDependencies(cloud, cloudId);
|
|
31873
|
+
if (globalOpts.json) {
|
|
31874
|
+
output(edges, true);
|
|
31875
|
+
return;
|
|
31876
|
+
}
|
|
31877
|
+
if (edges.dependencies.length > 0) {
|
|
31878
|
+
console.log(chalk4.bold("Depends on:"));
|
|
31879
|
+
for (const dep of edges.dependencies)
|
|
31880
|
+
console.log(` ${chalk4.cyan(dep.depends_on)}`);
|
|
31881
|
+
}
|
|
31882
|
+
if (edges.blocked_by.length > 0) {
|
|
31883
|
+
console.log(chalk4.bold("Blocks:"));
|
|
31884
|
+
for (const b of edges.blocked_by)
|
|
31885
|
+
console.log(` ${chalk4.cyan(b.task_id)}`);
|
|
31886
|
+
}
|
|
31887
|
+
if (edges.dependencies.length === 0 && edges.blocked_by.length === 0) {
|
|
31888
|
+
console.log(chalk4.dim("No dependencies."));
|
|
31889
|
+
}
|
|
31890
|
+
return;
|
|
31891
|
+
}
|
|
31576
31892
|
const { addDependency: addDependency2, removeDependency: removeDependency2, getTaskGraph: getTaskGraph2, getTaskWithRelations: getTaskWithRelations2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
31577
31893
|
const resolvedId = resolveTaskId(id);
|
|
31578
31894
|
if (opts.needs) {
|
|
@@ -32789,8 +33105,9 @@ function registerAgentCommands(program2) {
|
|
|
32789
33105
|
program2.command("init <name>").description("Register an agents and get a short UUID").option("-d, --description <text>", "Agent description").action(async (name, opts) => {
|
|
32790
33106
|
const globalOpts = program2.opts();
|
|
32791
33107
|
try {
|
|
32792
|
-
const
|
|
32793
|
-
const result =
|
|
33108
|
+
const cloud = getTodosCloudClient();
|
|
33109
|
+
const result = cloud ? await cloudRegisterAgent(cloud, { name, description: opts.description }) : (await Promise.resolve().then(() => (init_agents(), exports_agents))).registerAgent({ name, description: opts.description });
|
|
33110
|
+
const { isAgentConflict: isAgentConflict2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
32794
33111
|
if (isAgentConflict2(result)) {
|
|
32795
33112
|
console.error(chalk5.red("CONFLICT:"), result.message);
|
|
32796
33113
|
process.exit(1);
|
|
@@ -32947,14 +33264,15 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
|
32947
33264
|
});
|
|
32948
33265
|
program2.command("agent <name>").description("Show all info about an agent: tasks, status, last seen, stats").option("-j, --json", "Output as JSON").action(async (name, opts) => {
|
|
32949
33266
|
const globalOpts = program2.opts();
|
|
33267
|
+
const cloud = getTodosCloudClient();
|
|
32950
33268
|
const { getAgentByName: findByName } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
32951
|
-
const agent = findByName(name);
|
|
33269
|
+
const agent = cloud ? (await cloudListAgents(cloud)).find((a) => a.name === name || a.id === name) ?? null : findByName(name);
|
|
32952
33270
|
if (!agent) {
|
|
32953
33271
|
console.error(chalk5.red(`Agent not found: ${name}`));
|
|
32954
33272
|
process.exit(1);
|
|
32955
33273
|
}
|
|
32956
|
-
const byAssigned = listTasks({ assigned_to: agent.name });
|
|
32957
|
-
const byId = listTasks({ agent_id: agent.id });
|
|
33274
|
+
const byAssigned = cloud ? await cloudListTasks(cloud, { assigned_to: agent.name }) : listTasks({ assigned_to: agent.name });
|
|
33275
|
+
const byId = cloud ? await cloudListTasks(cloud, { agent_id: agent.id }) : listTasks({ agent_id: agent.id });
|
|
32958
33276
|
const seen = new Set;
|
|
32959
33277
|
const allTasks = [...byAssigned, ...byId].filter((t) => {
|
|
32960
33278
|
if (seen.has(t.id))
|
|
@@ -33092,7 +33410,8 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
|
|
|
33092
33410
|
console.log(chalk5.green("Task list deleted."));
|
|
33093
33411
|
return;
|
|
33094
33412
|
}
|
|
33095
|
-
const
|
|
33413
|
+
const cloud = getTodosCloudClient();
|
|
33414
|
+
const lists = cloud ? await cloudListTaskLists(cloud, projectId ?? undefined) : listTaskLists(projectId);
|
|
33096
33415
|
if (globalOpts.json) {
|
|
33097
33416
|
output(lists, true);
|
|
33098
33417
|
return;
|
|
@@ -38364,7 +38683,19 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
38364
38683
|
claimNext: (agentId, filters) => claimNextTask2(agentId, filters, store),
|
|
38365
38684
|
getNext: (_agentId, filters) => getNextTask2(filters, store),
|
|
38366
38685
|
getActiveWork: (filters) => getActiveWork2(filters, store),
|
|
38367
|
-
getChangedSince: (since, filters) => getChangedSince(since, filters, store)
|
|
38686
|
+
getChangedSince: (since, filters) => getChangedSince(since, filters, store),
|
|
38687
|
+
lock: (id, agentId) => lockTask2(id, agentId, store),
|
|
38688
|
+
unlock: (id, agentId) => unlockTask2(id, agentId, store)
|
|
38689
|
+
},
|
|
38690
|
+
dependencies: {
|
|
38691
|
+
add: (taskId, dependsOn, context) => addDependency2(taskId, dependsOn, store, context),
|
|
38692
|
+
remove: (taskId, dependsOn) => removeDependency2(taskId, dependsOn, store),
|
|
38693
|
+
list: (taskId) => listDependencies(taskId, store),
|
|
38694
|
+
listAll: () => store.list("dependencies")
|
|
38695
|
+
},
|
|
38696
|
+
verifications: {
|
|
38697
|
+
add: (input, context) => addVerification(input, store, context),
|
|
38698
|
+
list: (taskId) => listVerifications(taskId, store)
|
|
38368
38699
|
},
|
|
38369
38700
|
projects: {
|
|
38370
38701
|
create: (input, context) => createProject2(input, store, context),
|
|
@@ -38819,6 +39150,103 @@ async function patchTask(task, patch, store) {
|
|
|
38819
39150
|
await store.upsert("tasks", updated);
|
|
38820
39151
|
return updated;
|
|
38821
39152
|
}
|
|
39153
|
+
function cloudLockExpired(lockedAt) {
|
|
39154
|
+
if (!lockedAt)
|
|
39155
|
+
return true;
|
|
39156
|
+
return new Date(lockedAt).getTime() + CLOUD_LOCK_EXPIRY_MINUTES * 60 * 1000 < Date.now();
|
|
39157
|
+
}
|
|
39158
|
+
function cloudLockExpiresAt(lockedAt) {
|
|
39159
|
+
return new Date(new Date(lockedAt).getTime() + CLOUD_LOCK_EXPIRY_MINUTES * 60 * 1000).toISOString();
|
|
39160
|
+
}
|
|
39161
|
+
async function lockTask2(id, agentId, store) {
|
|
39162
|
+
const task = await requireRecord("tasks", id, store);
|
|
39163
|
+
if (task.status === "completed" || task.status === "cancelled") {
|
|
39164
|
+
return { success: false, error: `Task is ${task.status} and cannot be locked` };
|
|
39165
|
+
}
|
|
39166
|
+
if (task.locked_by && task.locked_by !== agentId && !cloudLockExpired(task.locked_at)) {
|
|
39167
|
+
return { success: false, locked_by: task.locked_by, locked_at: task.locked_at ?? undefined, error: `Task is locked by ${task.locked_by}` };
|
|
39168
|
+
}
|
|
39169
|
+
const timestamp = new Date().toISOString();
|
|
39170
|
+
await patchTask(task, { locked_by: agentId, locked_at: timestamp }, store);
|
|
39171
|
+
return { success: true, locked_by: agentId, locked_at: timestamp, expires_at: cloudLockExpiresAt(timestamp) };
|
|
39172
|
+
}
|
|
39173
|
+
async function unlockTask2(id, agentId, store) {
|
|
39174
|
+
const task = await requireRecord("tasks", id, store);
|
|
39175
|
+
if (agentId && task.locked_by && task.locked_by !== agentId) {
|
|
39176
|
+
throw new Error(`Task ${id} is locked by ${task.locked_by}, not ${agentId}`);
|
|
39177
|
+
}
|
|
39178
|
+
await patchTask(task, { locked_by: null, locked_at: null }, store);
|
|
39179
|
+
return true;
|
|
39180
|
+
}
|
|
39181
|
+
function dependencyId(taskId, dependsOn) {
|
|
39182
|
+
return `${taskId}::${dependsOn}`;
|
|
39183
|
+
}
|
|
39184
|
+
async function addDependency2(taskId, dependsOn, store, context) {
|
|
39185
|
+
if (taskId === dependsOn)
|
|
39186
|
+
throw new Error("A task cannot depend on itself");
|
|
39187
|
+
if (!await store.get("tasks", taskId))
|
|
39188
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
39189
|
+
if (!await store.get("tasks", dependsOn))
|
|
39190
|
+
throw new Error(`Task not found: ${dependsOn}`);
|
|
39191
|
+
const edges = await store.list("dependencies");
|
|
39192
|
+
const adjacency = new Map;
|
|
39193
|
+
for (const edge of edges) {
|
|
39194
|
+
if (!adjacency.has(edge.task_id))
|
|
39195
|
+
adjacency.set(edge.task_id, []);
|
|
39196
|
+
adjacency.get(edge.task_id).push(edge.depends_on);
|
|
39197
|
+
}
|
|
39198
|
+
const queue = [dependsOn];
|
|
39199
|
+
const seen = new Set;
|
|
39200
|
+
while (queue.length) {
|
|
39201
|
+
const node = queue.shift();
|
|
39202
|
+
if (node === taskId)
|
|
39203
|
+
throw new Error(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
|
|
39204
|
+
if (seen.has(node))
|
|
39205
|
+
continue;
|
|
39206
|
+
seen.add(node);
|
|
39207
|
+
for (const next of adjacency.get(node) ?? [])
|
|
39208
|
+
queue.push(next);
|
|
39209
|
+
}
|
|
39210
|
+
const timestamp = new Date().toISOString();
|
|
39211
|
+
const record = { id: dependencyId(taskId, dependsOn), task_id: taskId, depends_on: dependsOn, created_at: timestamp, updated_at: timestamp };
|
|
39212
|
+
await store.upsert("dependencies", record, context);
|
|
39213
|
+
return { task_id: taskId, depends_on: dependsOn };
|
|
39214
|
+
}
|
|
39215
|
+
async function removeDependency2(taskId, dependsOn, store) {
|
|
39216
|
+
const existing = await store.get("dependencies", dependencyId(taskId, dependsOn));
|
|
39217
|
+
if (!existing)
|
|
39218
|
+
return false;
|
|
39219
|
+
await store.delete("dependencies", dependencyId(taskId, dependsOn));
|
|
39220
|
+
return true;
|
|
39221
|
+
}
|
|
39222
|
+
async function listDependencies(taskId, store) {
|
|
39223
|
+
const edges = await store.list("dependencies");
|
|
39224
|
+
return {
|
|
39225
|
+
dependencies: edges.filter((edge) => edge.task_id === taskId).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on })),
|
|
39226
|
+
blocked_by: edges.filter((edge) => edge.depends_on === taskId).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on }))
|
|
39227
|
+
};
|
|
39228
|
+
}
|
|
39229
|
+
async function addVerification(input, store, context) {
|
|
39230
|
+
if (!await store.get("tasks", input.task_id))
|
|
39231
|
+
throw new Error(`Task not found: ${input.task_id}`);
|
|
39232
|
+
const timestamp = new Date().toISOString();
|
|
39233
|
+
const verification = {
|
|
39234
|
+
id: randomUUID3(),
|
|
39235
|
+
task_id: input.task_id,
|
|
39236
|
+
command: input.command,
|
|
39237
|
+
status: input.status ?? "unknown",
|
|
39238
|
+
output_summary: input.output_summary ?? null,
|
|
39239
|
+
artifact_path: input.artifact_path ?? null,
|
|
39240
|
+
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
39241
|
+
run_at: timestamp,
|
|
39242
|
+
created_at: timestamp
|
|
39243
|
+
};
|
|
39244
|
+
await store.upsert("verifications", { ...verification, updated_at: timestamp }, context);
|
|
39245
|
+
return verification;
|
|
39246
|
+
}
|
|
39247
|
+
async function listVerifications(taskId, store) {
|
|
39248
|
+
return (await store.list("verifications")).filter((verification) => verification.task_id === taskId).sort((a, b) => b.run_at.localeCompare(a.run_at));
|
|
39249
|
+
}
|
|
38822
39250
|
function toFilterArray(value) {
|
|
38823
39251
|
return Array.isArray(value) ? value : [value];
|
|
38824
39252
|
}
|
|
@@ -39192,7 +39620,7 @@ function compareClock(left, right) {
|
|
|
39192
39620
|
function numberValue2(value) {
|
|
39193
39621
|
return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
|
|
39194
39622
|
}
|
|
39195
|
-
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";
|
|
39623
|
+
var CLOUD_LOCK_EXPIRY_MINUTES = 30, 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";
|
|
39196
39624
|
var init_postgres_adapter = () => {};
|
|
39197
39625
|
|
|
39198
39626
|
// src/server/cloud.ts
|
|
@@ -39690,6 +40118,7 @@ async function handleV1Request(req, url) {
|
|
|
39690
40118
|
const resource = segments[1];
|
|
39691
40119
|
const id = segments[2];
|
|
39692
40120
|
const action = segments[3];
|
|
40121
|
+
const subId = segments[4];
|
|
39693
40122
|
try {
|
|
39694
40123
|
if (resource === "tasks") {
|
|
39695
40124
|
if (id === "exists" && !action) {
|
|
@@ -39765,6 +40194,89 @@ async function handleV1Request(req, url) {
|
|
|
39765
40194
|
}
|
|
39766
40195
|
return error(405, `method ${method} not allowed on /v1/tasks/:id/comments`);
|
|
39767
40196
|
}
|
|
40197
|
+
if (action === "lock" || action === "unlock") {
|
|
40198
|
+
if (method !== "POST")
|
|
40199
|
+
return error(405, `method ${method} not allowed on /v1/tasks/:id/${action}`);
|
|
40200
|
+
if (typeof store.tasks.lock !== "function" || typeof store.tasks.unlock !== "function") {
|
|
40201
|
+
return error(501, "task locking is not supported by this storage backend");
|
|
40202
|
+
}
|
|
40203
|
+
const body2 = await readJson(req) ?? {};
|
|
40204
|
+
if (!await store.tasks.get(id))
|
|
40205
|
+
return error(404, "task not found");
|
|
40206
|
+
if (action === "lock") {
|
|
40207
|
+
const agentId2 = body2.agent_id || principal.agent || "todos-serve";
|
|
40208
|
+
return json2({ result: await store.tasks.lock(id, agentId2) });
|
|
40209
|
+
}
|
|
40210
|
+
const released = await store.tasks.unlock(id, body2.agent_id || principal.agent || undefined);
|
|
40211
|
+
return json2({ success: released });
|
|
40212
|
+
}
|
|
40213
|
+
if (action === "dependencies") {
|
|
40214
|
+
if (!store.dependencies)
|
|
40215
|
+
return error(501, "dependencies are not supported by this storage backend");
|
|
40216
|
+
if (method === "GET") {
|
|
40217
|
+
if (!await store.tasks.get(id))
|
|
40218
|
+
return error(404, "task not found");
|
|
40219
|
+
const edges = await store.dependencies.list(id);
|
|
40220
|
+
return json2(edges);
|
|
40221
|
+
}
|
|
40222
|
+
if (method === "POST") {
|
|
40223
|
+
const body2 = await readJson(req) ?? {};
|
|
40224
|
+
if (typeof body2.depends_on !== "string" || !body2.depends_on.trim()) {
|
|
40225
|
+
return error(400, "depends_on is required");
|
|
40226
|
+
}
|
|
40227
|
+
try {
|
|
40228
|
+
const dependency = await store.dependencies.add(id, body2.depends_on, contextFromPrincipal(principal));
|
|
40229
|
+
return json2({ dependency }, 201);
|
|
40230
|
+
} catch (e) {
|
|
40231
|
+
const msg = e.message || "";
|
|
40232
|
+
if (msg.includes("not found"))
|
|
40233
|
+
return error(404, msg);
|
|
40234
|
+
if (msg.includes("cycle") || msg.includes("itself"))
|
|
40235
|
+
return error(409, msg);
|
|
40236
|
+
throw e;
|
|
40237
|
+
}
|
|
40238
|
+
}
|
|
40239
|
+
if (method === "DELETE") {
|
|
40240
|
+
if (!subId)
|
|
40241
|
+
return error(400, "dependency target id is required (/v1/tasks/:id/dependencies/:dep)");
|
|
40242
|
+
const removed = await store.dependencies.remove(id, subId);
|
|
40243
|
+
return json2({ removed });
|
|
40244
|
+
}
|
|
40245
|
+
return error(405, `method ${method} not allowed on /v1/tasks/:id/dependencies`);
|
|
40246
|
+
}
|
|
40247
|
+
if (action === "verifications") {
|
|
40248
|
+
if (!store.verifications)
|
|
40249
|
+
return error(501, "verifications are not supported by this storage backend");
|
|
40250
|
+
if (method === "GET") {
|
|
40251
|
+
if (!await store.tasks.get(id))
|
|
40252
|
+
return error(404, "task not found");
|
|
40253
|
+
const verifications = await store.verifications.list(id);
|
|
40254
|
+
return json2({ verifications, count: verifications.length });
|
|
40255
|
+
}
|
|
40256
|
+
if (method === "POST") {
|
|
40257
|
+
const body2 = await readJson(req) ?? {};
|
|
40258
|
+
if (typeof body2.command !== "string" || !body2.command.trim()) {
|
|
40259
|
+
return error(400, "command is required");
|
|
40260
|
+
}
|
|
40261
|
+
try {
|
|
40262
|
+
const verification = await store.verifications.add({
|
|
40263
|
+
task_id: id,
|
|
40264
|
+
command: body2.command,
|
|
40265
|
+
status: body2.status,
|
|
40266
|
+
output_summary: body2.output_summary,
|
|
40267
|
+
artifact_path: body2.artifact_path,
|
|
40268
|
+
agent_id: body2.agent_id
|
|
40269
|
+
}, contextFromPrincipal(principal, body2));
|
|
40270
|
+
return json2({ verification }, 201);
|
|
40271
|
+
} catch (e) {
|
|
40272
|
+
const msg = e.message || "";
|
|
40273
|
+
if (msg.includes("not found"))
|
|
40274
|
+
return error(404, msg);
|
|
40275
|
+
throw e;
|
|
40276
|
+
}
|
|
40277
|
+
}
|
|
40278
|
+
return error(405, `method ${method} not allowed on /v1/tasks/:id/verifications`);
|
|
40279
|
+
}
|
|
39768
40280
|
const body = await readJson(req) ?? {};
|
|
39769
40281
|
const agentId = body.agent_id || principal.agent || "todos-serve";
|
|
39770
40282
|
if (action === "start" && method === "POST") {
|
|
@@ -39869,16 +40381,55 @@ async function handleV1Request(req, url) {
|
|
|
39869
40381
|
}
|
|
39870
40382
|
if (!id && method === "POST") {
|
|
39871
40383
|
const body = await readJson(req);
|
|
39872
|
-
if (!body || typeof body.name !== "string")
|
|
40384
|
+
if (!body || typeof body.name !== "string" || !body.name.trim())
|
|
39873
40385
|
return error(400, "name is required");
|
|
39874
|
-
const
|
|
39875
|
-
|
|
40386
|
+
const result = await store.agents.register(body, contextFromPrincipal(principal));
|
|
40387
|
+
if (result && typeof result === "object" && "conflict" in result) {
|
|
40388
|
+
return error(409, result.message ?? "agent name conflict", { conflict: true });
|
|
40389
|
+
}
|
|
40390
|
+
return json2({ agent: result }, 201);
|
|
39876
40391
|
}
|
|
39877
40392
|
if (id && method === "GET") {
|
|
39878
40393
|
const agent = await store.agents.get(id);
|
|
39879
40394
|
return agent ? json2({ agent }) : error(404, "agent not found");
|
|
39880
40395
|
}
|
|
39881
40396
|
}
|
|
40397
|
+
if (resource === "activity" && !id) {
|
|
40398
|
+
if (method !== "GET")
|
|
40399
|
+
return error(405, `method ${method} not allowed on /v1/activity`);
|
|
40400
|
+
const limitParam = url.searchParams.get("limit");
|
|
40401
|
+
const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
|
|
40402
|
+
const activity = await store.audit.getRecentActivity(limit);
|
|
40403
|
+
return json2({ activity, count: activity.length });
|
|
40404
|
+
}
|
|
40405
|
+
if (resource === "task-lists" && !id) {
|
|
40406
|
+
if (method !== "GET")
|
|
40407
|
+
return error(405, `method ${method} not allowed on /v1/task-lists`);
|
|
40408
|
+
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
40409
|
+
const taskLists = await store.taskLists.list(projectId);
|
|
40410
|
+
return json2({ task_lists: taskLists, count: taskLists.length });
|
|
40411
|
+
}
|
|
40412
|
+
if (resource === "dependencies" && !id) {
|
|
40413
|
+
if (method !== "GET")
|
|
40414
|
+
return error(405, `method ${method} not allowed on /v1/dependencies`);
|
|
40415
|
+
if (typeof store.dependencies?.listAll !== "function") {
|
|
40416
|
+
return error(501, "dependency edge listing is not supported by this storage backend");
|
|
40417
|
+
}
|
|
40418
|
+
const dependencies = await store.dependencies.listAll();
|
|
40419
|
+
return json2({ dependencies, count: dependencies.length });
|
|
40420
|
+
}
|
|
40421
|
+
if (resource === "next" && !id) {
|
|
40422
|
+
if (method !== "GET")
|
|
40423
|
+
return error(405, `method ${method} not allowed on /v1/next`);
|
|
40424
|
+
const agent = url.searchParams.get("agent") ?? undefined;
|
|
40425
|
+
const filters = {
|
|
40426
|
+
...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
|
|
40427
|
+
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
40428
|
+
...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
|
|
40429
|
+
};
|
|
40430
|
+
const task = await store.tasks.getNext(agent, filters);
|
|
40431
|
+
return json2({ task: task ?? null });
|
|
40432
|
+
}
|
|
39882
40433
|
if (resource === "stats" && method === "GET") {
|
|
39883
40434
|
const [tasks, tasksAll, projects] = await Promise.all([
|
|
39884
40435
|
store.tasks.count(),
|
|
@@ -62399,6 +62950,33 @@ function registerAgentTools(server, { shouldRegisterTool, resolveId, formatError
|
|
|
62399
62950
|
force: exports_external3.boolean().optional().describe("Force takeover of an active agent's name. Use with caution \u2014 only when you know the previous session is dead.")
|
|
62400
62951
|
}, async ({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force }) => {
|
|
62401
62952
|
try {
|
|
62953
|
+
const cloud = getTodosCloudClient();
|
|
62954
|
+
if (cloud) {
|
|
62955
|
+
const agent2 = await cloudRegisterAgent(cloud, {
|
|
62956
|
+
name,
|
|
62957
|
+
description,
|
|
62958
|
+
role,
|
|
62959
|
+
title,
|
|
62960
|
+
level,
|
|
62961
|
+
permissions,
|
|
62962
|
+
capabilities,
|
|
62963
|
+
session_id,
|
|
62964
|
+
working_dir,
|
|
62965
|
+
force
|
|
62966
|
+
});
|
|
62967
|
+
return {
|
|
62968
|
+
content: [{
|
|
62969
|
+
type: "text",
|
|
62970
|
+
text: `Agent registered:
|
|
62971
|
+
ID: ${agent2.id}
|
|
62972
|
+
Name: ${agent2.name}${agent2.description ? `
|
|
62973
|
+
Description: ${agent2.description}` : ""}
|
|
62974
|
+
Session: ${agent2.session_id ?? "unbound"}
|
|
62975
|
+
Created: ${agent2.created_at}
|
|
62976
|
+
Last seen: ${agent2.last_seen_at}`
|
|
62977
|
+
}]
|
|
62978
|
+
};
|
|
62979
|
+
}
|
|
62402
62980
|
const pool = getAgentPoolForProject(working_dir);
|
|
62403
62981
|
const result = registerAgent({ name, description, role, title, level, permissions, capabilities, session_id, working_dir, force, pool: pool || undefined });
|
|
62404
62982
|
if (isAgentConflict(result)) {
|
|
@@ -68322,7 +68900,8 @@ function registerQueryCommands(program2) {
|
|
|
68322
68900
|
if (pid)
|
|
68323
68901
|
filters.project_id = pid;
|
|
68324
68902
|
}
|
|
68325
|
-
const
|
|
68903
|
+
const cloud = getTodosCloudClient();
|
|
68904
|
+
const task2 = cloud ? await cloudNextTask(cloud, opts.agent, Object.keys(filters).length ? filters : undefined) : getNextTask(opts.agent, Object.keys(filters).length ? filters : undefined, db);
|
|
68326
68905
|
if (!task2) {
|
|
68327
68906
|
if (json3) {
|
|
68328
68907
|
console.log(JSON.stringify(null));
|
|
@@ -68443,9 +69022,10 @@ Next up:`));
|
|
|
68443
69022
|
console.log(` ${chalk7.cyan(t.short_id || t.id.slice(0, 8))} ${chalk7.yellow(t.priority)} ${t.title}`);
|
|
68444
69023
|
}
|
|
68445
69024
|
});
|
|
68446
|
-
program2.command("recap").description("Show what happened in the last N hours \u2014 completed tasks, new tasks, agent activity, blockers").option("--hours <n>", "Look back N hours (default: 8)", "8").option("--project <id>", "Filter to project").action((opts) => {
|
|
69025
|
+
program2.command("recap").description("Show what happened in the last N hours \u2014 completed tasks, new tasks, agent activity, blockers").option("--hours <n>", "Look back N hours (default: 8)", "8").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
68447
69026
|
const globalOpts = program2.opts();
|
|
68448
|
-
const
|
|
69027
|
+
const cloud = getTodosCloudClient();
|
|
69028
|
+
const recap = cloud ? await cloudRecap(cloud, parseInt(opts.hours, 10), opts.project) : getRecap(parseInt(opts.hours, 10), opts.project);
|
|
68449
69029
|
if (globalOpts.json) {
|
|
68450
69030
|
output(recap, true);
|
|
68451
69031
|
return;
|
|
@@ -68508,11 +69088,12 @@ Agents:`));
|
|
|
68508
69088
|
}
|
|
68509
69089
|
console.log();
|
|
68510
69090
|
});
|
|
68511
|
-
program2.command("standup").description("Generate standup notes \u2014 completed since yesterday, in progress, blocked. Grouped by agent.").option("--since <date>", "ISO date or 'yesterday' (default: yesterday)").option("--project <id>", "Filter to project").action((opts) => {
|
|
69091
|
+
program2.command("standup").description("Generate standup notes \u2014 completed since yesterday, in progress, blocked. Grouped by agent.").option("--since <date>", "ISO date or 'yesterday' (default: yesterday)").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
68512
69092
|
const globalOpts = program2.opts();
|
|
68513
69093
|
const sinceDate = opts.since === "yesterday" || !opts.since ? new Date(Date.now() - 24 * 60 * 60 * 1000) : new Date(opts.since);
|
|
68514
69094
|
const hours = Math.max(1, Math.round((Date.now() - sinceDate.getTime()) / (60 * 60 * 1000)));
|
|
68515
|
-
const
|
|
69095
|
+
const cloud = getTodosCloudClient();
|
|
69096
|
+
const recap = cloud ? await cloudRecap(cloud, hours, opts.project) : getRecap(hours, opts.project);
|
|
68516
69097
|
if (globalOpts.json) {
|
|
68517
69098
|
output(recap, true);
|
|
68518
69099
|
return;
|
|
@@ -68582,7 +69163,8 @@ Blocked:`));
|
|
|
68582
69163
|
const filters = {};
|
|
68583
69164
|
if (opts.project)
|
|
68584
69165
|
filters.project_id = opts.project;
|
|
68585
|
-
const
|
|
69166
|
+
const cloud = getTodosCloudClient();
|
|
69167
|
+
const work = cloud ? await cloudActiveWork(cloud, Object.keys(filters).length ? filters : {}) : getActiveWork(Object.keys(filters).length ? filters : undefined, db);
|
|
68586
69168
|
if (json3) {
|
|
68587
69169
|
console.log(JSON.stringify(work, null, 2));
|
|
68588
69170
|
return;
|
|
@@ -68605,7 +69187,8 @@ Blocked:`));
|
|
|
68605
69187
|
const filters = {};
|
|
68606
69188
|
if (opts.project)
|
|
68607
69189
|
filters.project_id = opts.project;
|
|
68608
|
-
const
|
|
69190
|
+
const cloud = getTodosCloudClient();
|
|
69191
|
+
const tasks = cloud ? await cloudStaleTasks(cloud, parseInt(opts.minutes, 10), Object.keys(filters).length ? filters : {}) : getStaleTasks(parseInt(opts.minutes, 10), Object.keys(filters).length ? filters : undefined, db);
|
|
68609
69192
|
if (json3) {
|
|
68610
69193
|
console.log(JSON.stringify(tasks, null, 2));
|
|
68611
69194
|
return;
|
|
@@ -68757,12 +69340,13 @@ No task claimed (nothing available).`));
|
|
|
68757
69340
|
filter.project_id = projectId;
|
|
68758
69341
|
if (opts.agent)
|
|
68759
69342
|
filter.assigned_to = opts.agent;
|
|
69343
|
+
const cloud = getTodosCloudClient();
|
|
68760
69344
|
const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
68761
|
-
const changed = getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
|
|
69345
|
+
const changed = cloud ? await cloudChangedSince(cloud, since, filter) : getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
|
|
68762
69346
|
const completed = changed.filter((t) => t.status === "completed");
|
|
68763
69347
|
const inProgress = changed.filter((t) => t.status === "in_progress");
|
|
68764
69348
|
const failed = changed.filter((t) => t.status === "failed");
|
|
68765
|
-
const allTasks = listTasks({ ...filter, status: "pending" });
|
|
69349
|
+
const allTasks = cloud ? await cloudListTasks(cloud, { ...filter, status: "pending" }) : listTasks({ ...filter, status: "pending" });
|
|
68766
69350
|
if (opts.json || globalOpts.json) {
|
|
68767
69351
|
console.log(JSON.stringify({ completed, in_progress: inProgress, failed, pending: allTasks.length, period_days: days }, null, 2));
|
|
68768
69352
|
return;
|
|
@@ -69029,12 +69613,13 @@ Findings`));
|
|
|
69029
69613
|
const filter = {};
|
|
69030
69614
|
if (projectId)
|
|
69031
69615
|
filter.project_id = projectId;
|
|
69616
|
+
const cloud = getTodosCloudClient();
|
|
69032
69617
|
const { getTasksChangedSince: getTasksChangedSince2, getTaskStats: getTaskStats2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
69033
|
-
const changed = getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
|
|
69618
|
+
const changed = cloud ? await cloudChangedSince(cloud, since, filter) : getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
|
|
69034
69619
|
const completed = changed.filter((t) => t.status === "completed");
|
|
69035
69620
|
const failed = changed.filter((t) => t.status === "failed");
|
|
69036
|
-
const all = listTasks(filter);
|
|
69037
|
-
const stats2 = getTaskStats2(Object.keys(filter).length ? filter : undefined, db);
|
|
69621
|
+
const all = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter);
|
|
69622
|
+
const stats2 = cloud ? await cloudTaskStats(cloud, filter) : getTaskStats2(Object.keys(filter).length ? filter : undefined, db);
|
|
69038
69623
|
const byDay = {};
|
|
69039
69624
|
for (const t of changed) {
|
|
69040
69625
|
const day = t.updated_at.slice(0, 10);
|
|
@@ -69088,10 +69673,11 @@ Findings`));
|
|
|
69088
69673
|
program2.command("today").description("Show task activity from today").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
69089
69674
|
const globalOpts = program2.opts();
|
|
69090
69675
|
const db = getDatabase();
|
|
69676
|
+
const cloud = getTodosCloudClient();
|
|
69091
69677
|
const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
69092
69678
|
const start = new Date;
|
|
69093
69679
|
start.setHours(0, 0, 0, 0);
|
|
69094
|
-
const tasks = getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
69680
|
+
const tasks = cloud ? await cloudChangedSince(cloud, start.toISOString()) : getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
69095
69681
|
const completed = tasks.filter((t) => t.status === "completed");
|
|
69096
69682
|
const started = tasks.filter((t) => t.status === "in_progress");
|
|
69097
69683
|
const other = tasks.filter((t) => t.status !== "completed" && t.status !== "in_progress");
|
|
@@ -69118,13 +69704,14 @@ Findings`));
|
|
|
69118
69704
|
program2.command("yesterday").description("Show task activity from yesterday").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
69119
69705
|
const globalOpts = program2.opts();
|
|
69120
69706
|
const db = getDatabase();
|
|
69707
|
+
const cloud = getTodosCloudClient();
|
|
69121
69708
|
const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
69122
69709
|
const start = new Date;
|
|
69123
69710
|
start.setDate(start.getDate() - 1);
|
|
69124
69711
|
start.setHours(0, 0, 0, 0);
|
|
69125
69712
|
const end = new Date(start);
|
|
69126
69713
|
end.setHours(23, 59, 59, 999);
|
|
69127
|
-
const allChanged = getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
69714
|
+
const allChanged = cloud ? await cloudChangedSince(cloud, start.toISOString()) : getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
69128
69715
|
const tasks = allChanged.filter((t) => t.updated_at <= end.toISOString());
|
|
69129
69716
|
const completed = tasks.filter((t) => t.status === "completed");
|
|
69130
69717
|
const started = tasks.filter((t) => t.status === "in_progress");
|
|
@@ -69152,14 +69739,15 @@ Findings`));
|
|
|
69152
69739
|
const globalOpts = program2.opts();
|
|
69153
69740
|
const db = getDatabase();
|
|
69154
69741
|
const projectId = globalOpts.project ? autoProject(globalOpts) || undefined : undefined;
|
|
69742
|
+
const cloud = getTodosCloudClient();
|
|
69155
69743
|
const filter = { assigned_to: agent };
|
|
69156
69744
|
if (projectId)
|
|
69157
69745
|
filter.project_id = projectId;
|
|
69158
|
-
const tasks = listTasks(filter, db);
|
|
69746
|
+
const tasks = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter, db);
|
|
69159
69747
|
const filterByAgent = { agent_id: agent };
|
|
69160
69748
|
if (projectId)
|
|
69161
69749
|
filterByAgent.project_id = projectId;
|
|
69162
|
-
const agentTasks = listTasks(filterByAgent, db);
|
|
69750
|
+
const agentTasks = cloud ? await cloudListTasks(cloud, filterByAgent) : listTasks(filterByAgent, db);
|
|
69163
69751
|
const seen = new Set(tasks.map((t) => t.id));
|
|
69164
69752
|
for (const t of agentTasks) {
|
|
69165
69753
|
if (!seen.has(t.id)) {
|
|
@@ -69201,17 +69789,27 @@ Findings`));
|
|
|
69201
69789
|
program2.command("blocked").description("Show tasks blocked by incomplete dependencies").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
69202
69790
|
const globalOpts = program2.opts();
|
|
69203
69791
|
const db = getDatabase();
|
|
69204
|
-
const
|
|
69792
|
+
const cloud = getTodosCloudClient();
|
|
69205
69793
|
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
69206
69794
|
const filter = { status: "pending" };
|
|
69207
69795
|
if (projectId)
|
|
69208
69796
|
filter.project_id = projectId;
|
|
69209
|
-
const allPending = listTasks(filter, db);
|
|
69797
|
+
const allPending = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter, db);
|
|
69210
69798
|
const blockedTasks = [];
|
|
69211
|
-
|
|
69212
|
-
const
|
|
69213
|
-
|
|
69214
|
-
|
|
69799
|
+
if (cloud) {
|
|
69800
|
+
const map = await cloudBlockingDepsMap(cloud, allPending);
|
|
69801
|
+
for (const t of allPending) {
|
|
69802
|
+
const blockers = map.get(t.id);
|
|
69803
|
+
if (blockers && blockers.length > 0)
|
|
69804
|
+
blockedTasks.push({ task: t, blockers });
|
|
69805
|
+
}
|
|
69806
|
+
} else {
|
|
69807
|
+
const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
69808
|
+
for (const t of allPending) {
|
|
69809
|
+
const blockers = getBlockingDeps2(t.id, db);
|
|
69810
|
+
if (blockers.length > 0)
|
|
69811
|
+
blockedTasks.push({ task: t, blockers });
|
|
69812
|
+
}
|
|
69215
69813
|
}
|
|
69216
69814
|
if (opts.json || globalOpts.json) {
|
|
69217
69815
|
console.log(JSON.stringify(blockedTasks.map((b) => ({ ...b.task, blocked_by: b.blockers.map((bl) => ({ id: bl.id, short_id: bl.short_id, title: bl.title, status: bl.status })) }))));
|
|
@@ -69233,8 +69831,9 @@ Findings`));
|
|
|
69233
69831
|
program2.command("overdue").description("Show tasks past their due date").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
69234
69832
|
const globalOpts = program2.opts();
|
|
69235
69833
|
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
69834
|
+
const cloud = getTodosCloudClient();
|
|
69236
69835
|
const { getOverdueTasks: getOverdueTasks2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
69237
|
-
const tasks = getOverdueTasks2(projectId);
|
|
69836
|
+
const tasks = cloud ? await cloudOverdueTasks(cloud, projectId) : getOverdueTasks2(projectId);
|
|
69238
69837
|
if (opts.json || globalOpts.json) {
|
|
69239
69838
|
console.log(JSON.stringify(tasks));
|
|
69240
69839
|
return;
|
|
@@ -69255,10 +69854,8 @@ Findings`));
|
|
|
69255
69854
|
program2.command("sla").description("Show overdue or SLA-breached tasks that need escalation").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").option("--agent <id>", "Filter to assigned agent").option("--limit <n>", "Max tasks to show", "50").action(async (opts) => {
|
|
69256
69855
|
const globalOpts = program2.opts();
|
|
69257
69856
|
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
69258
|
-
const
|
|
69259
|
-
|
|
69260
|
-
agent_id: opts.agent
|
|
69261
|
-
}).slice(0, parseInt(opts.limit, 10));
|
|
69857
|
+
const cloud = getTodosCloudClient();
|
|
69858
|
+
const escalations = (cloud ? await cloudEscalatedTasks(cloud, { project_id: projectId, agent_id: opts.agent }) : getEscalatedTasks({ project_id: projectId, agent_id: opts.agent })).slice(0, parseInt(opts.limit, 10));
|
|
69262
69859
|
if (opts.json || globalOpts.json) {
|
|
69263
69860
|
console.log(JSON.stringify(escalations));
|
|
69264
69861
|
return;
|
|
@@ -69278,12 +69875,13 @@ Findings`));
|
|
|
69278
69875
|
program2.command("week").description("Show task activity from the past 7 days").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
69279
69876
|
const globalOpts = program2.opts();
|
|
69280
69877
|
const db = getDatabase();
|
|
69878
|
+
const cloud = getTodosCloudClient();
|
|
69281
69879
|
const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
69282
69880
|
const now4 = new Date;
|
|
69283
69881
|
const start = new Date(now4);
|
|
69284
69882
|
start.setDate(start.getDate() - 7);
|
|
69285
69883
|
start.setHours(0, 0, 0, 0);
|
|
69286
|
-
const tasks = getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
69884
|
+
const tasks = cloud ? await cloudChangedSince(cloud, start.toISOString()) : getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
69287
69885
|
const days = {};
|
|
69288
69886
|
for (let i = 0;i < 7; i++) {
|
|
69289
69887
|
const d = new Date(now4);
|
|
@@ -69331,9 +69929,10 @@ Findings`));
|
|
|
69331
69929
|
program2.command("burndown").description("Show task completion velocity over the past 7 days").option("--days <n>", "Number of days", "7").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
69332
69930
|
const globalOpts = program2.opts();
|
|
69333
69931
|
const db = getDatabase();
|
|
69932
|
+
const cloud = getTodosCloudClient();
|
|
69334
69933
|
const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
|
|
69335
69934
|
const numDays = parseInt(opts.days, 10);
|
|
69336
|
-
const entries = getRecentActivity2(5000, db);
|
|
69935
|
+
const entries = cloud ? await cloudRecentActivity(cloud, 5000) : getRecentActivity2(5000, db);
|
|
69337
69936
|
const now4 = new Date;
|
|
69338
69937
|
const dayStats = [];
|
|
69339
69938
|
for (let i = numDays - 1;i >= 0; i--) {
|
|
@@ -69373,8 +69972,9 @@ Findings`));
|
|
|
69373
69972
|
program2.command("log").description("Show recent task activity log (git-log style)").option("--limit <n>", "Number of entries", "30").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
69374
69973
|
const globalOpts = program2.opts();
|
|
69375
69974
|
const db = getDatabase();
|
|
69975
|
+
const cloud = getTodosCloudClient();
|
|
69376
69976
|
const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
|
|
69377
|
-
const entries = getRecentActivity2(parseInt(opts.limit, 10), db);
|
|
69977
|
+
const entries = cloud ? await cloudRecentActivity(cloud, parseInt(opts.limit, 10)) : getRecentActivity2(parseInt(opts.limit, 10), db);
|
|
69378
69978
|
if (opts.json || globalOpts.json) {
|
|
69379
69979
|
console.log(JSON.stringify(entries));
|
|
69380
69980
|
return;
|
|
@@ -69417,6 +70017,39 @@ Findings`));
|
|
|
69417
70017
|
program2.command("timeline").description("Show a unified local activity timeline for tasks, projects, plans, or runs").option("--task <id>", "Filter to a task").option("--project <id>", "Filter to a project").option("--plan <id>", "Filter to a plan").option("--run <id>", "Filter to a run ledger").option("--since <iso>", "Only include entries at or after this ISO timestamp").option("--until <iso>", "Only include entries at or before this ISO timestamp").option("--limit <n>", "Number of entries", "50").option("--offset <n>", "Entries to skip", "0").option("--order <order>", "Sort order: asc or desc", "desc").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
69418
70018
|
const globalOpts = program2.opts();
|
|
69419
70019
|
const db = getDatabase();
|
|
70020
|
+
const cloud = getTodosCloudClient();
|
|
70021
|
+
if (cloud) {
|
|
70022
|
+
const cloudOptions = {
|
|
70023
|
+
since: opts.since,
|
|
70024
|
+
until: opts.until,
|
|
70025
|
+
limit: parseInt(opts.limit, 10),
|
|
70026
|
+
offset: parseInt(opts.offset, 10),
|
|
70027
|
+
order: opts.order === "asc" ? "asc" : "desc",
|
|
70028
|
+
...opts.task ? { entity_type: "task", entity_id: opts.task } : {},
|
|
70029
|
+
...opts.project ? { entity_type: "project", entity_id: opts.project } : {},
|
|
70030
|
+
...opts.plan ? { entity_type: "plan", entity_id: opts.plan } : {},
|
|
70031
|
+
...opts.run ? { entity_type: "run", entity_id: opts.run } : {}
|
|
70032
|
+
};
|
|
70033
|
+
const timeline2 = await cloudTimeline(cloud, cloudOptions);
|
|
70034
|
+
if (opts.json || globalOpts.json) {
|
|
70035
|
+
console.log(JSON.stringify(timeline2, null, 2));
|
|
70036
|
+
return;
|
|
70037
|
+
}
|
|
70038
|
+
if (timeline2.entries.length === 0) {
|
|
70039
|
+
console.log(chalk7.dim(" No activity yet."));
|
|
70040
|
+
return;
|
|
70041
|
+
}
|
|
70042
|
+
console.log(chalk7.bold(`Activity timeline (${timeline2.total}${timeline2.total > timeline2.entries.length ? `, showing ${timeline2.entries.length}` : ""}):
|
|
70043
|
+
`));
|
|
70044
|
+
for (const entry2 of timeline2.entries) {
|
|
70045
|
+
const time2 = entry2.created_at.replace("T", " ").slice(0, 16);
|
|
70046
|
+
const ref = entry2.run_id ? `run ${entry2.run_id.slice(0, 8)}` : `task ${entry2.task_id.slice(0, 8)}`;
|
|
70047
|
+
const agent = entry2.agent_id ? chalk7.dim(` (${entry2.agent_id})`) : "";
|
|
70048
|
+
const message = entry2.message ? ` ${entry2.message}` : "";
|
|
70049
|
+
console.log(` ${chalk7.dim(time2)} ${chalk7.cyan(entry2.source)} ${chalk7.dim(ref)} ${entry2.event_type}${message}${agent}`);
|
|
70050
|
+
}
|
|
70051
|
+
return;
|
|
70052
|
+
}
|
|
69420
70053
|
const { getLocalActivityTimeline: getLocalActivityTimeline2 } = await Promise.resolve().then(() => (init_activity_timeline(), exports_activity_timeline));
|
|
69421
70054
|
const { resolveTaskRunId: resolveTaskRunId2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
69422
70055
|
const options = {
|
|
@@ -69501,21 +70134,35 @@ Findings`));
|
|
|
69501
70134
|
return;
|
|
69502
70135
|
}
|
|
69503
70136
|
const db = getDatabase();
|
|
69504
|
-
const
|
|
70137
|
+
const cloud = getTodosCloudClient();
|
|
69505
70138
|
const { isLockExpired: isLockExpired2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
69506
70139
|
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
69507
70140
|
const filter = { status: "pending" };
|
|
69508
70141
|
if (projectId)
|
|
69509
70142
|
filter.project_id = projectId;
|
|
69510
|
-
const pending = listTasks(filter, db);
|
|
69511
|
-
|
|
69512
|
-
|
|
69513
|
-
|
|
69514
|
-
|
|
69515
|
-
|
|
69516
|
-
|
|
70143
|
+
const pending = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter, db);
|
|
70144
|
+
let ready;
|
|
70145
|
+
if (cloud) {
|
|
70146
|
+
const map = await cloudBlockingDepsMap(cloud, pending);
|
|
70147
|
+
ready = pending.filter((t) => {
|
|
70148
|
+
if (t.locked_by && !isLockExpired2(t.locked_at))
|
|
70149
|
+
return false;
|
|
70150
|
+
return !map.has(t.id);
|
|
70151
|
+
});
|
|
70152
|
+
} else {
|
|
70153
|
+
const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
70154
|
+
ready = pending.filter((t) => {
|
|
70155
|
+
if (t.locked_by && !isLockExpired2(t.locked_at))
|
|
70156
|
+
return false;
|
|
70157
|
+
return getBlockingDeps2(t.id, db).length === 0;
|
|
70158
|
+
});
|
|
70159
|
+
}
|
|
69517
70160
|
const limited2 = ready.slice(0, parseInt(opts.limit, 10));
|
|
69518
70161
|
if (opts.json || globalOpts.json) {
|
|
70162
|
+
if (cloud) {
|
|
70163
|
+
console.log(JSON.stringify(limited2));
|
|
70164
|
+
return;
|
|
70165
|
+
}
|
|
69519
70166
|
const { getTaskRouteState: getTaskRouteState2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
|
|
69520
70167
|
console.log(JSON.stringify(limited2.map((task2) => ({ ...task2, route_state: getTaskRouteState2(task2, db) }))));
|
|
69521
70168
|
return;
|
|
@@ -69535,19 +70182,29 @@ Findings`));
|
|
|
69535
70182
|
program2.command("sprint").description("Sprint dashboard: in-progress, next up, blockers, and overdue").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
69536
70183
|
const globalOpts = program2.opts();
|
|
69537
70184
|
const db = getDatabase();
|
|
69538
|
-
const
|
|
70185
|
+
const cloud = getTodosCloudClient();
|
|
69539
70186
|
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
69540
70187
|
const baseFilter = {};
|
|
69541
70188
|
if (projectId)
|
|
69542
70189
|
baseFilter.project_id = projectId;
|
|
69543
|
-
const inProgress = listTasks({ ...baseFilter, status: "in_progress" }, db);
|
|
69544
|
-
const pending = listTasks({ ...baseFilter, status: "pending" }, db);
|
|
70190
|
+
const inProgress = cloud ? await cloudListTasks(cloud, { ...baseFilter, status: "in_progress" }) : listTasks({ ...baseFilter, status: "in_progress" }, db);
|
|
70191
|
+
const pending = cloud ? await cloudListTasks(cloud, { ...baseFilter, status: "pending" }) : listTasks({ ...baseFilter, status: "pending" }, db);
|
|
69545
70192
|
const nowStr = new Date().toISOString();
|
|
69546
70193
|
const blocked = [];
|
|
69547
|
-
|
|
69548
|
-
const
|
|
69549
|
-
|
|
69550
|
-
|
|
70194
|
+
if (cloud) {
|
|
70195
|
+
const map = await cloudBlockingDepsMap(cloud, pending);
|
|
70196
|
+
for (const t of pending) {
|
|
70197
|
+
const blockers = map.get(t.id);
|
|
70198
|
+
if (blockers && blockers.length > 0)
|
|
70199
|
+
blocked.push({ task: t, blockers });
|
|
70200
|
+
}
|
|
70201
|
+
} else {
|
|
70202
|
+
const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
70203
|
+
for (const t of pending) {
|
|
70204
|
+
const blockers = getBlockingDeps2(t.id, db);
|
|
70205
|
+
if (blockers.length > 0)
|
|
70206
|
+
blocked.push({ task: t, blockers });
|
|
70207
|
+
}
|
|
69551
70208
|
}
|
|
69552
70209
|
const overdue = [...inProgress, ...pending].filter((t) => t.due_at && t.due_at < nowStr);
|
|
69553
70210
|
const blockedIds = new Set(blocked.map((b) => b.task.id));
|
|
@@ -69778,17 +70435,20 @@ Findings`));
|
|
|
69778
70435
|
program2.command("priorities").description("Show task counts grouped by priority").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
69779
70436
|
const globalOpts = program2.opts();
|
|
69780
70437
|
const db = getDatabase();
|
|
70438
|
+
const cloud = getTodosCloudClient();
|
|
69781
70439
|
const { countTasks: countTasks2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
70440
|
+
const countFn = cloud ? (f) => cloudCountTasks(cloud, f) : (f) => Promise.resolve(countTasks2(f, db));
|
|
69782
70441
|
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
69783
70442
|
const base = projectId ? { project_id: projectId } : {};
|
|
69784
70443
|
const priorities = ["critical", "high", "medium", "low", "none"];
|
|
69785
70444
|
const counts = {};
|
|
69786
70445
|
for (const p of priorities) {
|
|
70446
|
+
const base2 = { ...base, priority: p === "none" ? undefined : p };
|
|
69787
70447
|
counts[p] = {
|
|
69788
|
-
total:
|
|
69789
|
-
pending:
|
|
69790
|
-
in_progress:
|
|
69791
|
-
completed:
|
|
70448
|
+
total: await countFn(base2),
|
|
70449
|
+
pending: await countFn({ ...base2, status: "pending" }),
|
|
70450
|
+
in_progress: await countFn({ ...base2, status: "in_progress" }),
|
|
70451
|
+
completed: await countFn({ ...base2, status: "completed" })
|
|
69792
70452
|
};
|
|
69793
70453
|
}
|
|
69794
70454
|
if (opts.json || globalOpts.json) {
|
|
@@ -71258,25 +71918,34 @@ Commands:`));
|
|
|
71258
71918
|
});
|
|
71259
71919
|
program2.command("record-verification <task-id> <command>").description("Record a verification command and result for a task").option("--status <status>", "Verification status: passed, failed, or unknown", "unknown").option("--summary <text>", "Short output summary").option("--artifact <path>", "Artifact or log path").option("--agent <name>", "Agent that ran the command").action(async (taskId, command, opts) => {
|
|
71260
71920
|
const globalOpts = program2.opts();
|
|
71261
|
-
const resolvedId = resolveTaskId(taskId);
|
|
71262
71921
|
if (opts.status !== "passed" && opts.status !== "failed" && opts.status !== "unknown") {
|
|
71263
71922
|
console.error(chalk8.red("--status must be passed, failed, or unknown"));
|
|
71264
71923
|
process.exit(1);
|
|
71265
71924
|
}
|
|
71266
|
-
|
|
71267
|
-
|
|
71268
|
-
|
|
71269
|
-
|
|
71270
|
-
|
|
71271
|
-
|
|
71272
|
-
|
|
71273
|
-
|
|
71274
|
-
|
|
71275
|
-
|
|
71276
|
-
|
|
71277
|
-
|
|
71925
|
+
try {
|
|
71926
|
+
const cloud = getTodosCloudClient();
|
|
71927
|
+
const verification = cloud ? await cloudRecordVerification(cloud, resolveTaskId(taskId), {
|
|
71928
|
+
command,
|
|
71929
|
+
status: opts.status,
|
|
71930
|
+
output_summary: opts.summary,
|
|
71931
|
+
artifact_path: opts.artifact,
|
|
71932
|
+
agent_id: opts.agent
|
|
71933
|
+
}) : (await Promise.resolve().then(() => (init_task_commits(), exports_task_commits))).addTaskVerification({
|
|
71934
|
+
task_id: resolveTaskId(taskId),
|
|
71935
|
+
command,
|
|
71936
|
+
status: opts.status,
|
|
71937
|
+
output_summary: opts.summary,
|
|
71938
|
+
artifact_path: opts.artifact,
|
|
71939
|
+
agent_id: opts.agent
|
|
71940
|
+
});
|
|
71941
|
+
if (globalOpts.json) {
|
|
71942
|
+
output(verification, true);
|
|
71943
|
+
return;
|
|
71944
|
+
}
|
|
71945
|
+
console.log(chalk8.green(`Recorded ${verification.status} verification for task ${taskId}`));
|
|
71946
|
+
} catch (e) {
|
|
71947
|
+
handleError(e);
|
|
71278
71948
|
}
|
|
71279
|
-
console.log(chalk8.green(`Recorded ${verification.status} verification for task ${taskId}`));
|
|
71280
71949
|
});
|
|
71281
71950
|
program2.command("trace <task-id>").description("Show local git refs, commits, changed files, and verification commands for a task").action(async (taskId) => {
|
|
71282
71951
|
const globalOpts = program2.opts();
|
|
@@ -71982,6 +72651,7 @@ var HOME2;
|
|
|
71982
72651
|
var init_mcp_hooks_commands = __esm(() => {
|
|
71983
72652
|
init_tasks();
|
|
71984
72653
|
init_helpers();
|
|
72654
|
+
init_cloud_router();
|
|
71985
72655
|
HOME2 = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
71986
72656
|
});
|
|
71987
72657
|
|