@hasna/todos 0.9.28 → 0.9.30

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/index.js CHANGED
@@ -264,6 +264,10 @@ var MIGRATIONS = [
264
264
  ALTER TABLE agents ADD COLUMN permissions TEXT DEFAULT '["*"]';
265
265
 
266
266
  INSERT OR IGNORE INTO _migrations (id) VALUES (10);
267
+ `,
268
+ `
269
+ ALTER TABLE agents ADD COLUMN reports_to TEXT;
270
+ INSERT OR IGNORE INTO _migrations (id) VALUES (11);
267
271
  `
268
272
  ];
269
273
  var _db = null;
@@ -385,6 +389,7 @@ function ensureSchema(db) {
385
389
  ensureColumn("tasks", "approved_at", "TEXT");
386
390
  ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
387
391
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
392
+ ensureColumn("agents", "reports_to", "TEXT");
388
393
  ensureColumn("plans", "task_list_id", "TEXT");
389
394
  ensureColumn("plans", "agent_id", "TEXT");
390
395
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
@@ -1115,7 +1120,18 @@ function updateTask(id, input, db) {
1115
1120
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
1116
1121
  if (input.approved_by !== undefined)
1117
1122
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
1118
- return getTask(id, d);
1123
+ return {
1124
+ ...task,
1125
+ ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
1126
+ tags: input.tags ?? task.tags,
1127
+ metadata: input.metadata ?? task.metadata,
1128
+ version: task.version + 1,
1129
+ updated_at: now(),
1130
+ completed_at: input.status === "completed" ? now() : task.completed_at,
1131
+ requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
1132
+ approved_by: input.approved_by ?? task.approved_by,
1133
+ approved_at: input.approved_by ? now() : task.approved_at
1134
+ };
1119
1135
  }
1120
1136
  function deleteTask(id, db) {
1121
1137
  const d = db || getDatabase();
@@ -1137,6 +1153,9 @@ function getBlockingDeps(id, db) {
1137
1153
  }
1138
1154
  function startTask(id, agentId, db) {
1139
1155
  const d = db || getDatabase();
1156
+ const task = getTask(id, d);
1157
+ if (!task)
1158
+ throw new TaskNotFoundError(id);
1140
1159
  const blocking = getBlockingDeps(id, d);
1141
1160
  if (blocking.length > 0) {
1142
1161
  const blockerIds = blocking.map((b) => b.id.slice(0, 8)).join(", ");
@@ -1147,15 +1166,12 @@ function startTask(id, agentId, db) {
1147
1166
  const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
1148
1167
  WHERE id = ? AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp, timestamp, id, agentId, cutoff]);
1149
1168
  if (result.changes === 0) {
1150
- const current = getTask(id, d);
1151
- if (!current)
1152
- throw new TaskNotFoundError(id);
1153
- if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
1154
- throw new LockError(id, current.locked_by);
1169
+ if (task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
1170
+ throw new LockError(id, task.locked_by);
1155
1171
  }
1156
1172
  }
1157
1173
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
1158
- return getTask(id, d);
1174
+ return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
1159
1175
  }
1160
1176
  function completeTask(id, agentId, db, evidence) {
1161
1177
  const d = db || getDatabase();
@@ -1167,14 +1183,15 @@ function completeTask(id, agentId, db, evidence) {
1167
1183
  }
1168
1184
  checkCompletionGuard(task, agentId || null, d);
1169
1185
  if (evidence) {
1170
- const meta = { ...task.metadata, _evidence: evidence };
1171
- d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta), id]);
1186
+ const meta2 = { ...task.metadata, _evidence: evidence };
1187
+ d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
1172
1188
  }
1173
1189
  const timestamp = now();
1174
1190
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
1175
1191
  WHERE id = ?`, [timestamp, timestamp, id]);
1176
1192
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
1177
- return getTask(id, d);
1193
+ const meta = evidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
1194
+ return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
1178
1195
  }
