@hasna/todos 0.13.10 → 0.13.12
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.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 +153 -102
- package/dist/contracts.js +92 -67
- package/dist/db/agent-metrics.d.ts.map +1 -1
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/budgets.d.ts.map +1 -1
- package/dist/db/database.d.ts +44 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/handoffs.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts +1 -0
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-files.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-relations.d.ts.map +1 -1
- package/dist/db/task-status.d.ts.map +1 -1
- package/dist/db/webhooks.d.ts.map +1 -1
- package/dist/index.js +119 -85
- package/dist/lib/assignee-validation.d.ts +22 -0
- package/dist/lib/assignee-validation.d.ts.map +1 -1
- package/dist/lib/capacity-forecasts.d.ts.map +1 -1
- package/dist/lib/completion-guard.d.ts.map +1 -1
- package/dist/lib/local-notifications.d.ts.map +1 -1
- package/dist/lib/local-reports.d.ts.map +1 -1
- package/dist/lib/notification-reminders.d.ts.map +1 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/usage-ledger.d.ts.map +1 -1
- package/dist/mcp/index.js +138 -93
- package/dist/mcp/tools/agents.d.ts.map +1 -1
- package/dist/mcp/tools/task-adv-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-auto-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-resources.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +92 -67
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +138 -93
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/storage.js +57 -42
- package/package.json +1 -1
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.12",
|
|
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",
|
|
@@ -6045,8 +6045,10 @@ async function cloudEscalatedTasks(client, opts = {}, at = new Date) {
|
|
|
6045
6045
|
const filter = {};
|
|
6046
6046
|
if (opts.project_id)
|
|
6047
6047
|
filter.project_id = opts.project_id;
|
|
6048
|
+
if (opts.agent_id)
|
|
6049
|
+
filter.assigned_to = opts.agent_id;
|
|
6048
6050
|
const active = await cloudActiveTasks(client, filter);
|
|
6049
|
-
return active.filter((t) => !t.archived_at
|
|
6051
|
+
return active.filter((t) => !t.archived_at).map((task) => {
|
|
6050
6052
|
const reasons = [];
|
|
6051
6053
|
const breachedTimes = [];
|
|
6052
6054
|
if (task.due_at) {
|
|
@@ -6258,7 +6260,11 @@ async function cloudRecap(client, hours, projectId) {
|
|
|
6258
6260
|
const blocked = pending.filter((t) => blockedMap.has(t.id));
|
|
6259
6261
|
const sinceMs = new Date(since).getTime();
|
|
6260
6262
|
const agentSummaries = agents.map((agent) => {
|
|
6261
|
-
const
|
|
6263
|
+
const assignedName = agent.name?.toLowerCase();
|
|
6264
|
+
const owned = all.filter((t) => {
|
|
6265
|
+
const assignedTo = t.assigned_to?.toLowerCase();
|
|
6266
|
+
return assignedTo === agent.id.toLowerCase() || assignedName && assignedTo === assignedName || t.agent_id === agent.id;
|
|
6267
|
+
});
|
|
6262
6268
|
return {
|
|
6263
6269
|
name: agent.name,
|
|
6264
6270
|
completed_count: owned.filter((t) => t.status === "completed" && t.completed_at != null && t.completed_at > since).length,
|
|
@@ -10714,14 +10720,17 @@ var exports_database = {};
|
|
|
10714
10720
|
__export(exports_database, {
|
|
10715
10721
|
uuid: () => uuid,
|
|
10716
10722
|
resolvePartialId: () => resolvePartialId,
|
|
10723
|
+
resolveAssignedToAliases: () => resolveAssignedToAliases,
|
|
10717
10724
|
resetDatabase: () => resetDatabase,
|
|
10718
10725
|
now: () => now,
|
|
10726
|
+
lowerInClause: () => lowerInClause,
|
|
10719
10727
|
lockExpiryCutoff: () => lockExpiryCutoff,
|
|
10720
10728
|
isLockExpired: () => isLockExpired,
|
|
10721
10729
|
getDatabasePath: () => getDatabasePath,
|
|
10722
10730
|
getDatabase: () => getDatabase,
|
|
10723
10731
|
closeDatabase: () => closeDatabase,
|
|
10724
10732
|
clearExpiredLocks: () => clearExpiredLocks,
|
|
10733
|
+
assignedToAliasSet: () => assignedToAliasSet,
|
|
10725
10734
|
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
10726
10735
|
});
|
|
10727
10736
|
import { Database } from "bun:sqlite";
|
|
@@ -10950,6 +10959,33 @@ function resolvePartialId(db, table, partialId) {
|
|
|
10950
10959
|
}
|
|
10951
10960
|
return null;
|
|
10952
10961
|
}
|
|
10962
|
+
function resolveAssignedToAliases(db, ref) {
|
|
10963
|
+
const aliases = new Set([ref]);
|
|
10964
|
+
let agentId;
|
|
10965
|
+
try {
|
|
10966
|
+
agentId = resolvePartialId(db, "agents", ref);
|
|
10967
|
+
} catch (err) {
|
|
10968
|
+
if (!(err instanceof IdentityAliasAmbiguousError))
|
|
10969
|
+
throw err;
|
|
10970
|
+
agentId = null;
|
|
10971
|
+
}
|
|
10972
|
+
if (agentId) {
|
|
10973
|
+
aliases.add(agentId);
|
|
10974
|
+
const row = db.query("SELECT name FROM agents WHERE id = ?").get(agentId);
|
|
10975
|
+
if (row?.name)
|
|
10976
|
+
aliases.add(row.name);
|
|
10977
|
+
}
|
|
10978
|
+
return [...aliases];
|
|
10979
|
+
}
|
|
10980
|
+
function assignedToAliasSet(db, ref) {
|
|
10981
|
+
return new Set(resolveAssignedToAliases(db, ref).map((a) => a.toLowerCase()));
|
|
10982
|
+
}
|
|
10983
|
+
function lowerInClause(column, values, params) {
|
|
10984
|
+
if (values.length === 0)
|
|
10985
|
+
return "1=0";
|
|
10986
|
+
params.push(...values.map((v) => v.toLowerCase()));
|
|
10987
|
+
return `LOWER(${column}) IN (${values.map(() => "?").join(",")})`;
|
|
10988
|
+
}
|
|
10953
10989
|
var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
|
|
10954
10990
|
var init_database = __esm(() => {
|
|
10955
10991
|
init_schema();
|
|
@@ -11740,17 +11776,19 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
11740
11776
|
throw new CompletionGuardError(`Too fast: task was started ${Math.floor(elapsedSeconds)}s ago. Minimum work duration is ${config.min_work_seconds}s. Wait ${remaining}s.`, remaining);
|
|
11741
11777
|
}
|
|
11742
11778
|
}
|
|
11779
|
+
const assignedAliasParams = [];
|
|
11780
|
+
const assignedInClause = agent ? lowerInClause("assigned_to", resolveAssignedToAliases(db, agent), assignedAliasParams) : "";
|
|
11743
11781
|
if (agent && config.max_completions_per_window && config.window_minutes) {
|
|
11744
11782
|
const windowStart = new Date(Date.now() - config.window_minutes * 60 * 1000).toISOString();
|
|
11745
11783
|
const result = db.query(`SELECT COUNT(*) as count FROM tasks
|
|
11746
|
-
WHERE completed_at > ? AND (
|
|
11784
|
+
WHERE completed_at > ? AND (${assignedInClause} OR agent_id = ?)`).get(windowStart, ...assignedAliasParams, agent);
|
|
11747
11785
|
if (result.count >= config.max_completions_per_window) {
|
|
11748
11786
|
throw new CompletionGuardError(`Rate limit: ${result.count} tasks completed in the last ${config.window_minutes} minutes (max ${config.max_completions_per_window}). Slow down.`);
|
|
11749
11787
|
}
|
|
11750
11788
|
}
|
|
11751
11789
|
if (agent && config.cooldown_seconds) {
|
|
11752
11790
|
const result = db.query(`SELECT MAX(completed_at) as last_completed FROM tasks
|
|
11753
|
-
WHERE completed_at IS NOT NULL AND (
|
|
11791
|
+
WHERE completed_at IS NOT NULL AND (${assignedInClause} OR agent_id = ?) AND id != ?`).get(...assignedAliasParams, agent, task.id);
|
|
11754
11792
|
if (result.last_completed) {
|
|
11755
11793
|
const elapsedSeconds = (Date.now() - new Date(result.last_completed).getTime()) / 1000;
|
|
11756
11794
|
if (elapsedSeconds < config.cooldown_seconds) {
|
|
@@ -11764,6 +11802,7 @@ var init_completion_guard = __esm(() => {
|
|
|
11764
11802
|
init_types();
|
|
11765
11803
|
init_config();
|
|
11766
11804
|
init_projects();
|
|
11805
|
+
init_database();
|
|
11767
11806
|
});
|
|
11768
11807
|
|
|
11769
11808
|
// src/lib/event-emission-safety.ts
|
|
@@ -13862,7 +13901,7 @@ function getRecap(hours = 8, projectId, db) {
|
|
|
13862
13901
|
const in_progress = projectId ? d.query(`SELECT id, short_id, title, assigned_to, started_at FROM tasks WHERE status = 'in_progress' AND project_id = ? ORDER BY updated_at DESC`).all(projectId) : d.query(`SELECT id, short_id, title, assigned_to, started_at FROM tasks WHERE status = 'in_progress' ORDER BY updated_at DESC`).all();
|
|
13863
13902
|
const blocked = projectId ? d.query(`SELECT DISTINCT t.id, t.short_id, t.title, t.assigned_to FROM tasks t JOIN task_dependencies td ON td.task_id = t.id JOIN tasks dep ON dep.id = td.depends_on AND dep.status NOT IN ('completed','cancelled') WHERE t.status = 'pending'${tpf}`).all(projectId) : d.query(`SELECT DISTINCT t.id, t.short_id, t.title, t.assigned_to FROM tasks t JOIN task_dependencies td ON td.task_id = t.id JOIN tasks dep ON dep.id = td.depends_on AND dep.status NOT IN ('completed','cancelled') WHERE t.status = 'pending'`).all();
|
|
13864
13903
|
const stale = projectId ? d.query(`SELECT id, short_id, title, assigned_to, updated_at FROM tasks WHERE status = 'in_progress' AND updated_at < ? AND project_id = ? ORDER BY updated_at ASC`).all(staleWindow, projectId) : d.query(`SELECT id, short_id, title, assigned_to, updated_at FROM tasks WHERE status = 'in_progress' AND updated_at < ? ORDER BY updated_at ASC`).all(staleWindow);
|
|
13865
|
-
const agents = projectId ? d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?${tpf}) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'in_progress'${tpf}) as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, projectId, projectId, since) : d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR t.agent_id = a.id) AND t.status = 'in_progress') as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, since);
|
|
13904
|
+
const agents = projectId ? d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?${tpf}) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'in_progress'${tpf}) as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, projectId, projectId, since) : d.query(`SELECT a.name, a.last_seen_at, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'completed' AND t.completed_at > ?) as completed_count, (SELECT COUNT(*) FROM tasks t WHERE (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name) OR t.agent_id = a.id) AND t.status = 'in_progress') as in_progress_count FROM agents a WHERE a.status = 'active' AND a.last_seen_at > ? ORDER BY completed_count DESC`).all(since, since);
|
|
13866
13905
|
return {
|
|
13867
13906
|
hours,
|
|
13868
13907
|
since,
|
|
@@ -14008,13 +14047,17 @@ function logDelivery(d, webhookId, event, payload, statusCode, response, attempt
|
|
|
14008
14047
|
const id = uuid();
|
|
14009
14048
|
d.run(`INSERT INTO webhook_deliveries (id, webhook_id, event, payload, status_code, response, attempt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, webhookId, event, payload, statusCode, response, attempt, now()]);
|
|
14010
14049
|
}
|
|
14011
|
-
function matchesScope(wh, payload) {
|
|
14050
|
+
function matchesScope(wh, payload, db) {
|
|
14012
14051
|
if (wh.project_id && payload.project_id !== wh.project_id)
|
|
14013
14052
|
return false;
|
|
14014
14053
|
if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
|
|
14015
14054
|
return false;
|
|
14016
|
-
if (wh.agent_id && payload.agent_id !== wh.agent_id
|
|
14017
|
-
|
|
14055
|
+
if (wh.agent_id && payload.agent_id !== wh.agent_id) {
|
|
14056
|
+
const aliasSet = assignedToAliasSet(db, wh.agent_id);
|
|
14057
|
+
const assignedTo = typeof payload.assigned_to === "string" ? payload.assigned_to.toLowerCase() : "";
|
|
14058
|
+
if (!aliasSet.has(assignedTo))
|
|
14059
|
+
return false;
|
|
14060
|
+
}
|
|
14018
14061
|
if (wh.task_id && payload.id !== wh.task_id)
|
|
14019
14062
|
return false;
|
|
14020
14063
|
return true;
|
|
@@ -14081,7 +14124,7 @@ async function dispatchWebhook2(event, payload, db) {
|
|
|
14081
14124
|
const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
14082
14125
|
const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
|
|
14083
14126
|
for (const wh of webhooks) {
|
|
14084
|
-
if (!matchesScope(wh, payloadObj))
|
|
14127
|
+
if (!matchesScope(wh, payloadObj, d))
|
|
14085
14128
|
continue;
|
|
14086
14129
|
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
14087
14130
|
deliverWebhook(wh, event, body, 1, d).catch((err) => {
|
|
@@ -15229,14 +15272,16 @@ function getNextTask(agentId, filters, db) {
|
|
|
15229
15272
|
conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
|
|
15230
15273
|
const where = conditions.join(" AND ");
|
|
15231
15274
|
let recentProjectIds = [];
|
|
15275
|
+
const assignedAliasParams = [];
|
|
15276
|
+
const assignedInClause = agentId ? lowerInClause("assigned_to", resolveAssignedToAliases(d, agentId), assignedAliasParams) : "";
|
|
15232
15277
|
if (agentId) {
|
|
15233
|
-
const recentRows = d.query(`SELECT DISTINCT project_id FROM tasks WHERE
|
|
15278
|
+
const recentRows = d.query(`SELECT DISTINCT project_id FROM tasks WHERE ${assignedInClause} AND status = 'completed' AND project_id IS NOT NULL ORDER BY completed_at DESC LIMIT 3`).all(...assignedAliasParams);
|
|
15234
15279
|
recentProjectIds = recentRows.map((r) => r.project_id);
|
|
15235
15280
|
}
|
|
15236
15281
|
let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
|
|
15237
15282
|
if (agentId) {
|
|
15238
|
-
sql += `CASE WHEN
|
|
15239
|
-
params.push(
|
|
15283
|
+
sql += `CASE WHEN ${assignedInClause} THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
|
|
15284
|
+
params.push(...assignedAliasParams);
|
|
15240
15285
|
}
|
|
15241
15286
|
if (recentProjectIds.length > 0) {
|
|
15242
15287
|
const placeholders = recentProjectIds.map(() => "?").join(",");
|
|
@@ -15630,30 +15675,6 @@ function getTaskWithRelations(id, db) {
|
|
|
15630
15675
|
checklist
|
|
15631
15676
|
};
|
|
15632
15677
|
}
|
|
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
|
-
}
|
|
15657
15678
|
function listTasks(filter = {}, db) {
|
|
15658
15679
|
const d = db || getDatabase();
|
|
15659
15680
|
const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
|
|
@@ -16126,6 +16147,7 @@ function deleteTask(id, db) {
|
|
|
16126
16147
|
var init_task_crud = __esm(() => {
|
|
16127
16148
|
init_types();
|
|
16128
16149
|
init_database();
|
|
16150
|
+
init_database();
|
|
16129
16151
|
init_completion_guard();
|
|
16130
16152
|
init_event_emission_safety();
|
|
16131
16153
|
init_event_hooks();
|
|
@@ -16288,8 +16310,8 @@ function getTaskStats(filters, db) {
|
|
|
16288
16310
|
params.push(filters.task_list_id);
|
|
16289
16311
|
}
|
|
16290
16312
|
if (filters?.agent_id) {
|
|
16291
|
-
|
|
16292
|
-
|
|
16313
|
+
params.push(filters.agent_id);
|
|
16314
|
+
conditions.push(`(agent_id = ? OR ${lowerInClause("assigned_to", resolveAssignedToAliases(d, filters.agent_id), params)})`);
|
|
16293
16315
|
}
|
|
16294
16316
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
16295
16317
|
const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
|
|
@@ -16472,8 +16494,7 @@ function getEscalatedTasks(opts = {}, db, at = new Date) {
|
|
|
16472
16494
|
params.push(opts.project_id);
|
|
16473
16495
|
}
|
|
16474
16496
|
if (opts.agent_id) {
|
|
16475
|
-
conditions.push("assigned_to
|
|
16476
|
-
params.push(opts.agent_id);
|
|
16497
|
+
conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
|
|
16477
16498
|
}
|
|
16478
16499
|
const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC, created_at ASC`).all(...params);
|
|
16479
16500
|
return rows.map(rowToTask).map((task) => {
|
|
@@ -16521,8 +16542,7 @@ function notifyUpcomingDeadlines(opts = {}, db) {
|
|
|
16521
16542
|
params.push(opts.project_id);
|
|
16522
16543
|
}
|
|
16523
16544
|
if (opts.agent_id) {
|
|
16524
|
-
conditions.push("assigned_to
|
|
16525
|
-
params.push(opts.agent_id);
|
|
16545
|
+
conditions.push(lowerInClause("assigned_to", resolveAssignedToAliases(d, opts.agent_id), params));
|
|
16526
16546
|
}
|
|
16527
16547
|
const rows = d.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")} ORDER BY due_at ASC`).all(...params);
|
|
16528
16548
|
return rows.map(rowToTask);
|
|
@@ -16780,13 +16800,14 @@ function getTimeReport(opts, db) {
|
|
|
16780
16800
|
params.push(opts.plan_id);
|
|
16781
16801
|
}
|
|
16782
16802
|
if (opts?.agent_id) {
|
|
16803
|
+
const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.agent_id), params);
|
|
16783
16804
|
conditions.push(`(
|
|
16784
|
-
|
|
16805
|
+
${assignedInClause}
|
|
16785
16806
|
OR t.agent_id = ?
|
|
16786
16807
|
OR EXISTS (SELECT 1 FROM task_time_logs ttl WHERE ttl.task_id = t.id AND ttl.agent_id = ?)
|
|
16787
16808
|
OR EXISTS (SELECT 1 FROM focus_sessions fs WHERE fs.task_id = t.id AND fs.agent_id = ?)
|
|
16788
16809
|
)`);
|
|
16789
|
-
params.push(opts.agent_id, opts.agent_id, opts.agent_id
|
|
16810
|
+
params.push(opts.agent_id, opts.agent_id, opts.agent_id);
|
|
16790
16811
|
}
|
|
16791
16812
|
if (opts?.since) {
|
|
16792
16813
|
conditions.push("(t.completed_at >= ? OR t.updated_at >= ?)");
|
|
@@ -17799,7 +17820,7 @@ function listActiveFiles(db) {
|
|
|
17799
17820
|
a.name AS agent_name
|
|
17800
17821
|
FROM task_files tf
|
|
17801
17822
|
JOIN tasks t ON tf.task_id = t.id
|
|
17802
|
-
LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
|
|
17823
|
+
LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name))))
|
|
17803
17824
|
WHERE t.status = 'in_progress'
|
|
17804
17825
|
AND tf.status != 'removed'
|
|
17805
17826
|
ORDER BY tf.updated_at DESC
|
|
@@ -19090,10 +19111,11 @@ function validateAssignee(input, ctx) {
|
|
|
19090
19111
|
if (ctx.allowSeat) {
|
|
19091
19112
|
return { ok: true, assignee: byId ? byId.name : raw, agentId: byId?.id, isSeat: true };
|
|
19092
19113
|
}
|
|
19114
|
+
const hint = ctx.seatHint?.(raw);
|
|
19093
19115
|
return {
|
|
19094
19116
|
ok: false,
|
|
19095
19117
|
reason: "seat",
|
|
19096
|
-
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
|
|
19118
|
+
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).")
|
|
19097
19119
|
};
|
|
19098
19120
|
}
|
|
19099
19121
|
if (byId) {
|
|
@@ -19821,10 +19843,10 @@ var init_agents = __esm(() => {
|
|
|
19821
19843
|
|
|
19822
19844
|
// src/cli/assignee-guard.ts
|
|
19823
19845
|
import chalk2 from "chalk";
|
|
19824
|
-
async function resolveValidatedAssignee(value, allowSeat) {
|
|
19846
|
+
async function resolveValidatedAssignee(value, allowSeat, buildSeatHint) {
|
|
19825
19847
|
const cloud = getTodosCloudClient();
|
|
19826
19848
|
const ctx = await loadAssigneeContext(() => cloud ? cloudListAgents(cloud) : listAgents(), allowSeat);
|
|
19827
|
-
const verdict = validateAssignee(value, ctx);
|
|
19849
|
+
const verdict = validateAssignee(value, { ...ctx, seatHint: buildSeatHint });
|
|
19828
19850
|
if (!verdict.ok) {
|
|
19829
19851
|
handleError(new Error(`Cannot assign to '${value}'. ${verdict.message}`));
|
|
19830
19852
|
}
|
|
@@ -20344,7 +20366,7 @@ function registerTaskCommands(program2) {
|
|
|
20344
20366
|
opts.list = opts.list || opts.taskList;
|
|
20345
20367
|
const creator = resolveCreatorIdentity(opts.createdBy || globalOpts.agent);
|
|
20346
20368
|
const router = resolveWritableIdentity(opts.createdBy || globalOpts.agent);
|
|
20347
|
-
const requestedAssign = opts.assign ? await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat)) : undefined;
|
|
20369
|
+
const requestedAssign = opts.assign ? await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`) : undefined;
|
|
20348
20370
|
const assignee = requestedAssign || (opts.unassigned ? undefined : router.agent_id || undefined);
|
|
20349
20371
|
if (!assignee && !opts.unassigned) {
|
|
20350
20372
|
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."));
|
|
@@ -20447,7 +20469,7 @@ function registerTaskCommands(program2) {
|
|
|
20447
20469
|
opts.tags = opts.tags || opts.tag;
|
|
20448
20470
|
opts.list = opts.list || opts.taskList;
|
|
20449
20471
|
if (opts.assign) {
|
|
20450
|
-
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat));
|
|
20472
|
+
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`);
|
|
20451
20473
|
}
|
|
20452
20474
|
const explicitProject = opts.project || globalOpts.project;
|
|
20453
20475
|
const cloud = getTodosCloudClient();
|
|
@@ -21108,7 +21130,7 @@ ${chalk3.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
21108
21130
|
handleError(new Error("Use either --working-dir or --clear-working-dir, not both."));
|
|
21109
21131
|
}
|
|
21110
21132
|
if (opts.assign) {
|
|
21111
|
-
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat));
|
|
21133
|
+
opts.assign = await resolveValidatedAssignee(opts.assign, Boolean(opts.assignSeat), (v) => `--assign ${v} --assign-seat`);
|
|
21112
21134
|
}
|
|
21113
21135
|
const cloud = getTodosCloudClient();
|
|
21114
21136
|
if (cloud) {
|
|
@@ -23367,8 +23389,7 @@ function searchTasks(options, projectId, taskListId, db) {
|
|
|
23367
23389
|
}
|
|
23368
23390
|
}
|
|
23369
23391
|
if (opts.assigned_to) {
|
|
23370
|
-
sql +=
|
|
23371
|
-
params.push(opts.assigned_to);
|
|
23392
|
+
sql += ` AND ${lowerInClause("t.assigned_to", resolveAssignedToAliases(d, opts.assigned_to), params)}`;
|
|
23372
23393
|
}
|
|
23373
23394
|
if (opts.agent_id) {
|
|
23374
23395
|
sql += " AND t.agent_id = ?";
|
|
@@ -34898,8 +34919,9 @@ async function handleAgentMe(_req, url, _ctx, json2, taskToSummary2) {
|
|
|
34898
34919
|
}
|
|
34899
34920
|
}
|
|
34900
34921
|
function handleAgentQueue(agentId, _ctx, json2, taskToSummary2) {
|
|
34922
|
+
const aliasSet = assignedToAliasSet(getDatabase(), agentId);
|
|
34901
34923
|
const pending = listTasks({ status: "pending" });
|
|
34902
|
-
const queue = pending.filter((t) => t.assigned_to
|
|
34924
|
+
const queue = pending.filter((t) => aliasSet.has((t.assigned_to ?? "").toLowerCase()) || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
|
|
34903
34925
|
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
34904
34926
|
queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
34905
34927
|
return json2(queue.map((t) => taskToSummary2(t)));
|
|
@@ -44586,11 +44608,12 @@ function runAlerts(input, db, checkedAt, quieted) {
|
|
|
44586
44608
|
if (input.include_runs === false)
|
|
44587
44609
|
return [];
|
|
44588
44610
|
const since = Date.parse(input.run_since || new Date(Date.parse(checkedAt) - 24 * 60 * 60000).toISOString());
|
|
44611
|
+
const runAlertAliases = input.agent_id ? assignedToAliasSet(db, input.agent_id) : null;
|
|
44589
44612
|
return listTaskRuns(undefined, db).filter((run) => run.completed_at && Date.parse(run.completed_at) >= since).map((run) => {
|
|
44590
44613
|
const task = getTask(run.task_id, db);
|
|
44591
44614
|
if (input.project_id && task?.project_id !== input.project_id)
|
|
44592
44615
|
return null;
|
|
44593
|
-
if (input.agent_id && run.agent_id !== input.agent_id && task?.assigned_to
|
|
44616
|
+
if (input.agent_id && run.agent_id !== input.agent_id && !(runAlertAliases && runAlertAliases.has((task?.assigned_to ?? "").toLowerCase())))
|
|
44594
44617
|
return null;
|
|
44595
44618
|
const failed = ["failed", "cancelled"].includes(run.status);
|
|
44596
44619
|
return {
|
|
@@ -44635,6 +44658,7 @@ async function checkLocalNotifications(input = {}, db) {
|
|
|
44635
44658
|
const warnings = [];
|
|
44636
44659
|
if (Number.isNaN(checkedMs))
|
|
44637
44660
|
warnings.push("Invalid check timestamp; due-soon window may be incomplete.");
|
|
44661
|
+
const notifyAgentAliases = input.agent_id ? assignedToAliasSet(d, input.agent_id) : null;
|
|
44638
44662
|
const alerts = [];
|
|
44639
44663
|
for (const escalation of getEscalatedTasks({ project_id: input.project_id, agent_id: input.agent_id }, d, new Date(checkedAt))) {
|
|
44640
44664
|
for (const reason of escalation.reasons) {
|
|
@@ -44646,7 +44670,7 @@ async function checkLocalNotifications(input = {}, db) {
|
|
|
44646
44670
|
for (const task of listTasks({ project_id: input.project_id, include_archived: false, limit: 5000 }, d)) {
|
|
44647
44671
|
if (!unfinished(task) || !task.due_at)
|
|
44648
44672
|
continue;
|
|
44649
|
-
if (input.agent_id && task.
|
|
44673
|
+
if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
|
|
44650
44674
|
continue;
|
|
44651
44675
|
if (task.due_at > checkedAt && task.due_at <= dueSoonUntil) {
|
|
44652
44676
|
alerts.push(taskAlert(task, "task_due_soon", "task.due_soon", "warning", `Due soon: ${task.title}`, task.due_at, quieted));
|
|
@@ -44654,7 +44678,7 @@ async function checkLocalNotifications(input = {}, db) {
|
|
|
44654
44678
|
}
|
|
44655
44679
|
}
|
|
44656
44680
|
for (const task of getStaleTasks(staleMinutes, { project_id: input.project_id }, d)) {
|
|
44657
|
-
if (input.agent_id && task.
|
|
44681
|
+
if (input.agent_id && task.agent_id !== input.agent_id && !(notifyAgentAliases && notifyAgentAliases.has((task.assigned_to ?? "").toLowerCase())))
|
|
44658
44682
|
continue;
|
|
44659
44683
|
alerts.push(taskAlert(task, "task_stale", "task.stale", "warning", `Stale task: ${task.title}`, checkedAt, quieted, { stale_minutes: staleMinutes }));
|
|
44660
44684
|
}
|
|
@@ -46479,10 +46503,12 @@ function matchesProfile(profile, query) {
|
|
|
46479
46503
|
return false;
|
|
46480
46504
|
return true;
|
|
46481
46505
|
}
|
|
46482
|
-
function taskMatchesAgent(task, agentId) {
|
|
46506
|
+
function taskMatchesAgent(task, agentId, aliasSet) {
|
|
46483
46507
|
if (!agentId)
|
|
46484
46508
|
return true;
|
|
46485
|
-
|
|
46509
|
+
if (task.agent_id === agentId)
|
|
46510
|
+
return true;
|
|
46511
|
+
return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
|
|
46486
46512
|
}
|
|
46487
46513
|
function estimateRemaining(task) {
|
|
46488
46514
|
if (task.status === "completed" || task.status === "cancelled")
|
|
@@ -46552,7 +46578,8 @@ function removeCapacityProfile(idOrAgent, projectId) {
|
|
|
46552
46578
|
}
|
|
46553
46579
|
function getPlanningForecast(input = {}, db) {
|
|
46554
46580
|
const startDate = input.start_date ? dateOnly(input.start_date) : todayIso();
|
|
46555
|
-
const
|
|
46581
|
+
const aliasSet = input.agent_id ? assignedToAliasSet(db || getDatabase(), input.agent_id) : undefined;
|
|
46582
|
+
const tasks = listTasks({ project_id: input.project_id, plan_id: input.plan_id, limit: 1e4 }, db).filter((task) => taskMatchesAgent(task, input.agent_id, aliasSet));
|
|
46556
46583
|
const reportByTask = new Map(getTimeReport({ project_id: input.project_id, plan_id: input.plan_id, agent_id: input.agent_id, include_open: true }, db).map((entry2) => [entry2.task_id, entry2]));
|
|
46557
46584
|
const profiles = listCapacityProfiles({ agent_id: input.agent_id, project_id: input.project_id });
|
|
46558
46585
|
const globalProfiles = input.project_id ? listCapacityProfiles({ agent_id: input.agent_id }).filter((profile) => profile.project_id === null) : [];
|
|
@@ -46650,6 +46677,7 @@ function renderPlanningForecastMarkdown(forecast) {
|
|
|
46650
46677
|
}
|
|
46651
46678
|
var LOCAL_CAPACITY_SCHEMA_VERSION = 1;
|
|
46652
46679
|
var init_capacity_forecasts = __esm(() => {
|
|
46680
|
+
init_database();
|
|
46653
46681
|
init_tasks();
|
|
46654
46682
|
init_task_relations();
|
|
46655
46683
|
init_config();
|
|
@@ -49759,8 +49787,10 @@ function getLatestHandoff(agentId, projectId, db) {
|
|
|
49759
49787
|
function createSessionRecoveryHandoff(input, db) {
|
|
49760
49788
|
const d = db || getDatabase();
|
|
49761
49789
|
const limit = input.limit || 20;
|
|
49762
|
-
const
|
|
49763
|
-
const
|
|
49790
|
+
const params = [];
|
|
49791
|
+
const assignedInClause = lowerInClause("assigned_to", resolveAssignedToAliases(d, input.agent_id), params);
|
|
49792
|
+
const conditions = ["status = 'in_progress'", `(${assignedInClause} OR agent_id = ? OR locked_by = ?)`];
|
|
49793
|
+
params.push(input.agent_id, input.agent_id);
|
|
49764
49794
|
if (input.session_id) {
|
|
49765
49795
|
conditions.push("session_id = ?");
|
|
49766
49796
|
params.push(input.session_id);
|
|
@@ -50352,7 +50382,8 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
50352
50382
|
const assigned = listTasks4({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
|
|
50353
50383
|
const now4 = Date.now();
|
|
50354
50384
|
const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
|
|
50355
|
-
const
|
|
50385
|
+
const blockedAliases = assignedToAliasSet(getDatabase(), effectiveAgentId);
|
|
50386
|
+
const blocked = getBlockedTasks2().filter((t) => blockedAliases.has((t.assigned_to ?? "").toLowerCase()));
|
|
50356
50387
|
const workload = {
|
|
50357
50388
|
in_progress: assigned.filter((t) => t.status === "in_progress").length,
|
|
50358
50389
|
pending: assigned.filter((t) => t.status === "pending").length,
|
|
@@ -50389,17 +50420,25 @@ function registerTaskAutoTools(server, ctx) {
|
|
|
50389
50420
|
if (agents.length === 0)
|
|
50390
50421
|
return { content: [{ type: "text", text: "No active agents available for rebalancing." }] };
|
|
50391
50422
|
const activeTasks = listTasks4({ project_id: resolvedProjectId, status: ["pending", "in_progress"], limit: 1000 }, undefined);
|
|
50392
|
-
const
|
|
50423
|
+
const agentKeyByAlias = new Map;
|
|
50424
|
+
for (const agent of agents) {
|
|
50425
|
+
agentKeyByAlias.set(String(agent.id).toLowerCase(), agent.id);
|
|
50426
|
+
if (agent.name)
|
|
50427
|
+
agentKeyByAlias.set(String(agent.name).toLowerCase(), agent.id);
|
|
50428
|
+
}
|
|
50429
|
+
const canonicalAgentKey = (assignedTo) => assignedTo ? agentKeyByAlias.get(assignedTo.toLowerCase()) : undefined;
|
|
50430
|
+
const load = new Map(agents.map((agent) => [agent.id, activeTasks.filter((t) => canonicalAgentKey(t.assigned_to) === agent.id).length]));
|
|
50393
50431
|
let moved = 0;
|
|
50394
50432
|
let skipped = 0;
|
|
50395
|
-
for (const task of activeTasks.filter((t) => t.status === "pending" && t.assigned_to && (load.get(t.assigned_to) ?? 0) > limit)) {
|
|
50396
|
-
const
|
|
50433
|
+
for (const task of activeTasks.filter((t) => t.status === "pending" && canonicalAgentKey(t.assigned_to) && (load.get(canonicalAgentKey(t.assigned_to)) ?? 0) > limit)) {
|
|
50434
|
+
const currentAgentKey = canonicalAgentKey(task.assigned_to);
|
|
50435
|
+
const target = agents.filter((agent) => agent.id !== currentAgentKey).sort((a, b) => (load.get(a.id) ?? 0) - (load.get(b.id) ?? 0))[0];
|
|
50397
50436
|
if (!target || (load.get(target.id) ?? 0) >= limit) {
|
|
50398
50437
|
skipped++;
|
|
50399
50438
|
continue;
|
|
50400
50439
|
}
|
|
50401
50440
|
updateTask3(task.id, { assigned_to: target.id, version: task.version });
|
|
50402
|
-
load.set(
|
|
50441
|
+
load.set(currentAgentKey, (load.get(currentAgentKey) ?? 1) - 1);
|
|
50403
50442
|
load.set(target.id, (load.get(target.id) ?? 0) + 1);
|
|
50404
50443
|
moved++;
|
|
50405
50444
|
}
|
|
@@ -50578,6 +50617,7 @@ ${lines.join(`
|
|
|
50578
50617
|
var init_task_auto_tools = __esm(() => {
|
|
50579
50618
|
init_zod();
|
|
50580
50619
|
init_cloud_router();
|
|
50620
|
+
init_database();
|
|
50581
50621
|
});
|
|
50582
50622
|
|
|
50583
50623
|
// src/lib/context-packs.ts
|
|
@@ -51757,7 +51797,8 @@ ${JSON.stringify(task.metadata, null, 2)}` : null
|
|
|
51757
51797
|
}, undefined);
|
|
51758
51798
|
const completedYesterday = completed.filter((t) => t.completed_at && t.completed_at.startsWith(yesterdayStr));
|
|
51759
51799
|
const { getBlockedTasks: getBlockedTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
51760
|
-
const
|
|
51800
|
+
const blockedAliases = effectiveAgentId ? assignedToAliasSet(getDatabase(), effectiveAgentId) : null;
|
|
51801
|
+
const blocked = getBlockedTasks2(effectiveProjectId ? resolveId(effectiveProjectId, "projects") : undefined).filter((t) => blockedAliases ? blockedAliases.has((t.assigned_to ?? "").toLowerCase()) : false);
|
|
51761
51802
|
const lines = [
|
|
51762
51803
|
`Standup for ${effectiveAgentId} (${effectiveProjectId ? `project: ${effectiveProjectId.slice(0, 8)}` : "all projects"})`,
|
|
51763
51804
|
inProgress.length > 0 ? `
|
|
@@ -51985,6 +52026,7 @@ var init_task_adv_tools = __esm(() => {
|
|
|
51985
52026
|
init_zod();
|
|
51986
52027
|
init_token_utils();
|
|
51987
52028
|
init_cloud_router();
|
|
52029
|
+
init_database();
|
|
51988
52030
|
});
|
|
51989
52031
|
|
|
51990
52032
|
// src/mcp/tools/task-meta-tools.ts
|
|
@@ -54181,27 +54223,29 @@ function getAgentMetrics(agentId, opts, db) {
|
|
|
54181
54223
|
const agent = d.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
|
|
54182
54224
|
if (!agent)
|
|
54183
54225
|
return null;
|
|
54226
|
+
const assignedAliases = resolveAssignedToAliases(d, agent.id).map((a) => a.toLowerCase());
|
|
54227
|
+
const assignedInClause = `LOWER(assigned_to) IN (${assignedAliases.map(() => "?").join(",")})`;
|
|
54184
54228
|
let projectFilter = "";
|
|
54185
|
-
const params = [agent.id,
|
|
54229
|
+
const params = [agent.id, ...assignedAliases];
|
|
54186
54230
|
if (opts?.project_id) {
|
|
54187
54231
|
projectFilter = " AND project_id = ?";
|
|
54188
54232
|
params.push(opts.project_id);
|
|
54189
54233
|
}
|
|
54190
|
-
const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR
|
|
54191
|
-
const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR
|
|
54192
|
-
const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR
|
|
54234
|
+
const completed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}`).get(...params).count;
|
|
54235
|
+
const failed = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'failed'${projectFilter}`).get(...params).count;
|
|
54236
|
+
const inProgress = d.query(`SELECT COUNT(*) as count FROM tasks WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'in_progress'${projectFilter}`).get(...params).count;
|
|
54193
54237
|
const total = completed + failed;
|
|
54194
54238
|
const completionRate = total > 0 ? completed / total : 0;
|
|
54195
54239
|
const avgTime = d.query(`SELECT AVG(
|
|
54196
54240
|
(julianday(completed_at) - julianday(created_at)) * 24 * 60
|
|
54197
54241
|
) as avg_minutes
|
|
54198
54242
|
FROM tasks
|
|
54199
|
-
WHERE (agent_id = ? OR
|
|
54243
|
+
WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND completed_at IS NOT NULL${projectFilter}`).get(...params);
|
|
54200
54244
|
const avgConf = d.query(`SELECT AVG(confidence) as avg_confidence
|
|
54201
54245
|
FROM tasks
|
|
54202
|
-
WHERE (agent_id = ? OR
|
|
54246
|
+
WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed' AND confidence IS NOT NULL${projectFilter}`).get(...params);
|
|
54203
54247
|
const reviewTasks = d.query(`SELECT metadata FROM tasks
|
|
54204
|
-
WHERE (agent_id = ? OR
|
|
54248
|
+
WHERE (agent_id = ? OR ${assignedInClause}) AND status = 'completed'${projectFilter}
|
|
54205
54249
|
AND metadata LIKE '%_review_score%'`).all(...params);
|
|
54206
54250
|
let reviewScoreAvg = null;
|
|
54207
54251
|
if (reviewTasks.length > 0) {
|
|
@@ -54671,14 +54715,15 @@ function millisBetween(start, end) {
|
|
|
54671
54715
|
return 0;
|
|
54672
54716
|
return endMs - startMs;
|
|
54673
54717
|
}
|
|
54674
|
-
function addTaskScope(where, params, options, alias = "t") {
|
|
54718
|
+
function addTaskScope(where, params, options, db, alias = "t") {
|
|
54675
54719
|
if (options.project_id) {
|
|
54676
54720
|
where.push(`${alias}.project_id = ?`);
|
|
54677
54721
|
params.push(options.project_id);
|
|
54678
54722
|
}
|
|
54679
54723
|
if (options.agent_id) {
|
|
54680
|
-
|
|
54681
|
-
|
|
54724
|
+
params.push(options.agent_id);
|
|
54725
|
+
const assignedInClause = lowerInClause(`${alias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
|
|
54726
|
+
where.push(`(${alias}.agent_id = ? OR ${assignedInClause})`);
|
|
54682
54727
|
}
|
|
54683
54728
|
if (options.since) {
|
|
54684
54729
|
where.push(`${alias}.created_at >= ?`);
|
|
@@ -54689,14 +54734,15 @@ function addTaskScope(where, params, options, alias = "t") {
|
|
|
54689
54734
|
params.push(options.until);
|
|
54690
54735
|
}
|
|
54691
54736
|
}
|
|
54692
|
-
function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
|
|
54737
|
+
function addRunScope(where, params, options, db, runAlias = "r", taskAlias = "t") {
|
|
54693
54738
|
if (options.project_id) {
|
|
54694
54739
|
where.push(`${taskAlias}.project_id = ?`);
|
|
54695
54740
|
params.push(options.project_id);
|
|
54696
54741
|
}
|
|
54697
54742
|
if (options.agent_id) {
|
|
54698
|
-
|
|
54699
|
-
|
|
54743
|
+
params.push(options.agent_id, options.agent_id);
|
|
54744
|
+
const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
|
|
54745
|
+
where.push(`(${runAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
|
|
54700
54746
|
}
|
|
54701
54747
|
if (options.since) {
|
|
54702
54748
|
where.push(`${runAlias}.started_at >= ?`);
|
|
@@ -54707,14 +54753,15 @@ function addRunScope(where, params, options, runAlias = "r", taskAlias = "t") {
|
|
|
54707
54753
|
params.push(options.until);
|
|
54708
54754
|
}
|
|
54709
54755
|
}
|
|
54710
|
-
function addTraceScope(where, params, options, traceAlias = "tr", taskAlias = "t") {
|
|
54756
|
+
function addTraceScope(where, params, options, db, traceAlias = "tr", taskAlias = "t") {
|
|
54711
54757
|
if (options.project_id) {
|
|
54712
54758
|
where.push(`${taskAlias}.project_id = ?`);
|
|
54713
54759
|
params.push(options.project_id);
|
|
54714
54760
|
}
|
|
54715
54761
|
if (options.agent_id) {
|
|
54716
|
-
|
|
54717
|
-
|
|
54762
|
+
params.push(options.agent_id, options.agent_id);
|
|
54763
|
+
const assignedInClause = lowerInClause(`${taskAlias}.assigned_to`, resolveAssignedToAliases(db, options.agent_id), params);
|
|
54764
|
+
where.push(`(${traceAlias}.agent_id = ? OR ${taskAlias}.agent_id = ? OR ${assignedInClause})`);
|
|
54718
54765
|
}
|
|
54719
54766
|
if (options.since) {
|
|
54720
54767
|
where.push(`${traceAlias}.created_at >= ?`);
|
|
@@ -54774,7 +54821,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
54774
54821
|
const generatedAt = options.generated_at || new Date().toISOString();
|
|
54775
54822
|
const taskWhere = [];
|
|
54776
54823
|
const taskParams = [];
|
|
54777
|
-
addTaskScope(taskWhere, taskParams, options);
|
|
54824
|
+
addTaskScope(taskWhere, taskParams, options, d);
|
|
54778
54825
|
const taskClause = taskWhere.length ? `WHERE ${taskWhere.join(" AND ")}` : "";
|
|
54779
54826
|
const taskTotals = queryOne(d, `SELECT COUNT(*) as tasks, COALESCE(SUM(cost_tokens), 0) as task_tokens, COALESCE(SUM(cost_usd), 0) as task_cost_usd FROM tasks t ${taskClause}`, taskParams);
|
|
54780
54827
|
let projectCount = 0;
|
|
@@ -54783,14 +54830,14 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
54783
54830
|
} else if (options.agent_id) {
|
|
54784
54831
|
const projectWhere = ["t.project_id IS NOT NULL"];
|
|
54785
54832
|
const projectParams = [];
|
|
54786
|
-
addTaskScope(projectWhere, projectParams, options);
|
|
54833
|
+
addTaskScope(projectWhere, projectParams, options, d);
|
|
54787
54834
|
projectCount = queryOne(d, `SELECT COUNT(DISTINCT t.project_id) as count FROM tasks t WHERE ${projectWhere.join(" AND ")}`, projectParams).count;
|
|
54788
54835
|
} else {
|
|
54789
54836
|
projectCount = queryOne(d, "SELECT COUNT(*) as count FROM projects", []).count;
|
|
54790
54837
|
}
|
|
54791
54838
|
const runWhere = [];
|
|
54792
54839
|
const runParams = [];
|
|
54793
|
-
addRunScope(runWhere, runParams, options);
|
|
54840
|
+
addRunScope(runWhere, runParams, options, d);
|
|
54794
54841
|
const runClause = runWhere.length ? `WHERE ${runWhere.join(" AND ")}` : "";
|
|
54795
54842
|
const runs = queryAll(d, `SELECT r.id, r.started_at, r.completed_at, r.metadata
|
|
54796
54843
|
FROM task_runs r JOIN tasks t ON t.id = r.task_id
|
|
@@ -54807,7 +54854,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
54807
54854
|
${runClause}`, runParams);
|
|
54808
54855
|
const traceWhere = [];
|
|
54809
54856
|
const traceParams = [];
|
|
54810
|
-
addTraceScope(traceWhere, traceParams, options);
|
|
54857
|
+
addTraceScope(traceWhere, traceParams, options, d);
|
|
54811
54858
|
const traceClause = traceWhere.length ? `WHERE ${traceWhere.join(" AND ")}` : "";
|
|
54812
54859
|
const traceTotals = queryOne(d, `SELECT COUNT(*) as traces,
|
|
54813
54860
|
COALESCE(SUM(tr.tokens), 0) as tokens,
|
|
@@ -54951,10 +54998,12 @@ function limitValue(value) {
|
|
|
54951
54998
|
function isTerminal2(task) {
|
|
54952
54999
|
return task.status === "completed" || task.status === "failed" || task.status === "cancelled";
|
|
54953
55000
|
}
|
|
54954
|
-
function sameAgent(task, agentId) {
|
|
55001
|
+
function sameAgent(task, agentId, aliasSet) {
|
|
54955
55002
|
if (!agentId)
|
|
54956
55003
|
return true;
|
|
54957
|
-
|
|
55004
|
+
if (task.agent_id === agentId)
|
|
55005
|
+
return true;
|
|
55006
|
+
return aliasSet ? aliasSet.has((task.assigned_to ?? "").toLowerCase()) : task.assigned_to === agentId;
|
|
54958
55007
|
}
|
|
54959
55008
|
function withinTaskWindow(task, options) {
|
|
54960
55009
|
const time = Date.parse(task.updated_at);
|
|
@@ -54967,11 +55016,12 @@ function withinTaskWindow(task, options) {
|
|
|
54967
55016
|
return true;
|
|
54968
55017
|
}
|
|
54969
55018
|
function scopedTasks(options, db) {
|
|
55019
|
+
const aliasSet = options.agent_id ? assignedToAliasSet(db, options.agent_id) : undefined;
|
|
54970
55020
|
return listTasks({
|
|
54971
55021
|
project_id: options.project_id,
|
|
54972
55022
|
plan_id: options.plan_id,
|
|
54973
55023
|
include_archived: false
|
|
54974
|
-
}, db).filter((task) => sameAgent(task, options.agent_id) && withinTaskWindow(task, options));
|
|
55024
|
+
}, db).filter((task) => sameAgent(task, options.agent_id, aliasSet) && withinTaskWindow(task, options));
|
|
54975
55025
|
}
|
|
54976
55026
|
function summarizeTask(task) {
|
|
54977
55027
|
return {
|
|
@@ -55037,7 +55087,7 @@ function initialAgentSummary(agentId) {
|
|
|
55037
55087
|
}
|
|
55038
55088
|
};
|
|
55039
55089
|
}
|
|
55040
|
-
function addScopeClauses(where, params, options, timeColumn) {
|
|
55090
|
+
function addScopeClauses(where, params, options, timeColumn, db) {
|
|
55041
55091
|
if (options.project_id) {
|
|
55042
55092
|
where.push("t.project_id = ?");
|
|
55043
55093
|
params.push(options.project_id);
|
|
@@ -55047,8 +55097,9 @@ function addScopeClauses(where, params, options, timeColumn) {
|
|
|
55047
55097
|
params.push(options.plan_id);
|
|
55048
55098
|
}
|
|
55049
55099
|
if (options.agent_id) {
|
|
55050
|
-
|
|
55051
|
-
|
|
55100
|
+
params.push(options.agent_id, options.agent_id);
|
|
55101
|
+
const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
|
|
55102
|
+
where.push(`(r.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
|
|
55052
55103
|
}
|
|
55053
55104
|
if (options.since) {
|
|
55054
55105
|
where.push(`${timeColumn} >= ?`);
|
|
@@ -55062,7 +55113,7 @@ function addScopeClauses(where, params, options, timeColumn) {
|
|
|
55062
55113
|
function loadRuns(options, db) {
|
|
55063
55114
|
const where = ["t.archived_at IS NULL"];
|
|
55064
55115
|
const params = [];
|
|
55065
|
-
addScopeClauses(where, params, options, "r.started_at");
|
|
55116
|
+
addScopeClauses(where, params, options, "r.started_at", db);
|
|
55066
55117
|
return db.query(`
|
|
55067
55118
|
SELECT
|
|
55068
55119
|
r.id,
|
|
@@ -55102,8 +55153,9 @@ function loadVerifications(options, db) {
|
|
|
55102
55153
|
params.push(options.plan_id);
|
|
55103
55154
|
}
|
|
55104
55155
|
if (options.agent_id) {
|
|
55105
|
-
|
|
55106
|
-
|
|
55156
|
+
params.push(options.agent_id, options.agent_id);
|
|
55157
|
+
const assignedInClause = lowerInClause("t.assigned_to", resolveAssignedToAliases(db, options.agent_id), params);
|
|
55158
|
+
where.push(`(v.agent_id = ? OR t.agent_id = ? OR ${assignedInClause})`);
|
|
55107
55159
|
}
|
|
55108
55160
|
if (options.since) {
|
|
55109
55161
|
where.push("v.run_at >= ?");
|
|
@@ -55325,7 +55377,6 @@ function renderLocalReportMarkdown(report) {
|
|
|
55325
55377
|
}
|
|
55326
55378
|
var LOCAL_REPORT_SCHEMA_VERSION = 1, LOCAL_REPORT_TYPES;
|
|
55327
55379
|
var init_local_reports = __esm(() => {
|
|
55328
|
-
init_database();
|
|
55329
55380
|
init_database();
|
|
55330
55381
|
init_plans();
|
|
55331
55382
|
init_tasks();
|
|
@@ -58381,7 +58432,7 @@ ${lines.join(`
|
|
|
58381
58432
|
a.name AS agent_name
|
|
58382
58433
|
FROM task_files tf
|
|
58383
58434
|
JOIN tasks t ON tf.task_id = t.id
|
|
58384
|
-
LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND t.assigned_to = a.id))
|
|
58435
|
+
LEFT JOIN agents a ON (tf.agent_id = a.id OR (tf.agent_id IS NULL AND (t.assigned_to = a.id OR LOWER(t.assigned_to) = LOWER(a.name))))
|
|
58385
58436
|
WHERE t.status = 'in_progress'
|
|
58386
58437
|
AND tf.status != 'removed'
|
|
58387
58438
|
AND t.project_id = ?
|
|
@@ -61761,7 +61812,7 @@ ${text2}` }] };
|
|
|
61761
61812
|
const oldName = agent.name;
|
|
61762
61813
|
const updated = updateAgent(agent.id, { name: new_name });
|
|
61763
61814
|
const db = getDatabase();
|
|
61764
|
-
const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
|
|
61815
|
+
const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE LOWER(assigned_to) = LOWER(?)", [new_name, oldName]);
|
|
61765
61816
|
const taskNote = tasksResult.changes > 0 ? `
|
|
61766
61817
|
Updated assigned_to on ${tasksResult.changes} task(s).` : "";
|
|
61767
61818
|
return {
|
|
@@ -68355,7 +68406,7 @@ No task claimed (nothing available).`));
|
|
|
68355
68406
|
});
|
|
68356
68407
|
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) => {
|
|
68357
68408
|
const globalOpts = program2.opts();
|
|
68358
|
-
const agent = await resolveValidatedAssignee(agentInput, Boolean(opts.assignSeat));
|
|
68409
|
+
const agent = await resolveValidatedAssignee(agentInput, Boolean(opts.assignSeat), (v) => `${id} ${v} --assign-seat`);
|
|
68359
68410
|
const cloud = getTodosCloudClient();
|
|
68360
68411
|
if (cloud) {
|
|
68361
68412
|
try {
|