@hasna/todos 0.10.10 → 0.10.13

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/index.js CHANGED
@@ -2897,6 +2897,20 @@ var init_database = __esm(() => {
2897
2897
  ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
2898
2898
  CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
2899
2899
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
2900
+ `,
2901
+ `
2902
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
2903
+ id TEXT PRIMARY KEY,
2904
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
2905
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
2906
+ role TEXT NOT NULL,
2907
+ is_lead INTEGER NOT NULL DEFAULT 0,
2908
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2909
+ UNIQUE(project_id, agent_id, role)
2910
+ );
2911
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
2912
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
2913
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
2900
2914
  `,
2901
2915
  `
2902
2916
  CREATE TABLE IF NOT EXISTS file_locks (
@@ -2910,7 +2924,7 @@ var init_database = __esm(() => {
2910
2924
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
2911
2925
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
2912
2926
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
2913
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
2927
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
2914
2928
  `
2915
2929
  ];
2916
2930
  });
@@ -10432,6 +10446,81 @@ var init_kg = __esm(() => {
10432
10446
  init_database();
10433
10447
  });
10434
10448
 
10449
+ // src/db/project-agent-roles.ts
10450
+ var exports_project_agent_roles = {};
10451
+ __export(exports_project_agent_roles, {
10452
+ setProjectAgentRole: () => setProjectAgentRole,
10453
+ removeProjectAgentRole: () => removeProjectAgentRole,
10454
+ listProjectAgentRoles: () => listProjectAgentRoles,
10455
+ getProjectOrgChart: () => getProjectOrgChart,
10456
+ getAgentProjectRoles: () => getAgentProjectRoles
10457
+ });
10458
+ function rowToRole(row) {
10459
+ return { ...row, is_lead: row.is_lead === 1 };
10460
+ }
10461
+ function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
10462
+ const d = db || getDatabase();
10463
+ const existing = d.query("SELECT * FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?").get(projectId, agentId, role);
10464
+ if (existing) {
10465
+ d.run("UPDATE project_agent_roles SET is_lead = ? WHERE id = ?", [isLead ? 1 : 0, existing.id]);
10466
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
10467
+ }
10468
+ const id = uuid();
10469
+ d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0, now()]);
10470
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
10471
+ }
10472
+ function removeProjectAgentRole(projectId, agentId, role, db) {
10473
+ const d = db || getDatabase();
10474
+ if (role) {
10475
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?", [projectId, agentId, role]).changes;
10476
+ }
10477
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ?", [projectId, agentId]).changes;
10478
+ }
10479
+ function listProjectAgentRoles(projectId, db) {
10480
+ const d = db || getDatabase();
10481
+ return d.query("SELECT * FROM project_agent_roles WHERE project_id = ? ORDER BY role, created_at").all(projectId).map(rowToRole);
10482
+ }
10483
+ function getAgentProjectRoles(agentId, db) {
10484
+ const d = db || getDatabase();
10485
+ return d.query("SELECT * FROM project_agent_roles WHERE agent_id = ? ORDER BY project_id, role").all(agentId).map(rowToRole);
10486
+ }
10487
+ function getProjectOrgChart(projectId, opts, db) {
10488
+ const d = db || getDatabase();
10489
+ const globalTree = getOrgChart(d);
10490
+ const projectRoles = listProjectAgentRoles(projectId, d);
10491
+ const rolesByAgent = new Map;
10492
+ for (const pr of projectRoles) {
10493
+ if (!rolesByAgent.has(pr.agent_id))
10494
+ rolesByAgent.set(pr.agent_id, { roles: [], isLead: false });
10495
+ const entry = rolesByAgent.get(pr.agent_id);
10496
+ entry.roles.push(pr.role);
10497
+ if (pr.is_lead)
10498
+ entry.isLead = true;
10499
+ }
10500
+ function augmentTree(nodes) {
10501
+ return nodes.map((n) => {
10502
+ const override = rolesByAgent.get(n.agent.id);
10503
+ return {
10504
+ ...n,
10505
+ reports: augmentTree(n.reports),
10506
+ project_roles: override?.roles ?? [],
10507
+ is_project_lead: override?.isLead ?? false
10508
+ };
10509
+ }).filter((n) => {
10510
+ if (!opts?.filter_to_project)
10511
+ return true;
10512
+ const hasRole = n.project_roles.length > 0;
10513
+ const hasDescendant = n.reports.length > 0;
10514
+ return hasRole || hasDescendant;
10515
+ });
10516
+ }
10517
+ return augmentTree(globalTree);
10518
+ }
10519
+ var init_project_agent_roles = __esm(() => {
10520
+ init_database();
10521
+ init_agents();
10522
+ });
10523
+
10435
10524
  // src/db/patrol.ts
10436
10525
  var exports_patrol = {};
10437
10526
  __export(exports_patrol, {
@@ -10836,8 +10925,6 @@ var init_mcp = __esm(() => {
10836
10925
  "heartbeat"
10837
10926
  ]);
10838
10927
  STANDARD_EXCLUDED = new Set([
10839
- "get_org_chart",
10840
- "set_reports_to",
10841
10928
  "rename_agent",
10842
10929
  "delete_agent",
10843
10930
  "unarchive_agent",
@@ -12282,14 +12369,32 @@ In Progress:`);
12282
12369
  });
12283
12370
  }