1179
1196
  function lockTask(id, agentId, db) {
1180
1197
  const d = db || getDatabase();
@@ -1376,13 +1393,14 @@ function registerAgent(input, db) {
1376
1393
  }
1377
1394
  const id = shortUuid();
1378
1395
  const timestamp = now();
1379
- d.run(`INSERT INTO agents (id, name, description, role, permissions, metadata, created_at, last_seen_at)
1380
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
1396
+ d.run(`INSERT INTO agents (id, name, description, role, permissions, reports_to, metadata, created_at, last_seen_at)
1397
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
1381
1398
  id,
1382
1399
  input.name,
1383
1400
  input.description || null,
1384
1401
  input.role || "agent",
1385
1402
  JSON.stringify(input.permissions || ["*"]),
1403
+ input.reports_to || null,
1386
1404
  JSON.stringify(input.metadata || {}),
1387
1405
  timestamp,
1388
1406
  timestamp
@@ -1430,6 +1448,10 @@ function updateAgent(id, input, db) {
1430
1448
  sets.push("permissions = ?");
1431
1449
  params.push(JSON.stringify(input.permissions));
1432
1450
  }
1451
+ if (input.reports_to !== undefined) {
1452
+ sets.push("reports_to = ?");
1453
+ params.push(input.reports_to);
1454
+ }
1433
1455
  if (input.metadata !== undefined) {
1434
1456
  sets.push("metadata = ?");
1435
1457
  params.push(JSON.stringify(input.metadata));
@@ -1442,6 +1464,25 @@ function deleteAgent(id, db) {
1442
1464
  const d = db || getDatabase();
1443
1465
  return d.run("DELETE FROM agents WHERE id = ?", [id]).changes > 0;
1444
1466
  }
1467
+ function getDirectReports(agentId, db) {
1468
+ const d = db || getDatabase();
1469
+ return d.query("SELECT * FROM agents WHERE reports_to = ? ORDER BY name").all(agentId).map(rowToAgent);
1470
+ }
1471
+ function getOrgChart(db) {
1472
+ const agents = listAgents(db);
1473
+ const byManager = new Map;
1474
+ for (const a of agents) {
1475
+ const key = a.reports_to;
1476
+ if (!byManager.has(key))
1477
+ byManager.set(key, []);
1478
+ byManager.get(key).push(a);
1479
+ }
1480
+ function buildTree(parentId) {
1481
+ const children = byManager.get(parentId) || [];
1482
+ return children.map((a) => ({ agent: a, reports: buildTree(a.id) }));
1483
+ }
1484
+ return buildTree(null);
1485
+ }
1445
1486
  // src/db/task-lists.ts
1446
1487
  function rowToTaskList(row) {
1447
1488
  return {
@@ -2252,6 +2293,8 @@ export {
2252
2293
  getProjectByPath,
2253
2294
  getProject,
2254
2295
  getPlan,
2296
+ getOrgChart,
2297
+ getDirectReports,
2255
2298
  getDatabase,
2256
2299
  getCompletionGuardConfig,
2257
2300
  getComment,
package/dist/mcp/index.js CHANGED
@@ -187,6 +187,7 @@ function ensureSchema(db) {
187
187
  ensureColumn("tasks", "approved_at", "TEXT");
188
188
  ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
189
189
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
190
+ ensureColumn("agents", "reports_to", "TEXT");
190
191
  ensureColumn("plans", "task_list_id", "TEXT");
191
192
  ensureColumn("plans", "agent_id", "TEXT");
192
193
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
@@ -478,6 +479,10 @@ var init_database = __esm(() => {
478
479
  ALTER TABLE agents ADD COLUMN permissions TEXT DEFAULT '["*"]';
479
480
 
480
481
  INSERT OR IGNORE INTO _migrations (id) VALUES (10);
482
+ `,
483
+ `
484
+ ALTER TABLE agents ADD COLUMN reports_to TEXT;
485
+ INSERT OR IGNORE INTO _migrations (id) VALUES (11);
481
486
  `
482
487
  ];
483
488
  });
@@ -509,6 +514,132 @@ var init_audit = __esm(() => {
509
514
  init_database();
510
515
  });
511
516
 
517
+ // src/db/agents.ts
518
+ var exports_agents = {};
519
+ __export(exports_agents, {
520
+ updateAgentActivity: () => updateAgentActivity,
521
+ updateAgent: () => updateAgent,
522
+ registerAgent: () => registerAgent,
523
+ listAgents: () => listAgents,
524
+ getOrgChart: () => getOrgChart,
525
+ getDirectReports: () => getDirectReports,
526
+ getAgentByName: () => getAgentByName,
527
+ getAgent: () => getAgent,
528
+ deleteAgent: () => deleteAgent
529
+ });
530
+ function shortUuid() {
531
+ return crypto.randomUUID().slice(0, 8);
532
+ }
533
+ function rowToAgent(row) {
534
+ return {
535
+ ...row,
536
+ permissions: JSON.parse(row.permissions || '["*"]'),
537
+ metadata: JSON.parse(row.metadata || "{}")
538
+ };
539
+ }
540
+ function registerAgent(input, db) {
541
+ const d = db || getDatabase();
542
+ const existing = getAgentByName(input.name, d);
543
+ if (existing) {
544
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
545
+ return getAgent(existing.id, d);
546
+ }
547
+ const id = shortUuid();
548
+ const timestamp = now();
549
+ d.run(`INSERT INTO agents (id, name, description, role, permissions, reports_to, metadata, created_at, last_seen_at)
550
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
551
+ id,
552
+ input.name,
553
+ input.description || null,
554
+ input.role || "agent",
555
+ JSON.stringify(input.permissions || ["*"]),
556
+ input.reports_to || null,
557
+ JSON.stringify(input.metadata || {}),
558
+ timestamp,
559
+ timestamp
560
+ ]);
561
+ return getAgent(id, d);
562
+ }
563
+ function getAgent(id, db) {
564
+ const d = db || getDatabase();
565
+ const row = d.query("SELECT * FROM agents WHERE id = ?").get(id);
566
+ return row ? rowToAgent(row) : null;
567
+ }
568
+ function getAgentByName(name, db) {
569
+ const d = db || getDatabase();
570
+ const row = d.query("SELECT * FROM agents WHERE name = ?").get(name);
571
+ return row ? rowToAgent(row) : null;
572
+ }
573
+ function listAgents(db) {
574
+ const d = db || getDatabase();
575
+ return d.query("SELECT * FROM agents ORDER BY name").all().map(rowToAgent);
576
+ }
577
+ function updateAgentActivity(id, db) {
578
+ const d = db || getDatabase();
579
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), id]);
580
+ }
581
+ function updateAgent(id, input, db) {
582
+ const d = db || getDatabase();
583
+ const agent = getAgent(id, d);
584
+ if (!agent)
585
+ throw new Error(`Agent not found: ${id}`);
586
+ const sets = ["last_seen_at = ?"];
587
+ const params = [now()];
588
+ if (input.name !== undefined) {
589
+ sets.push("name = ?");
590
+ params.push(input.name);
591
+ }
592
+ if (input.description !== undefined) {
593
+ sets.push("description = ?");
594
+ params.push(input.description);
595
+ }
596
+ if (input.role !== undefined) {
597
+ sets.push("role = ?");
598
+ params.push(input.role);
599
+ }
600
+ if (input.permissions !== undefined) {
601
+ sets.push("permissions = ?");
602
+ params.push(JSON.stringify(input.permissions));
603
+ }
604
+ if (input.reports_to !== undefined) {
605
+ sets.push("reports_to = ?");
606
+ params.push(input.reports_to);
607
+ }
608
+ if (input.metadata !== undefined) {
609
+ sets.push("metadata = ?");
610
+ params.push(JSON.stringify(input.metadata));
611
+ }
612
+ params.push(id);
613
+ d.run(`UPDATE agents SET ${sets.join(", ")} WHERE id = ?`, params);
614
+ return getAgent(id, d);
615
+ }
616
+ function deleteAgent(id, db) {
617
+ const d = db || getDatabase();
618
+ return d.run("DELETE FROM agents WHERE id = ?", [id]).changes > 0;
619
+ }
620
+ function getDirectReports(agentId, db) {
621
+ const d = db || getDatabase();
622
+ return d.query("SELECT * FROM agents WHERE reports_to = ? ORDER BY name").all(agentId).map(rowToAgent);
623
+ }
624
+ function getOrgChart(db) {
625
+ const agents = listAgents(db);
626
+ const byManager = new Map;
627
+ for (const a of agents) {
628
+ const key = a.reports_to;
629
+ if (!byManager.has(key))
630
+ byManager.set(key, []);
631
+ byManager.get(key).push(a);
632
+ }
633
+ function buildTree(parentId) {
634
+ const children = byManager.get(parentId) || [];
635
+ return children.map((a) => ({ agent: a, reports: buildTree(a.id) }));
636
+ }
637
+ return buildTree(null);
638
+ }
639
+ var init_agents = __esm(() => {
640
+ init_database();
641
+ });
642
+
512
643
  // src/db/webhooks.ts
513
644
  var exports_webhooks = {};
514
645
  __export(exports_webhooks, {
@@ -5154,7 +5285,18 @@ function updateTask(id, input, db) {
5154
5285
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
5155
5286
  if (input.approved_by !== undefined)
5156
5287
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
5157
- return getTask(id, d);
5288
+ return {
5289
+ ...task,
5290
+ ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
5291
+ tags: input.tags ?? task.tags,
5292
+ metadata: input.metadata ?? task.metadata,
5293
+ version: task.version + 1,
5294
+ updated_at: now(),
5295
+ completed_at: input.status === "completed" ? now() : task.completed_at,
5296
+ requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
5297
+ approved_by: input.approved_by ?? task.approved_by,
5298
+ approved_at: input.approved_by ? now() : task.approved_at
5299
+ };
5158
5300
  }
5159
5301
  function deleteTask(id, db) {
5160
5302
  const d = db || getDatabase();
@@ -5176,6 +5318,9 @@ function getBlockingDeps(id, db) {
5176
5318
  }
5177
5319
  function startTask(id, agentId, db) {
5178
5320
  const d = db || getDatabase();
5321
+ const task = getTask(id, d);
5322
+ if (!task)
5323
+ throw new TaskNotFoundError(id);
5179
5324
  const blocking = getBlockingDeps(id, d);
5180
5325
  if (blocking.length > 0) {
5181
5326
  const blockerIds = blocking.map((b) => b.id.slice(0, 8)).join(", ");
@@ -5186,15 +5331,12 @@ function startTask(id, agentId, db) {
5186
5331
  const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
5187
5332
  WHERE id = ? AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp, timestamp, id, agentId, cutoff]);
5188
5333
  if (result.changes === 0) {
5189
- const current = getTask(id, d);
5190
- if (!current)
5191
- throw new TaskNotFoundError(id);
5192
- if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
5193
- throw new LockError(id, current.locked_by);
5334
+ if (task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
5335
+ throw new LockError(id, task.locked_by);
5194
5336
  }
5195
5337
  }
5196
5338
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
5197
- return getTask(id, d);
5339
+ return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
5198
5340
  }
5199
5341
  function completeTask(id, agentId, db, evidence) {
5200
5342
  const d = db || getDatabase();
@@ -5206,14 +5348,15 @@ function completeTask(id, agentId, db, evidence) {
5206
5348
  }
5207
5349
  checkCompletionGuard(task, agentId || null, d);
5208
5350
  if (evidence) {
5209
- const meta = { ...task.metadata, _evidence: evidence };
5210
- d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta), id]);
5351
+ const meta2 = { ...task.metadata, _evidence: evidence };
5352
+ d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
5211
5353
  }
5212
5354
  const timestamp = now();
5213
5355
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
5214
5356
  WHERE id = ?`, [timestamp, timestamp, id]);
5215
5357
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
5216
- return getTask(id, d);
5358
+ const meta = evidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
5359
+ return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
5217
5360
  }
5218
5361
  function lockTask(id, agentId, db) {
5219
5362
  const d = db || getDatabase();
@@ -5387,54 +5530,8 @@ function deletePlan(id, db) {
5387
5530
  return result.changes > 0;
5388
5531
  }
5389
5532
 
5390
- // src/db/agents.ts
5391
- init_database();
5392
- function shortUuid() {
5393
- return crypto.randomUUID().slice(0, 8);
5394
- }
5395
- function rowToAgent(row) {
5396
- return {
5397
- ...row,
5398
- permissions: JSON.parse(row.permissions || '["*"]'),
5399
- metadata: JSON.parse(row.metadata || "{}")
5400
- };
5401
- }
5402
- function registerAgent(input, db) {
5403
- const d = db || getDatabase();
5404
- const existing = getAgentByName(input.name, d);
5405
- if (existing) {
5406
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
5407
- return getAgent(existing.id, d);
5408
- }
5409
- const id = shortUuid();
5410
- const timestamp = now();
5411
- d.run(`INSERT INTO agents (id, name, description, role, permissions, metadata, created_at, last_seen_at)
5412
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
5413
- id,
5414
- input.name,
5415
- input.description || null,
5416
- input.role || "agent",
5417
- JSON.stringify(input.permissions || ["*"]),
5418
- JSON.stringify(input.metadata || {}),
5419
- timestamp,
5420
- timestamp
5421
- ]);
5422
- return getAgent(id, d);
5423
- }
5424
- function getAgent(id, db) {
5425
- const d = db || getDatabase();
5426
- const row = d.query("SELECT * FROM agents WHERE id = ?").get(id);
5427
- return row ? rowToAgent(row) : null;
5428
- }
5429
- function getAgentByName(name, db) {
5430
- const d = db || getDatabase();
5431
- const row = d.query("SELECT * FROM agents WHERE name = ?").get(name);
5432
- return row ? rowToAgent(row) : null;
5433
- }
5434
- function listAgents(db) {
5435
- const d = db || getDatabase();
5436
- return d.query("SELECT * FROM agents ORDER BY name").all().map(rowToAgent);
5437
- }
5533
+ // src/mcp/index.ts
5534
+ init_agents();
5438
5535
 
5439
5536
  // src/db/task-lists.ts
5440
5537
  init_database();
@@ -6959,6 +7056,50 @@ In Progress:`);
6959
7056
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6960
7057
  }
6961
7058
  });
7059
+ server.tool("get_org_chart", "Get agent org chart \u2014 who reports to who.", {}, async () => {
7060
+ try {
7061
+ let render = function(nodes, indent = 0) {
7062
+ return nodes.map((n) => {
7063
+ const prefix = " ".repeat(indent);
7064
+ const role = n.agent.role ? ` (${n.agent.role})` : "";
7065
+ const line = `${prefix}${n.agent.name}${role} [${n.agent.id}]`;
7066
+ const children = n.reports.length > 0 ? `
7067
+ ` + render(n.reports, indent + 1) : "";
7068
+ return line + children;
7069
+ }).join(`
7070
+ `);
7071
+ };
7072
+ const { getOrgChart: getOrgChart2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
7073
+ const tree = getOrgChart2();
7074
+ const text = tree.length > 0 ? render(tree) : "No agents registered.";
7075
+ return { content: [{ type: "text", text }] };
7076
+ } catch (e) {
7077
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
7078
+ }
7079
+ });
7080
+ server.tool("set_reports_to", "Set who an agent reports to in the org chart.", {
7081
+ agent_name: exports_external.string(),
7082
+ manager_name: exports_external.string().optional()
7083
+ }, async ({ agent_name, manager_name }) => {
7084
+ try {
7085
+ const agent = getAgentByName(agent_name);
7086
+ if (!agent)
7087
+ return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
7088
+ let managerId = null;
7089
+ if (manager_name) {
7090
+ const manager = getAgentByName(manager_name);
7091
+ if (!manager)
7092
+ return { content: [{ type: "text", text: `Manager not found: ${manager_name}` }], isError: true };
7093
+ managerId = manager.id;
7094
+ }
7095
+ const { updateAgent: updateAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
7096
+ updateAgent2(agent.id, { reports_to: managerId });
7097
+ const result = managerId ? `${agent_name} now reports to ${manager_name}` : `${agent_name} reports to no one (top-level)`;
7098
+ return { content: [{ type: "text", text: result }] };
7099
+ } catch (e) {
7100
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
7101
+ }
7102
+ });
6962
7103
  server.tool("search_tools", "List tool names matching a query.", { query: exports_external.string().optional() }, async ({ query }) => {
6963
7104
  const all = [
6964
7105
  "create_task",
@@ -249,6 +249,7 @@ function ensureSchema(db) {
249
249
  ensureColumn("tasks", "approved_at", "TEXT");
250
250
  ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
251
251
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
252
+ ensureColumn("agents", "reports_to", "TEXT");
252
253
  ensureColumn("plans", "task_list_id", "TEXT");
253
254
  ensureColumn("plans", "agent_id", "TEXT");
254
255
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
@@ -526,6 +527,10 @@ var init_database = __esm(() => {
526
527
  ALTER TABLE agents ADD COLUMN permissions TEXT DEFAULT '["*"]';
527
528
 
528
529
  INSERT OR IGNORE INTO _migrations (id) VALUES (10);
530
+ `,
531
+ `
532
+ ALTER TABLE agents ADD COLUMN reports_to TEXT;
533
+ INSERT OR IGNORE INTO _migrations (id) VALUES (11);
529
534
  `
530
535
  ];
