gemini-helper-friend 2.0.0 → 2.0.2

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.
@@ -0,0 +1,191 @@
1
+ /**
2
+ * SQLite-based persistent task store
3
+ * Replaces in-memory Map to support async task persistence across MCP sessions
4
+ */
5
+ import Database from 'better-sqlite3';
6
+ import { join } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname } from 'path';
9
+ import { existsSync, mkdirSync } from 'fs';
10
+ import { TASK_ID_MIN, TASK_ID_MAX, TASK_EXPIRY_MS, CLEANUP_INTERVAL_MS, DATA_DIR_NAME, DB_FILE_NAME, } from './constants.js';
11
+ import { cleanupOrphanedTempFiles } from './cleanup.js';
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ // Database path - store in project root/.mcp-data directory
15
+ const DATA_DIR = join(__dirname, '../..', DATA_DIR_NAME);
16
+ const DB_PATH = join(DATA_DIR, DB_FILE_NAME);
17
+ // Ensure data directory exists
18
+ if (!existsSync(DATA_DIR)) {
19
+ mkdirSync(DATA_DIR, { recursive: true });
20
+ }
21
+ // Initialize database
22
+ const db = new Database(DB_PATH);
23
+ // Enable WAL mode for better concurrency
24
+ db.pragma('journal_mode = WAL');
25
+ // Create tasks table
26
+ db.exec(`
27
+ CREATE TABLE IF NOT EXISTS tasks (
28
+ id INTEGER PRIMARY KEY,
29
+ status TEXT NOT NULL,
30
+ task_type TEXT NOT NULL,
31
+ progress INTEGER NOT NULL DEFAULT 0,
32
+ result TEXT,
33
+ error TEXT,
34
+ started_at TEXT NOT NULL,
35
+ completed_at TEXT,
36
+ updated_at TEXT NOT NULL,
37
+ pid INTEGER
38
+ );
39
+
40
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
41
+ CREATE INDEX IF NOT EXISTS idx_tasks_started_at ON tasks(started_at);
42
+ `);
43
+ // Migration: Add pid column if it doesn't exist (for existing databases)
44
+ try {
45
+ db.exec(`ALTER TABLE tasks ADD COLUMN pid INTEGER;`);
46
+ console.error('[TaskStore] Migration: Added pid column to existing database');
47
+ }
48
+ catch (error) {
49
+ // Column already exists or other error - safe to ignore
50
+ if (!error.message?.includes('duplicate column')) {
51
+ console.error('[TaskStore] Migration note:', error.message);
52
+ }
53
+ }
54
+ // Prepared statements for better performance
55
+ const insertTaskStmt = db.prepare(`
56
+ INSERT INTO tasks (id, status, task_type, progress, started_at, updated_at)
57
+ VALUES (?, ?, ?, ?, ?, ?)
58
+ `);
59
+ const updateTaskStmt = db.prepare(`
60
+ UPDATE tasks
61
+ SET status = ?, progress = ?, result = ?, error = ?, completed_at = ?, updated_at = ?, pid = ?
62
+ WHERE id = ?
63
+ `);
64
+ const getTaskStmt = db.prepare(`
65
+ SELECT * FROM tasks WHERE id = ?
66
+ `);
67
+ const getCompletedTasksStmt = db.prepare(`
68
+ SELECT * FROM tasks WHERE status IN ('completed', 'failed')
69
+ ORDER BY completed_at DESC
70
+ `);
71
+ const deleteTaskStmt = db.prepare(`
72
+ DELETE FROM tasks WHERE id = ?
73
+ `);
74
+ const deleteExpiredTasksStmt = db.prepare(`
75
+ DELETE FROM tasks
76
+ WHERE datetime(started_at, '+1 hour') < datetime('now')
77
+ `);
78
+ // ID counter - get max ID from database on startup
79
+ let taskIdCounter = TASK_ID_MIN;
80
+ const maxIdRow = db.prepare('SELECT MAX(id) as max_id FROM tasks').get();
81
+ if (maxIdRow.max_id && maxIdRow.max_id >= taskIdCounter) {
82
+ taskIdCounter = maxIdRow.max_id + 1;
83
+ }
84
+ /**
85
+ * Generate unique 5-digit task ID
86
+ * Wraps around from TASK_ID_MAX back to TASK_ID_MIN
87
+ */
88
+ export function generateTaskId() {
89
+ const id = taskIdCounter;
90
+ taskIdCounter++;
91
+ if (taskIdCounter > TASK_ID_MAX) {
92
+ taskIdCounter = TASK_ID_MIN;
93
+ }
94
+ return id;
95
+ }
96
+ /**
97
+ * Create a new task in pending state
98
+ */
99
+ export function createTask(taskType) {
100
+ const id = generateTaskId();
101
+ const now = new Date().toISOString();
102
+ const task = {
103
+ id,
104
+ status: 'pending',
105
+ task_type: taskType,
106
+ progress: 0,
107
+ started_at: now,
108
+ updated_at: now,
109
+ };
110
+ insertTaskStmt.run(id, task.status, task.task_type, task.progress, task.started_at, task.updated_at);
111
+ // Schedule cleanup after expiry (note: this only works if process stays alive)
112
+ setTimeout(() => {
113
+ deleteTaskStmt.run(id);
114
+ }, TASK_EXPIRY_MS);
115
+ return task;
116
+ }
117
+ /**
118
+ * Update task status
119
+ */
120
+ export function updateTask(id, updates) {
121
+ const task = getTask(id);
122
+ if (!task)
123
+ return null;
124
+ // Merge updates
125
+ const updatedTask = { ...task, ...updates, updated_at: new Date().toISOString() };
126
+ updateTaskStmt.run(updatedTask.status, updatedTask.progress, updatedTask.result || null, updatedTask.error || null, updatedTask.completed_at || null, updatedTask.updated_at, updatedTask.pid || null, id);
127
+ return updatedTask;
128
+ }
129
+ /**
130
+ * Get task by ID
131
+ */
132
+ export function getTask(id) {
133
+ const row = getTaskStmt.get(id);
134
+ if (!row)
135
+ return null;
136
+ return {
137
+ id: row.id,
138
+ status: row.status,
139
+ task_type: row.task_type,
140
+ progress: row.progress,
141
+ result: row.result || undefined,
142
+ error: row.error || undefined,
143
+ started_at: row.started_at,
144
+ completed_at: row.completed_at || undefined,
145
+ updated_at: row.updated_at,
146
+ pid: row.pid || undefined,
147
+ };
148
+ }
149
+ /**
150
+ * Get all completed or failed tasks
151
+ */
152
+ export function getCompletedTasks() {
153
+ const rows = getCompletedTasksStmt.all();
154
+ return rows.map(row => ({
155
+ id: row.id,
156
+ status: row.status,
157
+ task_type: row.task_type,
158
+ progress: row.progress,
159
+ result: row.result || undefined,
160
+ error: row.error || undefined,
161
+ started_at: row.started_at,
162
+ completed_at: row.completed_at || undefined,
163
+ updated_at: row.updated_at,
164
+ pid: row.pid || undefined,
165
+ }));
166
+ }
167
+ /**
168
+ * Delete expired tasks (older than 1 hour)
169
+ * Call this periodically to clean up old tasks
170
+ */
171
+ export function cleanupExpiredTasks() {
172
+ const result = deleteExpiredTasksStmt.run();
173
+ return result.changes;
174
+ }
175
+ /**
176
+ * Close database connection (call on shutdown)
177
+ */
178
+ export function closeDatabase() {
179
+ db.close();
180
+ }
181
+ // Cleanup expired tasks and orphaned temp files on startup
182
+ cleanupExpiredTasks();
183
+ cleanupOrphanedTempFiles();
184
+ // Periodic cleanup at configured interval
185
+ setInterval(() => {
186
+ const deleted = cleanupExpiredTasks();
187
+ if (deleted > 0) {
188
+ console.error(`[TaskStore] Cleaned up ${deleted} expired tasks`);
189
+ }
190
+ }, CLEANUP_INTERVAL_MS);
191
+ //# sourceMappingURL=task-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-store.js","sourceRoot":"","sources":["../../src/tools/task-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EACL,WAAW,EACX,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,YAAY,GAEb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAkCtC,4DAA4D;AAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AACzD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;AAE7C,+BAA+B;AAC/B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,sBAAsB;AACtB,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAC;AAEjC,yCAAyC;AACzC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAEhC,qBAAqB;AACrB,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;CAgBP,CAAC,CAAC;AAEH,yEAAyE;AACzE,IAAI,CAAC;IACH,EAAE,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IACrD,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;AAChF,CAAC;AAAC,OAAO,KAAU,EAAE,CAAC;IACpB,wDAAwD;IACxD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACjD,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC;AAED,6CAA6C;AAC7C,MAAM,cAAc,GAAG,EAAE,CAAC,OAAO,CAAC;;;CAGjC,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,EAAE,CAAC,OAAO,CAAC;;;;CAIjC,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC;;CAE9B,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,EAAE,CAAC,OAAO,CAAC;;;CAGxC,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,EAAE,CAAC,OAAO,CAAC;;CAEjC,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,EAAE,CAAC,OAAO,CAAC;;;CAGzC,CAAC,CAAC;AAEH,mDAAmD;AACnD,IAAI,aAAa,GAAG,WAAW,CAAC;AAChC,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,EAA+B,CAAC;AACtG,IAAI,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;IACxD,aAAa,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,EAAE,GAAG,aAAa,CAAC;IACzB,aAAa,EAAE,CAAC;IAChB,IAAI,aAAa,GAAG,WAAW,EAAE,CAAC;QAChC,aAAa,GAAG,WAAW,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,QAAkB;IAC3C,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,IAAI,GAAc;QACtB,EAAE;QACF,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,QAAQ;QACnB,QAAQ,EAAE,CAAC;QACX,UAAU,EAAE,GAAG;QACf,UAAU,EAAE,GAAG;KAChB,CAAC;IAEF,cAAc,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAErG,+EAA+E;IAC/E,UAAU,CAAC,GAAG,EAAE;QACd,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC,EAAE,cAAc,CAAC,CAAC;IAEnB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,OAA2B;IAChE,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IACzB,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,gBAAgB;IAChB,MAAM,WAAW,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAElF,cAAc,CAAC,GAAG,CAChB,WAAW,CAAC,MAAM,EAClB,WAAW,CAAC,QAAQ,EACpB,WAAW,CAAC,MAAM,IAAI,IAAI,EAC1B,WAAW,CAAC,KAAK,IAAI,IAAI,EACzB,WAAW,CAAC,YAAY,IAAI,IAAI,EAChC,WAAW,CAAC,UAAU,EACtB,WAAW,CAAC,GAAG,IAAI,IAAI,EACvB,EAAE,CACH,CAAC;IAEF,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAwB,CAAC;IACvD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,MAAoB;QAChC,SAAS,EAAE,GAAG,CAAC,SAAqB;QACpC,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,SAAS;QAC/B,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,SAAS;QAC7B,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;QAC3C,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,SAAS;KAC1B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,IAAI,GAAG,qBAAqB,CAAC,GAAG,EAAe,CAAC;IAEtD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtB,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,MAAoB;QAChC,SAAS,EAAE,GAAG,CAAC,SAAqB;QACpC,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,SAAS;QAC/B,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,SAAS;QAC7B,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;QAC3C,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,SAAS;KAC1B,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,EAAE,CAAC;IAC5C,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,EAAE,CAAC,KAAK,EAAE,CAAC;AACb,CAAC;AAED,2DAA2D;AAC3D,mBAAmB,EAAE,CAAC;AACtB,wBAAwB,EAAE,CAAC;AAE3B,0CAA0C;AAC1C,WAAW,CAAC,GAAG,EAAE;IACf,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;IACtC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,0BAA0B,OAAO,gBAAgB,CAAC,CAAC;IACnE,CAAC;AACH,CAAC,EAAE,mBAAmB,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-helper-friend",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Your autonomous AI helper that works on your codebase using Gemini CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -48,6 +48,8 @@
48
48
  ],
49
49
  "dependencies": {
50
50
  "@modelcontextprotocol/sdk": "^0.5.0",
51
+ "@types/better-sqlite3": "^7.6.13",
52
+ "better-sqlite3": "^12.5.0",
51
53
  "yaml": "^2.3.4",
52
54
  "zod": "^3.25.76",
53
55
  "zod-to-json-schema": "^3.24.6"