@grec0/memory-bank-mcp 0.1.21 → 0.1.23
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/common/agentBoard.js +188 -291
- package/dist/common/agentBoardSqlite.js +486 -0
- package/dist/common/database.js +199 -0
- package/dist/common/projectKnowledgeService.js +23 -10
- package/dist/common/sessionLogger.js +171 -25
- package/dist/common/sessionState.js +24 -0
- package/dist/common/version.js +1 -1
- package/dist/index.js +6 -6
- package/dist/tools/generateProjectDocs.js +32 -5
- package/dist/tools/getProjectDocs.js +25 -8
- package/dist/tools/indexCode.js +25 -0
- package/dist/tools/manageAgents.js +30 -17
- package/dist/tools/readFile.js +24 -0
- package/dist/tools/searchMemory.js +6 -4
- package/dist/tools/writeFile.js +37 -0
- package/package.json +5 -3
|
@@ -1,307 +1,204 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent Board - Unified interface for agent coordination
|
|
3
|
+
*
|
|
4
|
+
* This module provides backward-compatible API while using SQLite internally.
|
|
5
|
+
* The AgentBoard class maintains the same method signatures but delegates to
|
|
6
|
+
* AgentBoardSqlite for actual operations.
|
|
7
|
+
*
|
|
8
|
+
* Migration: MD-based storage is deprecated. All operations now use SQLite.
|
|
9
|
+
*/
|
|
10
|
+
import { AgentBoardSqlite } from './agentBoardSqlite.js';
|
|
11
|
+
import { databaseManager } from './database.js';
|
|
12
|
+
import * as crypto from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* AgentBoard - Facade for agent coordination
|
|
15
|
+
*
|
|
16
|
+
* Maintains backward-compatible API while using SQLite storage.
|
|
17
|
+
* The basePath parameter is kept for compatibility but ignored (DB is global).
|
|
18
|
+
*/
|
|
4
19
|
export class AgentBoard {
|
|
5
|
-
|
|
20
|
+
sqlite;
|
|
6
21
|
projectId;
|
|
7
|
-
lockManager;
|
|
8
22
|
constructor(basePath, projectId) {
|
|
9
|
-
|
|
23
|
+
// basePath is ignored - SQLite DB is at ~/.memorybank/agentboard.db
|
|
10
24
|
this.projectId = projectId;
|
|
11
|
-
this.
|
|
12
|
-
}
|
|
25
|
+
this.sqlite = new AgentBoardSqlite(projectId);
|
|
26
|
+
}
|
|
27
|
+
// ========================================================================
|
|
28
|
+
// Board Content (Markdown export for compatibility)
|
|
29
|
+
// ========================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Get board content as Markdown (for display/debugging)
|
|
32
|
+
*/
|
|
13
33
|
async getBoardContent() {
|
|
14
|
-
|
|
15
|
-
|
|
34
|
+
return this.sqlite.exportToMarkdown();
|
|
35
|
+
}
|
|
36
|
+
// ========================================================================
|
|
37
|
+
// Agent Management
|
|
38
|
+
// ========================================================================
|
|
39
|
+
/**
|
|
40
|
+
* Register an agent with optional session ID
|
|
41
|
+
* Note: For new flow, use registerAgentWithHash() which returns the generated ID
|
|
42
|
+
*/
|
|
43
|
+
async registerAgent(agentId, sessionId) {
|
|
44
|
+
// Legacy method - agent ID already includes hash
|
|
45
|
+
// Just update/insert into SQLite
|
|
46
|
+
const db = databaseManager.getConnection();
|
|
47
|
+
const now = new Date().toISOString();
|
|
48
|
+
const effectiveSessionId = sessionId || crypto.randomUUID();
|
|
49
|
+
// Deactivate other agents for this project
|
|
50
|
+
db.prepare(`
|
|
51
|
+
UPDATE agents
|
|
52
|
+
SET status = 'INACTIVE', last_heartbeat = ?
|
|
53
|
+
WHERE project_id = ? AND status = 'ACTIVE' AND id != ?
|
|
54
|
+
`).run(now, this.projectId, agentId);
|
|
55
|
+
// Upsert this agent
|
|
56
|
+
db.prepare(`
|
|
57
|
+
INSERT INTO agents (id, project_id, session_id, status, focus, last_heartbeat)
|
|
58
|
+
VALUES (?, ?, ?, 'ACTIVE', '-', ?)
|
|
59
|
+
ON CONFLICT(id, project_id) DO UPDATE SET
|
|
60
|
+
session_id = excluded.session_id,
|
|
61
|
+
status = 'ACTIVE',
|
|
62
|
+
last_heartbeat = excluded.last_heartbeat
|
|
63
|
+
`).run(agentId, this.projectId, effectiveSessionId, now);
|
|
64
|
+
this.sqlite.logMessage(agentId, 'Agent registered');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Register agent and generate hash suffix (new flow)
|
|
68
|
+
* Client provides base ID (e.g., "Dev-VSCode-Gemini"), MCP generates full ID with hash
|
|
69
|
+
*/
|
|
70
|
+
registerAgentWithHash(baseAgentId, sessionId) {
|
|
71
|
+
return this.sqlite.registerAgent(baseAgentId, sessionId);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Update agent status and current focus
|
|
75
|
+
*/
|
|
76
|
+
async updateStatus(agentId, status, focus) {
|
|
77
|
+
this.sqlite.updateStatus(agentId, status, focus);
|
|
16
78
|
}
|
|
17
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Resolve a base agent ID to the actual active agent ID
|
|
81
|
+
*/
|
|
82
|
+
async resolveActiveAgentId(baseId) {
|
|
83
|
+
return this.sqlite.resolveActiveAgentId(baseId);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get session ID for an agent
|
|
87
|
+
*/
|
|
88
|
+
async getSessionId(agentId) {
|
|
89
|
+
return this.sqlite.getSessionId(agentId) || undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get the currently active agent for this project
|
|
93
|
+
*/
|
|
94
|
+
getActiveAgent() {
|
|
95
|
+
return this.sqlite.getActiveAgent();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get all agents (for session history)
|
|
99
|
+
*/
|
|
100
|
+
getAllAgents() {
|
|
101
|
+
return this.sqlite.getAllAgents();
|
|
102
|
+
}
|
|
103
|
+
// ========================================================================
|
|
104
|
+
// Task Management
|
|
105
|
+
// ========================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Create a task (project-centric)
|
|
108
|
+
* Note: assignedTo parameter is deprecated - tasks go to the project, not agent
|
|
109
|
+
*/
|
|
18
110
|
async createTask(title, fromAgentId, assignedTo, description) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const tasks = this.parseTable(content, 'Pending Tasks');
|
|
22
|
-
const now = new Date().toISOString();
|
|
23
|
-
// Columns: ID, Title, Assigned To, From, Status, Created At
|
|
24
|
-
tasks.push([taskId, title, assignedTo, fromAgentId, 'PENDING', now]);
|
|
25
|
-
return this.updateTable(content, 'Pending Tasks', ['ID', 'Title', 'Assigned To', 'From', 'Status', 'Created At'], tasks);
|
|
26
|
-
});
|
|
27
|
-
await this.logMessage(fromAgentId, `Created task ${taskId}: ${title}`);
|
|
28
|
-
return taskId;
|
|
111
|
+
// assignedTo is ignored in new model - tasks are project-centric
|
|
112
|
+
return this.sqlite.createTask(title, description, fromAgentId);
|
|
29
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Create an external task from another project
|
|
116
|
+
*/
|
|
30
117
|
async createExternalTask(title, fromProject, context) {
|
|
31
|
-
|
|
32
|
-
await this.updateBoard((content) => {
|
|
33
|
-
const requests = this.parseTable(content, 'External Requests');
|
|
34
|
-
const now = new Date().toISOString();
|
|
35
|
-
// Sanitize context to fit in a table cell (no newlines, escape pipes)
|
|
36
|
-
const safeContext = context.replace(/[\r\n]+/g, '<br/>').replace(/\|/g, '\\|');
|
|
37
|
-
// Columns: ID, Title, From Project, Context, Status, Received At
|
|
38
|
-
requests.push([taskId, title, fromProject, safeContext, 'PENDING', now]);
|
|
39
|
-
return this.updateTable(content, 'External Requests', ['ID', 'Title', 'From Project', 'Context', 'Status', 'Received At'], requests);
|
|
40
|
-
});
|
|
41
|
-
await this.logMessage('SYSTEM', `Received external prompt from project ${fromProject}: ${title}`);
|
|
42
|
-
return taskId;
|
|
118
|
+
return this.sqlite.createExternalTask(title, fromProject, context);
|
|
43
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Complete a task
|
|
122
|
+
*/
|
|
44
123
|
async completeTask(taskId, agentId) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
msgSectionIdx = i;
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
|
70
|
-
const msgLine = `- [${timestamp}] **${agentId}**: ${message}`;
|
|
71
|
-
if (msgSectionIdx !== -1) {
|
|
72
|
-
// Determine insertion point (after header)
|
|
73
|
-
lines.splice(msgSectionIdx + 1, 0, msgLine);
|
|
74
|
-
// Truncate logs if too long (keep last 20)
|
|
75
|
-
let nextHeaderIdx = -1;
|
|
76
|
-
for (let j = msgSectionIdx + 2; j < lines.length; j++) {
|
|
77
|
-
if (lines[j].startsWith('## ')) {
|
|
78
|
-
nextHeaderIdx = j;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const endOfMessages = nextHeaderIdx === -1 ? lines.length : nextHeaderIdx;
|
|
83
|
-
if (endOfMessages - (msgSectionIdx + 1) > 20) {
|
|
84
|
-
lines.splice(endOfMessages - 1, 1);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return lines.join('\n');
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
getBoardPath() {
|
|
91
|
-
return path.join(this.basePath, '.memorybank', 'projects', this.projectId, 'docs', 'agentBoard.md');
|
|
92
|
-
}
|
|
93
|
-
async ensureBoardExists() {
|
|
94
|
-
const boardPath = this.getBoardPath();
|
|
95
|
-
try {
|
|
96
|
-
await fs.access(boardPath);
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
const initialContent = `# Multi-Agent Board
|
|
100
|
-
|
|
101
|
-
## Active Agents
|
|
102
|
-
| Agent ID | Status | Current Focus | Session ID | Last Heartbeat |
|
|
103
|
-
|---|---|---|---|---|
|
|
104
|
-
|
|
105
|
-
## Pending Tasks
|
|
106
|
-
| ID | Title | Assigned To | From | Status | Created At |
|
|
107
|
-
|---|---|---|---|---|---|
|
|
108
|
-
|
|
109
|
-
## External Requests
|
|
110
|
-
| ID | Title | From Project | Context | Status | Received At |
|
|
111
|
-
|---|---|---|---|---|---|
|
|
112
|
-
|
|
113
|
-
## File Locks
|
|
114
|
-
| File Pattern | Claimed By | Since |
|
|
115
|
-
|---|---|---|
|
|
116
|
-
|
|
117
|
-
## Agent Messages
|
|
118
|
-
- [System]: Board initialized
|
|
119
|
-
`;
|
|
120
|
-
await fs.mkdir(path.dirname(boardPath), { recursive: true });
|
|
121
|
-
await fs.writeFile(boardPath, initialContent, 'utf-8');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
async registerAgent(agentId, sessionId) {
|
|
125
|
-
await this.updateBoard((content) => {
|
|
126
|
-
const agents = this.parseTable(content, 'Active Agents');
|
|
127
|
-
const existing = agents.findIndex(a => a[0]?.trim() === agentId);
|
|
128
|
-
const now = new Date().toISOString();
|
|
129
|
-
const session = sessionId || (existing >= 0 ? (agents[existing][3] || '') : ''); // Use col 3 as SessionID based on previous read
|
|
130
|
-
if (existing >= 0) {
|
|
131
|
-
// Check if it's already 5 cols or 4
|
|
132
|
-
if (agents[existing].length < 5) {
|
|
133
|
-
agents[existing] = [agentId, 'ACTIVE', '-', session, now];
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
// preserve existing fields if needed, but update timestamp
|
|
137
|
-
agents[existing] = [agentId, 'ACTIVE', '-', session, now];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
agents.push([agentId, 'ACTIVE', '-', session, now]);
|
|
142
|
-
}
|
|
143
|
-
return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Session ID', 'Last Heartbeat'], agents);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
async updateStatus(agentId, status, focus) {
|
|
147
|
-
await this.updateBoard((content) => {
|
|
148
|
-
let agents = this.parseTable(content, 'Active Agents');
|
|
149
|
-
// Migration: Ensure 5 columns
|
|
150
|
-
agents = agents.map(row => {
|
|
151
|
-
if (row.length === 4) {
|
|
152
|
-
return [row[0], row[1], row[2], '', row[3]];
|
|
153
|
-
}
|
|
154
|
-
return row;
|
|
155
|
-
});
|
|
156
|
-
const idx = agents.findIndex(a => a[0]?.trim() === agentId);
|
|
157
|
-
const now = new Date().toISOString();
|
|
158
|
-
if (idx >= 0) {
|
|
159
|
-
// Keep existing session ID
|
|
160
|
-
const currentSession = agents[idx][3] || '';
|
|
161
|
-
agents[idx] = [agentId, status, focus, currentSession, now];
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
agents.push([agentId, status, focus, '', now]);
|
|
165
|
-
}
|
|
166
|
-
return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Session ID', 'Last Heartbeat'], agents);
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
async getSessionId(agentId) {
|
|
170
|
-
const content = await this.getBoardContent();
|
|
171
|
-
const agents = this.parseTable(content, 'Active Agents');
|
|
172
|
-
const agent = agents.find(a => a[0]?.trim() === agentId);
|
|
173
|
-
if (agent) {
|
|
174
|
-
// Handle 5 cols
|
|
175
|
-
if (agent.length >= 5)
|
|
176
|
-
return agent[3].trim();
|
|
177
|
-
// Handle 4 cols (legacy) - no session ID
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
124
|
+
this.sqlite.completeTask(taskId, agentId);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get pending tasks for this project
|
|
128
|
+
*/
|
|
129
|
+
getPendingTasks() {
|
|
130
|
+
return this.sqlite.getPendingTasks();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Claim a task
|
|
134
|
+
*/
|
|
135
|
+
claimTask(taskId, agentId) {
|
|
136
|
+
return this.sqlite.claimTask(taskId, agentId);
|
|
137
|
+
}
|
|
138
|
+
// ========================================================================
|
|
139
|
+
// Resource Locks
|
|
140
|
+
// ========================================================================
|
|
141
|
+
/**
|
|
142
|
+
* Claim a resource lock
|
|
143
|
+
*/
|
|
182
144
|
async claimResource(agentId, resource) {
|
|
183
|
-
|
|
184
|
-
await this.updateBoard((content) => {
|
|
185
|
-
const locks = this.parseTable(content, 'File Locks');
|
|
186
|
-
// Check if already locked by someone else
|
|
187
|
-
const existing = locks.find(l => l[0]?.trim() === resource);
|
|
188
|
-
if (existing && existing[1]?.trim() !== agentId) {
|
|
189
|
-
success = false;
|
|
190
|
-
return content; // No change
|
|
191
|
-
}
|
|
192
|
-
// Add or update lock
|
|
193
|
-
const now = new Date().toISOString();
|
|
194
|
-
if (existing) {
|
|
195
|
-
existing[2] = now; // Renew timestamp
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
locks.push([resource, agentId, now]);
|
|
199
|
-
}
|
|
200
|
-
success = true;
|
|
201
|
-
return this.updateTable(content, 'File Locks', ['File Pattern', 'Claimed By', 'Since'], locks);
|
|
202
|
-
});
|
|
203
|
-
return success;
|
|
145
|
+
return this.sqlite.claimResource(agentId, resource);
|
|
204
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Release a resource lock
|
|
149
|
+
*/
|
|
205
150
|
async releaseResource(agentId, resource) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// skip header
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
result.push(cols);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
result.push(cols);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return result;
|
|
268
|
-
}
|
|
269
|
-
updateTable(content, headerName, headers, rows) {
|
|
270
|
-
const lines = content.split('\n');
|
|
271
|
-
let startIdx = -1;
|
|
272
|
-
let endIdx = -1;
|
|
273
|
-
// Validar rows (limpiar arrays vacíos o mal formados)
|
|
274
|
-
const cleanRows = rows.filter(r => r.length > 0);
|
|
275
|
-
for (let i = 0; i < lines.length; i++) {
|
|
276
|
-
if (lines[i].trim().startsWith(`## ${headerName}`)) {
|
|
277
|
-
startIdx = i;
|
|
278
|
-
// Find end of section (next ## or end of file)
|
|
279
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
280
|
-
if (lines[j].trim().startsWith('## ')) {
|
|
281
|
-
endIdx = j;
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (endIdx === -1)
|
|
286
|
-
endIdx = lines.length;
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const newTable = [
|
|
291
|
-
`## ${headerName}`,
|
|
292
|
-
`| ${headers.join(' | ')} |`,
|
|
293
|
-
`| ${headers.map(() => '---').join(' | ')} |`,
|
|
294
|
-
...cleanRows.map(row => `| ${row.join(' | ')} |`)
|
|
295
|
-
].join('\n');
|
|
296
|
-
if (startIdx === -1) {
|
|
297
|
-
// Append if not found
|
|
298
|
-
return content + '\n\n' + newTable;
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
// Replace section
|
|
302
|
-
const before = lines.slice(0, startIdx);
|
|
303
|
-
const after = lines.slice(endIdx);
|
|
304
|
-
return [...before, newTable, '', ...after].join('\n');
|
|
305
|
-
}
|
|
151
|
+
this.sqlite.releaseResource(agentId, resource);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get all locks for this project
|
|
155
|
+
*/
|
|
156
|
+
getLocks() {
|
|
157
|
+
return this.sqlite.getLocks();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cleanup orphaned locks
|
|
161
|
+
*/
|
|
162
|
+
cleanupOrphanedLocks() {
|
|
163
|
+
return this.sqlite.cleanupOrphanedLocks();
|
|
164
|
+
}
|
|
165
|
+
// ========================================================================
|
|
166
|
+
// Messages
|
|
167
|
+
// ========================================================================
|
|
168
|
+
/**
|
|
169
|
+
* Log a message to the agent board
|
|
170
|
+
*/
|
|
171
|
+
async logMessage(agentId, message) {
|
|
172
|
+
this.sqlite.logMessage(agentId, message);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get recent messages
|
|
176
|
+
*/
|
|
177
|
+
getMessages(limit = 20) {
|
|
178
|
+
return this.sqlite.getMessages(limit);
|
|
179
|
+
}
|
|
180
|
+
// ========================================================================
|
|
181
|
+
// Session Events
|
|
182
|
+
// ========================================================================
|
|
183
|
+
/**
|
|
184
|
+
* Log a session event
|
|
185
|
+
*/
|
|
186
|
+
logSessionEvent(sessionId, eventType, eventData, agentId) {
|
|
187
|
+
this.sqlite.logSessionEvent(sessionId, eventType, eventData, agentId);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get session history
|
|
191
|
+
*/
|
|
192
|
+
getSessionHistory(sessionId) {
|
|
193
|
+
return this.sqlite.getSessionHistory(sessionId);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get all sessions for this project
|
|
197
|
+
*/
|
|
198
|
+
getProjectSessions() {
|
|
199
|
+
return this.sqlite.getProjectSessions();
|
|
306
200
|
}
|
|
307
201
|
}
|
|
202
|
+
// Re-export SQLite implementation for direct access if needed
|
|
203
|
+
export { AgentBoardSqlite } from './agentBoardSqlite.js';
|
|
204
|
+
export { cleanupStaleAgents, cleanupAllOrphanedLocks } from './agentBoardSqlite.js';
|