531
536
  });
@@ -680,6 +685,8 @@ __export(exports_agents, {
680
685
  updateAgent: () => updateAgent,
681
686
  registerAgent: () => registerAgent,
682
687
  listAgents: () => listAgents,
688
+ getOrgChart: () => getOrgChart,
689
+ getDirectReports: () => getDirectReports,
683
690
  getAgentByName: () => getAgentByName,
684
691
  getAgent: () => getAgent,
685
692
  deleteAgent: () => deleteAgent
@@ -703,13 +710,14 @@ function registerAgent(input, db) {
703
710
  }
704
711
  const id = shortUuid();
705
712
  const timestamp = now();
706
- d.run(`INSERT INTO agents (id, name, description, role, permissions, metadata, created_at, last_seen_at)
707
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
713
+ d.run(`INSERT INTO agents (id, name, description, role, permissions, reports_to, metadata, created_at, last_seen_at)
714
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
708
715
  id,
709
716
  input.name,
710
717
  input.description || null,
711
718
  input.role || "agent",
712
719
  JSON.stringify(input.permissions || ["*"]),
720
+ input.reports_to || null,
713
721
  JSON.stringify(input.metadata || {}),
714
722
  timestamp,
715
723
  timestamp
@@ -757,6 +765,10 @@ function updateAgent(id, input, db) {
757
765
  sets.push("permissions = ?");
758
766
  params.push(JSON.stringify(input.permissions));
759
767
  }
768
+ if (input.reports_to !== undefined) {
769
+ sets.push("reports_to = ?");
770
+ params.push(input.reports_to);
771
+ }
760
772
  if (input.metadata !== undefined) {
761
773
  sets.push("metadata = ?");
762
774
  params.push(JSON.stringify(input.metadata));
@@ -769,6 +781,25 @@ function deleteAgent(id, db) {
769
781
  const d = db || getDatabase();
770
782
  return d.run("DELETE FROM agents WHERE id = ?", [id]).changes > 0;
771
783
  }
784
+ function getDirectReports(agentId, db) {
785
+ const d = db || getDatabase();
786
+ return d.query("SELECT * FROM agents WHERE reports_to = ? ORDER BY name").all(agentId).map(rowToAgent);
787
+ }
788
+ function getOrgChart(db) {
789
+ const agents = listAgents(db);
790
+ const byManager = new Map;
791
+ for (const a of agents) {
792
+ const key = a.reports_to;
793
+ if (!byManager.has(key))
794
+ byManager.set(key, []);
795
+ byManager.get(key).push(a);
796
+ }
797
+ function buildTree(parentId) {
798
+ const children = byManager.get(parentId) || [];
799
+ return children.map((a) => ({ agent: a, reports: buildTree(a.id) }));
800
+ }
801
+ return buildTree(null);
802
+ }
772
803
  var init_agents = __esm(() => {
773
804
  init_database();
774
805
  });
@@ -1233,7 +1264,18 @@ function updateTask(id, input, db) {
1233
1264
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
1234
1265
  if (input.approved_by !== undefined)
1235
1266
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
1236
- return getTask(id, d);
1267
+ return {
1268
+ ...task,
1269
+ ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
1270
+ tags: input.tags ?? task.tags,
1271
+ metadata: input.metadata ?? task.metadata,
1272
+ version: task.version + 1,
1273
+ updated_at: now(),
1274
+ completed_at: input.status === "completed" ? now() : task.completed_at,
1275
+ requires_approval: input.requires_approval !== undefined ? input.requires_approval : task.requires_approval,
1276
+ approved_by: input.approved_by ?? task.approved_by,
1277
+ approved_at: input.approved_by ? now() : task.approved_at
1278
+ };
1237
1279
  }
1238
1280
  function deleteTask(id, db) {
1239
1281
  const d = db || getDatabase();
@@ -1255,6 +1297,9 @@ function getBlockingDeps(id, db) {
1255
1297
  }
1256
1298
  function startTask(id, agentId, db) {
1257
1299
  const d = db || getDatabase();
1300
+ const task = getTask(id, d);
1301
+ if (!task)
1302
+ throw new TaskNotFoundError(id);
1258
1303
  const blocking = getBlockingDeps(id, d);
1259
1304
  if (blocking.length > 0) {
1260
1305
  const blockerIds = blocking.map((b) => b.id.slice(0, 8)).join(", ");
@@ -1265,15 +1310,12 @@ function startTask(id, agentId, db) {
1265
1310
  const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
1266
1311
  WHERE id = ? AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp, timestamp, id, agentId, cutoff]);
1267
1312
  if (result.changes === 0) {
1268
- const current = getTask(id, d);
1269
- if (!current)
1270
- throw new TaskNotFoundError(id);
1271
- if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
1272
- throw new LockError(id, current.locked_by);
1313
+ if (task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
1314
+ throw new LockError(id, task.locked_by);
1273
1315
  }
1274
1316
  }
1275
1317
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
1276
- return getTask(id, d);
1318
+ return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
1277
1319
  }
1278
1320
  function completeTask(id, agentId, db, evidence) {
1279
1321
  const d = db || getDatabase();
@@ -1285,14 +1327,15 @@ function completeTask(id, agentId, db, evidence) {
1285
1327
  }
1286
1328
  checkCompletionGuard(task, agentId || null, d);
1287
1329
  if (evidence) {
1288
- const meta = { ...task.metadata, _evidence: evidence };
1289
- d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta), id]);
1330
+ const meta2 = { ...task.metadata, _evidence: evidence };
1331
+ d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
1290
1332
  }
1291
1333
  const timestamp = now();
1292
1334
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
1293
1335
  WHERE id = ?`, [timestamp, timestamp, id]);
1294
1336
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
1295
- return getTask(id, d);
1337
+ const meta = evidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
1338
+ return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
1296
1339
  }
1297
1340
  function getTaskDependencies(taskId, db) {
1298
1341
  const d = db || getDatabase();
@@ -1739,6 +1782,16 @@ Dashboard not found at: ${dashboardDir}`);
1739
1782
  return json({ error: e instanceof Error ? e.message : "Failed to claim" }, 500, port);
1740
1783
  }
1741
1784
  }
1785
+ if (path === "/api/org" && method === "GET") {
1786
+ const { getOrgChart: getOrgChart2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
1787
+ return json(getOrgChart2(), 200, port);
1788
+ }
1789
+ const teamMatch = path.match(/^\/api\/agents\/([^/]+)\/team$/);
1790
+ if (teamMatch && method === "GET") {
1791
+ const agentId = decodeURIComponent(teamMatch[1]);
1792
+ const { getDirectReports: getDirectReports2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
1793
+ return json(getDirectReports2(agentId), 200, port);
1794
+ }
1742
1795
  if (path === "/api/agents" && method === "GET") {
1743
1796
  return json(listAgents(), 200, port);
1744
1797
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.28",
3
+ "version": "0.9.30",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",