@dalmasonto/taskflow-mcp 1.0.1 → 1.0.3

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
@@ -3,21 +3,29 @@ import { startSSEServer } from './sse.js';
3
3
  import { getDb } from './db.js';
4
4
  import { broadcast } from './sse.js';
5
5
  const httpOnly = process.argv.includes('--http-only');
6
- // Close any orphaned sessions left from a previous crash
7
- // If the server died while sessions were active, they'll have no `end` timestamp
6
+ // Close orphaned sessions left from a previous crash.
7
+ // Uses a grace period so that sessions from the current conversation
8
+ // (stdio transport restarts the server on each tool call) are preserved.
9
+ const ORPHAN_GRACE_MS = 5 * 60 * 1000; // 5 minutes
8
10
  function cleanupOrphanedSessions() {
9
11
  const db = getDb();
10
- const now = new Date().toISOString();
11
- const orphaned = db.prepare('SELECT * FROM sessions WHERE end IS NULL').all();
12
+ const nowMs = Date.now();
13
+ const nowIso = new Date(nowMs).toISOString();
14
+ const cutoff = new Date(nowMs - ORPHAN_GRACE_MS).toISOString();
15
+ // Only close sessions that started more than ORPHAN_GRACE_MS ago
16
+ const orphaned = db.prepare('SELECT * FROM sessions WHERE end IS NULL AND start < ?').all(cutoff);
12
17
  if (orphaned.length === 0)
13
18
  return;
14
- db.prepare('UPDATE sessions SET end = ? WHERE end IS NULL').run(now);
19
+ // Close each orphaned session with end = now (preserves the real elapsed time)
20
+ for (const session of orphaned) {
21
+ db.prepare('UPDATE sessions SET end = ? WHERE id = ?').run(nowIso, session.id);
22
+ }
15
23
  // Set orphaned in_progress tasks back to paused
16
24
  const taskIds = [...new Set(orphaned.map(s => s.task_id))];
17
25
  for (const taskId of taskIds) {
18
26
  const task = db.prepare('SELECT status FROM tasks WHERE id = ?').get(taskId);
19
27
  if (task?.status === 'in_progress') {
20
- db.prepare("UPDATE tasks SET status = 'paused', updated_at = ? WHERE id = ?").run(now, taskId);
28
+ db.prepare("UPDATE tasks SET status = 'paused', updated_at = ? WHERE id = ?").run(nowIso, taskId);
21
29
  broadcast('task_updated', { entity: 'task', action: 'task_status_changed', payload: db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) });
22
30
  }
23
31
  }
@@ -258,6 +258,16 @@ export async function updateTaskStatus(params) {
258
258
  return errorResponse(`Cannot transition from "${currentStatus}" to "${params.status}"`, 'INVALID_TRANSITION');
259
259
  }
260
260
  const ts = now();
261
+ // Auto-start a timer session when entering in_progress
262
+ if (params.status === 'in_progress') {
263
+ const existingOpen = db.prepare('SELECT id FROM sessions WHERE task_id = ? AND end IS NULL').get(params.id);
264
+ if (!existingOpen) {
265
+ const result = db.prepare('INSERT INTO sessions (task_id, start, end) VALUES (?, ?, ?)').run(params.id, ts, null);
266
+ const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(result.lastInsertRowid);
267
+ logActivity('timer_started', row.title, { entityType: 'task', entityId: params.id });
268
+ broadcastChange('timer', 'timer_started', { task_id: params.id, session, task_status: 'in_progress' });
269
+ }
270
+ }
261
271
  // Auto-close any active timer session when leaving an active state
262
272
  const terminalStatuses = ['done', 'partial_done', 'blocked', 'paused'];
263
273
  if (terminalStatuses.includes(params.status)) {
@@ -35,12 +35,23 @@ export async function pauseTimer(params) {
35
35
  return errorResponse('Task not found', 'NOT_FOUND');
36
36
  // Find open session
37
37
  const session = db.prepare('SELECT * FROM sessions WHERE task_id = ? AND end IS NULL').get(params.task_id);
38
- if (!session)
39
- return errorResponse('No active timer session for this task', 'NO_ACTIVE_SESSION');
40
38
  const endTime = now();
41
- const duration = new Date(endTime).getTime() - new Date(session.start).getTime();
42
- // Close session
43
- db.prepare('UPDATE sessions SET end = ? WHERE id = ?').run(endTime, session.id);
39
+ let duration;
40
+ let closedSession;
41
+ if (session) {
42
+ // Normal path — close the active session
43
+ duration = new Date(endTime).getTime() - new Date(session.start).getTime();
44
+ db.prepare('UPDATE sessions SET end = ? WHERE id = ?').run(endTime, session.id);
45
+ closedSession = { ...session, end: endTime };
46
+ }
47
+ else {
48
+ // Fallback — session already closed (orphan cleanup, auto-close)
49
+ const lastSession = db.prepare('SELECT * FROM sessions WHERE task_id = ? ORDER BY start DESC LIMIT 1').get(params.task_id);
50
+ if (!lastSession)
51
+ return errorResponse('No timer sessions found for this task', 'NO_ACTIVE_SESSION');
52
+ duration = new Date(lastSession.end).getTime() - new Date(lastSession.start).getTime();
53
+ closedSession = lastSession;
54
+ }
44
55
  // Update task to paused
45
56
  db.prepare('UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?').run('paused', endTime, params.task_id);
46
57
  logActivity('timer_paused', task.title, {
@@ -48,7 +59,7 @@ export async function pauseTimer(params) {
48
59
  entityType: 'task',
49
60
  entityId: params.task_id,
50
61
  });
51
- const pausedSession = { ...session, end: endTime, duration };
62
+ const pausedSession = { ...closedSession, duration };
52
63
  broadcastChange('timer', 'timer_paused', { task_id: params.task_id, session: pausedSession, task_status: 'paused' });
53
64
  return successResponse(pausedSession);
54
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalmasonto/taskflow-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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",