12284
12371
  if (shouldRegisterTool("get_org_chart")) {
12285
- server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
12372
+ server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy with roles, titles, capabilities, and activity status.", {
12373
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
12374
+ role: exports_external.string().optional().describe("Filter by agent role (e.g. 'lead', 'developer')"),
12375
+ active_only: exports_external.coerce.boolean().optional().describe("Only show agents active in last 30 min")
12376
+ }, async ({ format, role, active_only }) => {
12286
12377
  try {
12287
- let render = function(nodes, indent = 0) {
12378
+ let filterTree = function(nodes) {
12379
+ return nodes.map((n) => ({ ...n, reports: filterTree(n.reports) })).filter((n) => {
12380
+ if (role && n.agent.role !== role)
12381
+ return false;
12382
+ if (active_only) {
12383
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
12384
+ if (now2 - lastSeen > ACTIVE_MS)
12385
+ return false;
12386
+ }
12387
+ return true;
12388
+ });
12389
+ }, render = function(nodes, indent = 0) {
12288
12390
  return nodes.map((n) => {
12289
12391
  const prefix = " ".repeat(indent);
12290
12392
  const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
12291
- const level = n.agent.level ? ` (${n.agent.level})` : "";
12292
- const line = `${prefix}${n.agent.name}${title}${level}`;
12393
+ const level = n.agent.level ? ` [${n.agent.level}]` : "";
12394
+ const caps = n.agent.capabilities?.length > 0 ? ` {${n.agent.capabilities.join(", ")}}` : "";
12395
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
12396
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
12397
+ const line = `${prefix}${active} ${n.agent.name}${title}${level}${caps}`;
12293
12398
  const children = n.reports.length > 0 ? `
12294
12399
  ` + render(n.reports, indent + 1) : "";
12295
12400
  return line + children;
@@ -12297,7 +12402,14 @@ In Progress:`);
12297
12402
  `);
12298
12403
  };
12299
12404
  const { getOrgChart: getOrgChart2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
12300
- const tree = getOrgChart2();
12405
+ let tree = getOrgChart2();
12406
+ const now2 = Date.now();
12407
+ const ACTIVE_MS = 1800000;
12408
+ if (role || active_only)
12409
+ tree = filterTree(tree);
12410
+ if (format === "json") {
12411
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
12412
+ }
12301
12413
  const text = tree.length > 0 ? render(tree) : "No agents registered.";
12302
12414
  return { content: [{ type: "text", text }] };
12303
12415
  } catch (e) {
@@ -13745,6 +13857,78 @@ ${lines.join(`
13745
13857
  }
13746
13858
  });
13747
13859
  }
13860
+ if (shouldRegisterTool("set_project_agent_role")) {
13861
+ server.tool("set_project_agent_role", "Assign an agent a role on a specific project (client, lead, developer, qa, reviewer, etc.). Per-project roles extend the global org chart.", {
13862
+ project_id: exports_external.string().describe("Project ID"),
13863
+ agent_name: exports_external.string().describe("Agent name"),
13864
+ role: exports_external.string().describe("Role on this project (e.g. 'lead', 'developer', 'qa')"),
13865
+ is_lead: exports_external.coerce.boolean().optional().describe("Whether this agent is the project lead for this role")
13866
+ }, async ({ project_id, agent_name, role, is_lead }) => {
13867
+ try {
13868
+ const { setProjectAgentRole: setProjectAgentRole2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
13869
+ const agent = getAgentByName(agent_name);
13870
+ if (!agent)
13871
+ return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
13872
+ const pid = resolveId(project_id, "projects");
13873
+ const result = setProjectAgentRole2(pid, agent.id, role, is_lead ?? false);
13874
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
13875
+ } catch (e) {
13876
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13877
+ }
13878
+ });
13879
+ }
13880
+ if (shouldRegisterTool("get_project_org_chart")) {
13881
+ server.tool("get_project_org_chart", "Get org chart scoped to a project \u2014 global hierarchy with per-project role overrides merged in.", {
13882
+ project_id: exports_external.string().describe("Project ID"),
13883
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
13884
+ filter_to_project: exports_external.coerce.boolean().optional().describe("Only show agents with a role on this project")
13885
+ }, async ({ project_id, format, filter_to_project }) => {
13886
+ try {
13887
+ let render = function(nodes, indent = 0) {
13888
+ return nodes.map((n) => {
13889
+ const prefix = " ".repeat(indent);
13890
+ const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
13891
+ const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
13892
+ const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
13893
+ const lead = n.is_project_lead ? " \u2605" : "";
13894
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
13895
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
13896
+ const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
13897
+ const children = n.reports.length > 0 ? `
13898
+ ` + render(n.reports, indent + 1) : "";
13899
+ return line + children;
13900
+ }).join(`
13901
+ `);
13902
+ };
13903
+ const { getProjectOrgChart: getProjectOrgChart2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
13904
+ const pid = resolveId(project_id, "projects");
13905
+ const tree = getProjectOrgChart2(pid, { filter_to_project });
13906
+ if (format === "json") {
13907
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
13908
+ }
13909
+ const now2 = Date.now();
13910
+ const ACTIVE_MS = 30 * 60 * 1000;
13911
+ const text = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
13912
+ return { content: [{ type: "text", text }] };
13913
+ } catch (e) {
13914
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13915
+ }
13916
+ });
13917
+ }
13918
+ if (shouldRegisterTool("list_project_agent_roles")) {
13919
+ server.tool("list_project_agent_roles", "List all agent role assignments for a project.", {
13920
+ project_id: exports_external.string().describe("Project ID")
13921
+ }, async ({ project_id }) => {
13922
+ try {
13923
+ const { listProjectAgentRoles: listProjectAgentRoles2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
13924
+ const pid = resolveId(project_id, "projects");
13925
+ const roles = listProjectAgentRoles2(pid);
13926
+ return { content: [{ type: "text", text: JSON.stringify(roles, null, 2) }] };
13927
+ } catch (e) {
13928
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
13929
+ }
13930
+ });
13931
+ }
13748
13932
  if (shouldRegisterTool("get_capable_agents")) {
13749
13933
  server.tool("get_capable_agents", "Find agents that match given capabilities, sorted by match score.", {
13750
13934
  capabilities: exports_external.array(exports_external.string()).describe("Required capabilities to match against"),
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA6hBtC,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAkBrD;AAyRD,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAK9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,SAAa,GAAG,MAAM,CAG3D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGpD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B9F"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA4iBtC,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAkBrD;AAyRD,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAK9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,SAAa,GAAG,MAAM,CAG3D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGpD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B9F"}
@@ -0,0 +1,34 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { OrgNode } from "./agents.js";
3
+ export interface ProjectAgentRole {
4
+ id: string;
5
+ project_id: string;
6
+ agent_id: string;
7
+ role: string;
8
+ is_lead: boolean;
9
+ created_at: string;
10
+ }
11
+ export interface ProjectAgentRoleRow {
12
+ id: string;
13
+ project_id: string;
14
+ agent_id: string;
15
+ role: string;
16
+ is_lead: number;
17
+ created_at: string;
18
+ }
19
+ export declare function setProjectAgentRole(projectId: string, agentId: string, role: string, isLead?: boolean, db?: Database): ProjectAgentRole;
20
+ export declare function removeProjectAgentRole(projectId: string, agentId: string, role?: string, db?: Database): number;
21
+ export declare function listProjectAgentRoles(projectId: string, db?: Database): ProjectAgentRole[];
22
+ export declare function getAgentProjectRoles(agentId: string, db?: Database): ProjectAgentRole[];
23
+ export interface ProjectOrgNode extends OrgNode {
24
+ project_roles: string[];
25
+ is_project_lead: boolean;
26
+ }
27
+ /**
28
+ * Get org chart scoped to a project. Returns global org chart with per-project
29
+ * role overrides merged in. Agents not in the project are excluded when filter=true.
30
+ */
31
+ export declare function getProjectOrgChart(projectId: string, opts?: {
32
+ filter_to_project?: boolean;
33
+ }, db?: Database): ProjectOrgNode[];
34
+ //# sourceMappingURL=project-agent-roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-agent-roles.d.ts","sourceRoot":"","sources":["../../src/db/project-agent-roles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,UAAQ,EACd,EAAE,CAAC,EAAE,QAAQ,GACZ,gBAAgB,CAoBlB;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,EACb,EAAE,CAAC,EAAE,QAAQ,GACZ,MAAM,CAYR;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CAK1F;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CAKvF;AAED,MAAM,WAAW,cAAe,SAAQ,OAAO;IAC7C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,EACtC,EAAE,CAAC,EAAE,QAAQ,GACZ,cAAc,EAAE,CAkClB"}
package/dist/index.js CHANGED
@@ -704,6 +704,20 @@ var MIGRATIONS = [
704
704
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
705
705
  `,
706
706
  `
707
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
708
+ id TEXT PRIMARY KEY,
709
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
710
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
711
+ role TEXT NOT NULL,
712
+ is_lead INTEGER NOT NULL DEFAULT 0,
713
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
714
+ UNIQUE(project_id, agent_id, role)
715
+ );
716
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
717
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
718
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
719
+ `,
720
+ `
707
721
  CREATE TABLE IF NOT EXISTS file_locks (
708
722
  id TEXT PRIMARY KEY,
709
723
  path TEXT NOT NULL UNIQUE,
@@ -715,7 +729,7 @@ var MIGRATIONS = [
715
729
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
716
730
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
717
731
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
718
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
732
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
719
733
  `
720
734
  ];
721
735
  var _db = null;
package/dist/mcp/index.js CHANGED
@@ -942,6 +942,20 @@ var init_database = __esm(() => {
942
942
  ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
943
943
  CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
944
944
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
945
+ `,
946
+ `
947
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
948
+ id TEXT PRIMARY KEY,
949
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
950
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
951
+ role TEXT NOT NULL,
952
+ is_lead INTEGER NOT NULL DEFAULT 0,
953
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
954
+ UNIQUE(project_id, agent_id, role)
955
+ );
956
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
957
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
958
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
945
959
  `,
946
960
  `
947
961
  CREATE TABLE IF NOT EXISTS file_locks (
@@ -955,7 +969,7 @@ var init_database = __esm(() => {
955
969
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
956
970
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
957
971
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
958
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
972
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
959
973
  `
960
974
  ];
961
975
  });
@@ -1977,6 +1991,81 @@ var init_kg = __esm(() => {
1977
1991
  init_database();
1978
1992
  });
1979
1993
 
1994
+ // src/db/project-agent-roles.ts
1995
+ var exports_project_agent_roles = {};
1996
+ __export(exports_project_agent_roles, {
1997
+ setProjectAgentRole: () => setProjectAgentRole,
1998
+ removeProjectAgentRole: () => removeProjectAgentRole,
1999
+ listProjectAgentRoles: () => listProjectAgentRoles,
2000
+ getProjectOrgChart: () => getProjectOrgChart,
2001
+ getAgentProjectRoles: () => getAgentProjectRoles
2002
+ });
2003
+ function rowToRole(row) {
2004
+ return { ...row, is_lead: row.is_lead === 1 };
2005
+ }
2006
+ function setProjectAgentRole(projectId, agentId, role, isLead = false, db) {
2007
+ const d = db || getDatabase();
2008
+ const existing = d.query("SELECT * FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?").get(projectId, agentId, role);
2009
+ if (existing) {
2010
+ d.run("UPDATE project_agent_roles SET is_lead = ? WHERE id = ?", [isLead ? 1 : 0, existing.id]);
2011
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(existing.id));
2012
+ }
2013
+ const id = uuid();
2014
+ d.run("INSERT INTO project_agent_roles (id, project_id, agent_id, role, is_lead, created_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, agentId, role, isLead ? 1 : 0, now()]);
2015
+ return rowToRole(d.query("SELECT * FROM project_agent_roles WHERE id = ?").get(id));
2016
+ }
2017
+ function removeProjectAgentRole(projectId, agentId, role, db) {
2018
+ const d = db || getDatabase();
2019
+ if (role) {
2020
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ? AND role = ?", [projectId, agentId, role]).changes;
2021
+ }
2022
+ return d.run("DELETE FROM project_agent_roles WHERE project_id = ? AND agent_id = ?", [projectId, agentId]).changes;
2023
+ }
2024
+ function listProjectAgentRoles(projectId, db) {
2025
+ const d = db || getDatabase();
2026
+ return d.query("SELECT * FROM project_agent_roles WHERE project_id = ? ORDER BY role, created_at").all(projectId).map(rowToRole);
2027
+ }
2028
+ function getAgentProjectRoles(agentId, db) {
2029
+ const d = db || getDatabase();
2030
+ return d.query("SELECT * FROM project_agent_roles WHERE agent_id = ? ORDER BY project_id, role").all(agentId).map(rowToRole);
2031
+ }
2032
+ function getProjectOrgChart(projectId, opts, db) {
2033
+ const d = db || getDatabase();
2034
+ const globalTree = getOrgChart(d);
2035
+ const projectRoles = listProjectAgentRoles(projectId, d);
2036
+ const rolesByAgent = new Map;
2037
+ for (const pr of projectRoles) {
2038
+ if (!rolesByAgent.has(pr.agent_id))
2039
+ rolesByAgent.set(pr.agent_id, { roles: [], isLead: false });
2040
+ const entry = rolesByAgent.get(pr.agent_id);
2041
+ entry.roles.push(pr.role);
2042
+ if (pr.is_lead)
2043
+ entry.isLead = true;
2044
+ }
2045
+ function augmentTree(nodes) {
2046
+ return nodes.map((n) => {
2047
+ const override = rolesByAgent.get(n.agent.id);
2048
+ return {
2049
+ ...n,
2050
+ reports: augmentTree(n.reports),
2051
+ project_roles: override?.roles ?? [],
2052
+ is_project_lead: override?.isLead ?? false
2053
+ };
2054
+ }).filter((n) => {
2055
+ if (!opts?.filter_to_project)
2056
+ return true;
2057
+ const hasRole = n.project_roles.length > 0;
2058
+ const hasDescendant = n.reports.length > 0;
2059
+ return hasRole || hasDescendant;
2060
+ });
2061
+ }
2062
+ return augmentTree(globalTree);
2063
+ }
2064
+ var init_project_agent_roles = __esm(() => {
2065
+ init_database();
2066
+ init_agents();
2067
+ });
2068
+
1980
2069
  // src/db/patrol.ts
1981
2070
  var exports_patrol = {};
1982
2071
  __export(exports_patrol, {
@@ -8441,8 +8530,6 @@ var MINIMAL_TOOLS = new Set([
8441
8530
  "heartbeat"
8442
8531
  ]);
8443
8532
  var STANDARD_EXCLUDED = new Set([
8444
- "get_org_chart",
8445
- "set_reports_to",
8446
8533
  "rename_agent",
8447
8534
  "delete_agent",
8448
8535
  "unarchive_agent",
@@ -10010,14 +10097,32 @@ In Progress:`);
10010
10097
  });
10011
10098
  }
10012
10099
  if (shouldRegisterTool("get_org_chart")) {
10013
- server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
10100
+ server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy with roles, titles, capabilities, and activity status.", {
10101
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
10102
+ role: exports_external.string().optional().describe("Filter by agent role (e.g. 'lead', 'developer')"),
10103
+ active_only: exports_external.coerce.boolean().optional().describe("Only show agents active in last 30 min")
10104
+ }, async ({ format, role, active_only }) => {
10014
10105
  try {
10015
- let render = function(nodes, indent = 0) {
10106
+ let filterTree = function(nodes) {
10107
+ return nodes.map((n) => ({ ...n, reports: filterTree(n.reports) })).filter((n) => {
10108
+ if (role && n.agent.role !== role)
10109
+ return false;
10110
+ if (active_only) {
10111
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
10112
+ if (now2 - lastSeen > ACTIVE_MS)
10113
+ return false;
10114
+ }
10115
+ return true;
10116
+ });
10117
+ }, render = function(nodes, indent = 0) {
10016
10118
  return nodes.map((n) => {
10017
10119
  const prefix = " ".repeat(indent);
10018
10120
  const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
10019
- const level = n.agent.level ? ` (${n.agent.level})` : "";
10020
- const line = `${prefix}${n.agent.name}${title}${level}`;
10121
+ const level = n.agent.level ? ` [${n.agent.level}]` : "";
10122
+ const caps = n.agent.capabilities?.length > 0 ? ` {${n.agent.capabilities.join(", ")}}` : "";
10123
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
10124
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
10125
+ const line = `${prefix}${active} ${n.agent.name}${title}${level}${caps}`;
10021
10126
  const children = n.reports.length > 0 ? `
10022
10127
  ` + render(n.reports, indent + 1) : "";
10023
10128
  return line + children;
@@ -10025,7 +10130,14 @@ if (shouldRegisterTool("get_org_chart")) {
10025
10130
  `);
10026
10131
  };
10027
10132
  const { getOrgChart: getOrgChart2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
10028
- const tree = getOrgChart2();
10133
+ let tree = getOrgChart2();
10134
+ const now2 = Date.now();
10135
+ const ACTIVE_MS = 1800000;
10136
+ if (role || active_only)
10137
+ tree = filterTree(tree);
10138
+ if (format === "json") {
10139
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
10140
+ }
10029
10141
  const text = tree.length > 0 ? render(tree) : "No agents registered.";
10030
10142
  return { content: [{ type: "text", text }] };
10031
10143
  } catch (e) {
@@ -11473,6 +11585,78 @@ ${lines.join(`
11473
11585
  }
11474
11586
  });
11475
11587
  }
11588
+ if (shouldRegisterTool("set_project_agent_role")) {
11589
+ server.tool("set_project_agent_role", "Assign an agent a role on a specific project (client, lead, developer, qa, reviewer, etc.). Per-project roles extend the global org chart.", {
11590
+ project_id: exports_external.string().describe("Project ID"),
11591
+ agent_name: exports_external.string().describe("Agent name"),
11592
+ role: exports_external.string().describe("Role on this project (e.g. 'lead', 'developer', 'qa')"),
11593
+ is_lead: exports_external.coerce.boolean().optional().describe("Whether this agent is the project lead for this role")
11594
+ }, async ({ project_id, agent_name, role, is_lead }) => {
11595
+ try {
11596
+ const { setProjectAgentRole: setProjectAgentRole2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
11597
+ const agent = getAgentByName(agent_name);
11598
+ if (!agent)
11599
+ return { content: [{ type: "text", text: `Agent not found: ${agent_name}` }], isError: true };
11600
+ const pid = resolveId(project_id, "projects");
11601
+ const result = setProjectAgentRole2(pid, agent.id, role, is_lead ?? false);
11602
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
11603
+ } catch (e) {
11604
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11605
+ }
11606
+ });
11607
+ }
11608
+ if (shouldRegisterTool("get_project_org_chart")) {
11609
+ server.tool("get_project_org_chart", "Get org chart scoped to a project \u2014 global hierarchy with per-project role overrides merged in.", {
11610
+ project_id: exports_external.string().describe("Project ID"),
11611
+ format: exports_external.enum(["text", "json"]).optional().describe("Output format (default: text)"),
11612
+ filter_to_project: exports_external.coerce.boolean().optional().describe("Only show agents with a role on this project")
11613
+ }, async ({ project_id, format, filter_to_project }) => {
11614
+ try {
11615
+ let render = function(nodes, indent = 0) {
11616
+ return nodes.map((n) => {
11617
+ const prefix = " ".repeat(indent);
11618
+ const title = n.agent.title ? ` \u2014 ${n.agent.title}` : "";
11619
+ const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
11620
+ const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
11621
+ const lead = n.is_project_lead ? " \u2605" : "";
11622
+ const lastSeen = new Date(n.agent.last_seen_at).getTime();
11623
+ const active = now2 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
11624
+ const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
11625
+ const children = n.reports.length > 0 ? `
11626
+ ` + render(n.reports, indent + 1) : "";
11627
+ return line + children;
11628
+ }).join(`
11629
+ `);
11630
+ };
11631
+ const { getProjectOrgChart: getProjectOrgChart2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
11632
+ const pid = resolveId(project_id, "projects");
11633
+ const tree = getProjectOrgChart2(pid, { filter_to_project });
11634
+ if (format === "json") {
11635
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
11636
+ }
11637
+ const now2 = Date.now();
11638
+ const ACTIVE_MS = 30 * 60 * 1000;
11639
+ const text = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
11640
+ return { content: [{ type: "text", text }] };
11641
+ } catch (e) {
11642
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11643
+ }
11644
+ });
11645
+ }
11646
+ if (shouldRegisterTool("list_project_agent_roles")) {
11647
+ server.tool("list_project_agent_roles", "List all agent role assignments for a project.", {
11648
+ project_id: exports_external.string().describe("Project ID")
11649
+ }, async ({ project_id }) => {
11650
+ try {
11651
+ const { listProjectAgentRoles: listProjectAgentRoles2 } = (init_project_agent_roles(), __toCommonJS(exports_project_agent_roles));
11652
+ const pid = resolveId(project_id, "projects");
11653
+ const roles = listProjectAgentRoles2(pid);
11654
+ return { content: [{ type: "text", text: JSON.stringify(roles, null, 2) }] };
11655
+ } catch (e) {
11656
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
11657
+ }
11658
+ });
11659
+ }
11476
11660
  if (shouldRegisterTool("get_capable_agents")) {
11477
11661
  server.tool("get_capable_agents", "Find agents that match given capabilities, sorted by match score.", {
11478
11662
  capabilities: exports_external.array(exports_external.string()).describe("Required capabilities to match against"),
@@ -858,6 +858,20 @@ var init_database = __esm(() => {
858
858
  ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived'));
859
859
  CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
860
860
  INSERT OR IGNORE INTO _migrations (id) VALUES (30);
861
+ `,
862
+ `
863
+ CREATE TABLE IF NOT EXISTS project_agent_roles (
864
+ id TEXT PRIMARY KEY,
865
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
866
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
867
+ role TEXT NOT NULL,
868
+ is_lead INTEGER NOT NULL DEFAULT 0,
869
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
870
+ UNIQUE(project_id, agent_id, role)
871
+ );
872
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_project ON project_agent_roles(project_id);
873
+ CREATE INDEX IF NOT EXISTS idx_project_agent_roles_agent ON project_agent_roles(agent_id);
874
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
861
875
  `,
862
876
  `
863
877
  CREATE TABLE IF NOT EXISTS file_locks (
@@ -871,7 +885,7 @@ var init_database = __esm(() => {
871
885
  CREATE INDEX IF NOT EXISTS idx_file_locks_path ON file_locks(path);
872
886
  CREATE INDEX IF NOT EXISTS idx_file_locks_agent ON file_locks(agent_id);
873
887
  CREATE INDEX IF NOT EXISTS idx_file_locks_expires ON file_locks(expires_at);
874
- INSERT OR IGNORE INTO _migrations (id) VALUES (31);
888
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
875
889
  `
876
890
  ];
877
891
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.10.10",
3
+ "version": "0.10.13",
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",