@dalmasonto/taskflow-mcp 1.0.24 → 1.0.26

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.
@@ -57,12 +57,36 @@ export function registerAgent(options) {
57
57
  checkAgentLiveness();
58
58
  // Find all entries for this project path
59
59
  const entries = db.prepare('SELECT * FROM agent_registry WHERE project_path = ? ORDER BY connected_at DESC').all(projectPath);
60
- // Priority 1: Reuse a disconnected entry that had the SAME PID (exact session resume)
61
- // Priority 2: Reuse any disconnected entry for this path (new session, same project)
62
- // This prevents a reconnecting agent from stealing another agent's identity
63
- // when two sessions share the same project directory.
64
- const disconnected = entries.find(e => e.status === 'disconnected' && e.pid === agentPid)
65
- || entries.find(e => e.status === 'disconnected');
60
+ // Priority 0: Same PID already connected this agent is renaming itself
61
+ // (e.g. auto-registered as folder name, now calling register_agent with a custom name)
62
+ const samePid = entries.find(e => e.status === 'connected' && e.pid === agentPid);
63
+ if (samePid && options?.customName && options.customName !== samePid.name) {
64
+ const oldName = samePid.name;
65
+ const newName = options.customName;
66
+ // Update agent_messages to preserve history
67
+ db.prepare('UPDATE agent_messages SET sender_name = ? WHERE sender_name = ?').run(newName, oldName);
68
+ db.prepare('UPDATE agent_messages SET recipient_name = ? WHERE recipient_name = ?').run(newName, oldName);
69
+ db.prepare('UPDATE agent_registry SET name = ?, tmux_pane = ?, connected_at = ? WHERE id = ?').run(newName, tmuxPane, ts, samePid.id);
70
+ const row = db.prepare('SELECT * FROM agent_registry WHERE id = ?').get(samePid.id);
71
+ broadcast('agent_renamed', { entity: 'agent', action: 'agent_renamed', payload: { ...row, oldName } });
72
+ logActivity('agent_renamed', `Agent "${oldName}" renamed to "${newName}"`, { entityType: 'agent' });
73
+ return newName;
74
+ }
75
+ // If same PID is already connected with the right name (or no custom name), just return it
76
+ if (samePid) {
77
+ return samePid.name;
78
+ }
79
+ // Reconnection priority for disconnected entries:
80
+ // 1. Custom name matches a disconnected entry exactly — the name IS the stable identity.
81
+ // This is how users resume a specific agent: register_agent({ name: "sentinmail_coder" })
82
+ // 2. Same PID — exact session resume (e.g. MCP server restarted, same shell)
83
+ // 3. Only ONE disconnected entry — unambiguous, safe to reuse
84
+ // When multiple disconnected entries exist and no match is found,
85
+ // we create a fresh entry rather than guessing wrong and mixing up histories.
86
+ const allDisconnected = entries.filter(e => e.status === 'disconnected');
87
+ const disconnected = (options?.customName ? allDisconnected.find(e => e.name === options.customName) : undefined)
88
+ || allDisconnected.find(e => e.pid === agentPid)
89
+ || (allDisconnected.length === 1 ? allDisconnected[0] : undefined);
66
90
  if (disconnected) {
67
91
  const newName = options?.customName || disconnected.name;
68
92
  const renamed = newName !== disconnected.name;
@@ -77,7 +101,7 @@ export function registerAgent(options) {
77
101
  logActivity('agent_connected', `Agent "${newName}" reconnected`, { entityType: 'agent' });
78
102
  return newName;
79
103
  }
80
- // Priority 2: All entries for this path are connected — this is a concurrent agent
104
+ // Priority 3: All entries for this path are connected — this is a concurrent agent
81
105
  // Generate a suffixed name to avoid collision
82
106
  const baseName = options?.customName || folderName;
83
107
  const name = entries.length === 0 ? baseName : generateName(baseName);
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ if (!httpOnly) {
60
60
  const { registerTerminalTools } = await import('./tools/terminal.js');
61
61
  const { registerResources } = await import('./resources.js');
62
62
  const { registerCheckpointTools } = await import('./tools/checkpoint.js');
63
- const server = new McpServer({ name: 'taskflow', version: '1.0.24' }, {
63
+ const server = new McpServer({ name: 'taskflow', version: '1.0.25' }, {
64
64
  instructions: [
65
65
  'You MUST call bootstrap (preferred) or get_agent_instructions before doing any work. This loads your task management rules and project context.',
66
66
  'You MUST create a task in TaskFlow before editing files or running commands. search_tasks first to avoid duplicates.',
package/dist/sse.js CHANGED
@@ -618,12 +618,12 @@ export async function startSSEServer() {
618
618
  if (isTaskFlow) {
619
619
  // Another TaskFlow instance owns this port — connect to it instead
620
620
  activePort = port;
621
- console.log(`[SSE] port ${port} owned by another TaskFlow instance (pid probe) — using it`);
621
+ console.error(`[SSE] port ${port} owned by another TaskFlow instance (pid probe) — using it`);
622
622
  resolve();
623
623
  }
624
624
  else {
625
625
  // Not ours — try next port
626
- console.log(`[SSE] port ${port} in use by non-TaskFlow service — trying ${port + 1}`);
626
+ console.error(`[SSE] port ${port} in use by non-TaskFlow service — trying ${port + 1}`);
627
627
  attempt++;
628
628
  tryPort(port + 1);
629
629
  }
@@ -658,14 +658,14 @@ export async function startSSEServer() {
658
658
  catch { /* ignore */ }
659
659
  }, 3_000);
660
660
  if (port !== PREFERRED_PORT) {
661
- console.log(`[SSE] listening on fallback port ${port} (preferred ${PREFERRED_PORT} was unavailable)`);
661
+ console.error(`[SSE] listening on fallback port ${port} (preferred ${PREFERRED_PORT} was unavailable)`);
662
662
  }
663
663
  else {
664
- console.log(`[SSE] listening on port ${port}`);
664
+ console.error(`[SSE] listening on port ${port}`);
665
665
  }
666
666
  // Relay command polling + state pushing (if relay is configured)
667
667
  if (RELAY_URL && RELAY_PUSH_TOKEN) {
668
- console.log(`[relay] command polling started → ${RELAY_URL}`);
668
+ console.error(`[relay] command polling started → ${RELAY_URL}`);
669
669
  // Push lightweight sync state — only what the remote app needs
670
670
  // SSE events handle real-time updates after this initial load
671
671
  const pushState = () => {
@@ -92,14 +92,25 @@ function injectAndMarkDelivered(id, text, tmuxPane) {
92
92
  const db = getDb();
93
93
  db.prepare('UPDATE agent_messages SET delivered = 1 WHERE id = ?').run(id);
94
94
  try {
95
- execFileSync('tmux', ['send-keys', '-t', tmuxPane, text, 'Enter'], { stdio: 'ignore', timeout: 5000 });
96
- // Long text triggers tmux bracketed paste async delay then extra Enter (non-blocking)
95
+ // Send text literally (-l) so special chars are safe and tmux doesn't interpret them
96
+ execFileSync('tmux', ['send-keys', '-t', tmuxPane, '-l', text], { stdio: 'ignore', timeout: 5000 });
97
+ // Delay before Enter — gives the CLI time to fully process the bracketed paste.
98
+ // Without this, Codex (and similar TUIs) may only show partial text because
99
+ // the Enter arrives inside the paste bracket and gets swallowed.
97
100
  setTimeout(() => {
98
101
  try {
99
102
  execFileSync('tmux', ['send-keys', '-t', tmuxPane, 'Enter'], { stdio: 'ignore', timeout: 5000 });
100
103
  }
101
104
  catch { /* pane may have closed */ }
102
- }, 1000);
105
+ // Second Enter after another delay — catches CLIs that need an extra nudge
106
+ // after bracketed paste ends (e.g. long messages that trigger paste mode)
107
+ setTimeout(() => {
108
+ try {
109
+ execFileSync('tmux', ['send-keys', '-t', tmuxPane, 'Enter'], { stdio: 'ignore', timeout: 5000 });
110
+ }
111
+ catch { /* pane may have closed */ }
112
+ }, 500);
113
+ }, 300);
103
114
  console.error(`[bridge] delivered message ${id} to tmux pane ${tmuxPane}`);
104
115
  }
105
116
  catch (err) {
@@ -38,7 +38,7 @@ const DEFAULT_MAX_LOG_ENTRIES = 500;
38
38
  const MILESTONE_ACTIONS = new Set([
39
39
  'task_created', 'task_completed', 'task_partial_done',
40
40
  'project_created', 'project_deleted',
41
- 'agent_connected', 'agent_disconnected',
41
+ 'agent_connected', 'agent_disconnected', 'agent_renamed',
42
42
  ]);
43
43
  export async function compactActivityLog(params) {
44
44
  const db = getDb();
@@ -16,7 +16,7 @@ export async function getAgentInstructions() {
16
16
  'Derive the project name from the **folder name** of the current working directory (e.g. `/home/user/projects/my-app` → search for "my-app"). Use search_projects with that name. If 2–3 results match, **ask the user** which project to use — never guess.',
17
17
  'list_tasks status="in_progress" and status="blocked" for the confirmed project.',
18
18
  'list_notifications unread_only=true',
19
- 'register_agent with a descriptive name for your role (e.g. "backend", "frontend", "lead"). Then list_agents to see who else is online. If other agents are active on the same project, check_messages for any pending messages addressed to you.',
19
+ 'register_agent with a descriptive name for your role (e.g. "backend", "frontend", "lead"). Then list_agents to see who else is online. If 2+ disconnected agents exist for the same project path, ask the user which name to register as — the name preserves message history from previous sessions. If other agents are active on the same project, check_messages for any pending messages addressed to you.',
20
20
  ],
21
21
  state: {
22
22
  projects: projectCount,
@@ -58,8 +58,8 @@ export async function getAgentInstructions() {
58
58
  'The `context` field in ask_user is rendered as Markdown in the UI. Format it well: use `## headings` to organize sections, `**bold**` for key terms, bullet lists for options/trade-offs, and `\\`code\\`` for file paths or commands. Use real newlines (not literal \\\\n). The context should read like a well-written message, not raw debug output.',
59
59
  // ── Multi-Agent Collaboration ──────────────────────────────────────
60
60
  // Identity & discovery
61
- 'On startup, call register_agent with a descriptive name that reflects your role (e.g. "backend", "frontend", "designer", "qa"). If the user assigns you a role, use that. This name is how other agents address you.',
62
- 'Call list_agents to discover who else is online. Before starting work, check if another agent is already working on the same project — coordinate instead of duplicating effort.',
61
+ 'On startup, call register_agent with a descriptive name that reflects your role (e.g. "backend", "frontend", "designer", "qa"). If the user assigns you a role, use that. This name is how other agents address you and how your message history is preserved across sessions.',
62
+ 'Call list_agents to discover who else is online. If you see 2+ disconnected agents on the same project path, ask the user which name to register as — registering with an existing name resumes that agent\'s full message history. Before starting work, check if another agent is already working on the same project — coordinate instead of duplicating effort.',
63
63
  // Communication
64
64
  'Use send_to_agent for fire-and-forget updates — status changes, "I finished task X", "file Y is ready for you". Use ask_agent when you need a response before proceeding — "Should I use REST or GraphQL for this endpoint?", "Is the auth middleware ready?".',
65
65
  'When you receive a message from another agent (via check_messages), respond promptly using respond_to_message. Treat agent messages with the same priority as user messages.',
package/dist/types.d.ts CHANGED
@@ -67,6 +67,7 @@ export declare const ActivityAction: z.ZodEnum<{
67
67
  agent_broadcast: "agent_broadcast";
68
68
  agent_connected: "agent_connected";
69
69
  agent_disconnected: "agent_disconnected";
70
+ agent_renamed: "agent_renamed";
70
71
  terminal_send_keys: "terminal_send_keys";
71
72
  terminal_captured: "terminal_captured";
72
73
  compaction_summary: "compaction_summary";
package/dist/types.js CHANGED
@@ -15,7 +15,7 @@ export const ActivityAction = z.enum([
15
15
  'task_linked', 'task_unlinked', 'dependency_added', 'dependency_removed',
16
16
  'link_added', 'tag_added', 'tag_removed', 'debug_log',
17
17
  'agent_question', 'agent_question_answered', 'agent_broadcast',
18
- 'agent_connected', 'agent_disconnected',
18
+ 'agent_connected', 'agent_disconnected', 'agent_renamed',
19
19
  'terminal_send_keys', 'terminal_captured',
20
20
  'compaction_summary', 'activity_compacted',
21
21
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalmasonto/taskflow-mcp",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
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",