@dalmasonto/taskflow-mcp 1.0.27 → 1.0.29

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.
@@ -27,25 +27,20 @@ function detectTmuxPane(pid) {
27
27
  return null;
28
28
  }
29
29
  /** Generate a unique agent name from the project folder, auto-suffixing on collision.
30
- * Cleans up dead entries it encounters so the name is available for INSERT. */
30
+ * A name is available if no row exists or the existing row's process is dead. */
31
31
  function generateName(folderName) {
32
32
  const db = getDb();
33
- const check = (name) => {
33
+ const isAvailable = (name) => {
34
34
  const row = db.prepare('SELECT * FROM agent_registry WHERE name = ?').get(name);
35
35
  if (!row)
36
- return true; // name is free
37
- if (!isAlive(row.pid)) {
38
- // Dead entry — remove it so the name can be reused via INSERT
39
- db.prepare('DELETE FROM agent_registry WHERE id = ?').run(row.id);
40
36
  return true;
41
- }
42
- return false; // name is taken by a live agent
37
+ return !isAlive(row.pid);
43
38
  };
44
- if (check(folderName))
39
+ if (isAvailable(folderName))
45
40
  return folderName;
46
41
  for (let i = 2; i < 100; i++) {
47
42
  const candidate = `${folderName}:${i}`;
48
- if (check(candidate))
43
+ if (isAvailable(candidate))
49
44
  return candidate;
50
45
  }
51
46
  return `${folderName}:${Date.now()}`;
@@ -72,9 +67,12 @@ export function registerAgent(options) {
72
67
  if (samePid && options?.customName && options.customName !== samePid.name) {
73
68
  const oldName = samePid.name;
74
69
  const newName = options.customName;
75
- // Update agent_messages to preserve history
76
- db.prepare('UPDATE agent_messages SET sender_name = ? WHERE sender_name = ?').run(newName, oldName);
77
- db.prepare('UPDATE agent_messages SET recipient_name = ? WHERE recipient_name = ?').run(newName, oldName);
70
+ // If the target name already exists as another row (disconnected from a previous session),
71
+ // delete it to free the UNIQUE name. Messages stay — they're linked by name string, not FK.
72
+ const existing = db.prepare('SELECT * FROM agent_registry WHERE name = ?').get(newName);
73
+ if (existing && existing.id !== samePid.id) {
74
+ db.prepare('DELETE FROM agent_registry WHERE id = ?').run(existing.id);
75
+ }
78
76
  db.prepare('UPDATE agent_registry SET name = ?, tmux_pane = ?, connected_at = ? WHERE id = ?').run(newName, tmuxPane, ts, samePid.id);
79
77
  const row = db.prepare('SELECT * FROM agent_registry WHERE id = ?').get(samePid.id);
80
78
  broadcast('agent_renamed', { entity: 'agent', action: 'agent_renamed', payload: { ...row, oldName } });
@@ -98,23 +96,25 @@ export function registerAgent(options) {
98
96
  || (allDisconnected.length === 1 ? allDisconnected[0] : undefined);
99
97
  if (disconnected) {
100
98
  const newName = options?.customName || disconnected.name;
101
- const renamed = newName !== disconnected.name;
102
- // Update agent_messages if renaming to preserve history
103
- if (renamed) {
104
- db.prepare('UPDATE agent_messages SET sender_name = ? WHERE sender_name = ?').run(newName, disconnected.name);
105
- db.prepare('UPDATE agent_messages SET recipient_name = ? WHERE recipient_name = ?').run(newName, disconnected.name);
106
- }
107
99
  db.prepare('UPDATE agent_registry SET name = ?, pid = ?, tmux_pane = ?, status = ?, connected_at = ?, disconnected_at = NULL WHERE id = ?').run(newName, agentPid, tmuxPane, 'connected', ts, disconnected.id);
108
100
  const row = db.prepare('SELECT * FROM agent_registry WHERE id = ?').get(disconnected.id);
109
101
  broadcast('agent_connected', { entity: 'agent', action: 'agent_connected', payload: row });
110
102
  logActivity('agent_connected', `Agent "${newName}" reconnected`, { entityType: 'agent' });
111
103
  return newName;
112
104
  }
113
- // Priority 3: All entries for this path are connected — this is a concurrent agent
114
- // Generate a suffixed name to avoid collision
105
+ // Priority 3: No reusable disconnected entry — this is a new or concurrent agent
106
+ // Generate a suffixed name to avoid collision with live agents
115
107
  const baseName = options?.customName || folderName;
116
108
  const name = entries.length === 0 ? baseName : generateName(baseName);
117
- db.prepare('INSERT INTO agent_registry (name, project_path, pid, tmux_pane, status, connected_at) VALUES (?, ?, ?, ?, ?, ?)').run(name, projectPath, agentPid, tmuxPane, 'connected', ts);
109
+ // Upsert: if a dead entry with this name exists (from another project path),
110
+ // reuse it in-place to preserve its message history. Otherwise insert fresh.
111
+ const stale = db.prepare('SELECT * FROM agent_registry WHERE name = ?').get(name);
112
+ if (stale) {
113
+ db.prepare('UPDATE agent_registry SET project_path = ?, pid = ?, tmux_pane = ?, status = ?, connected_at = ?, disconnected_at = NULL WHERE id = ?').run(projectPath, agentPid, tmuxPane, 'connected', ts, stale.id);
114
+ }
115
+ else {
116
+ db.prepare('INSERT INTO agent_registry (name, project_path, pid, tmux_pane, status, connected_at) VALUES (?, ?, ?, ?, ?, ?)').run(name, projectPath, agentPid, tmuxPane, 'connected', ts);
117
+ }
118
118
  const row = db.prepare('SELECT * FROM agent_registry WHERE name = ?').get(name);
119
119
  broadcast('agent_connected', { entity: 'agent', action: 'agent_connected', payload: row });
120
120
  logActivity('agent_connected', `Agent "${name}" connected`, { entityType: 'agent' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalmasonto/taskflow-mcp",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "MCP server for TaskFlow — manage projects, tasks, timers, analytics via AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",