lobstakit-cloud 1.0.16 → 1.1.0
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/.github/workflows/claude-review.yml +57 -0
- package/lib/mc-db.js +437 -0
- package/lib/mc-routes.js +378 -0
- package/package.json +2 -1
- package/public/js/mission-control.js +1263 -0
- package/public/manage.html +3 -0
- package/public/mission-control.html +1193 -0
- package/server.js +9 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Claude Code Review
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
review:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pull-requests: write
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
|
|
18
|
+
- uses: anthropics/claude-code-action@v1
|
|
19
|
+
with:
|
|
20
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
21
|
+
track_progress: true
|
|
22
|
+
prompt: |
|
|
23
|
+
REPO: ${{ github.repository }}
|
|
24
|
+
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
25
|
+
|
|
26
|
+
You are a senior code reviewer for LobstaCloud (RedLobsta Cloud hosting platform).
|
|
27
|
+
Review this pull request thoroughly. Focus on:
|
|
28
|
+
|
|
29
|
+
**Security (HIGH PRIORITY — we run a hosting platform):**
|
|
30
|
+
- Auth/token handling (Bearer tokens, encryption, timing-safe comparison)
|
|
31
|
+
- Input validation and sanitization
|
|
32
|
+
- CORS, CSP, and header security
|
|
33
|
+
- Cloud-init / server provisioning safety
|
|
34
|
+
- No secrets in code, logs, or error messages
|
|
35
|
+
|
|
36
|
+
**Code Quality:**
|
|
37
|
+
- Logic errors and edge cases
|
|
38
|
+
- Error handling (fail gracefully, not silently)
|
|
39
|
+
- TypeScript type safety
|
|
40
|
+
- DRY violations and dead code
|
|
41
|
+
|
|
42
|
+
**Performance:**
|
|
43
|
+
- N+1 queries, unnecessary API calls
|
|
44
|
+
- Memory leaks, unbounded loops
|
|
45
|
+
|
|
46
|
+
**Architecture:**
|
|
47
|
+
- Separation of concerns
|
|
48
|
+
- API contract consistency
|
|
49
|
+
|
|
50
|
+
Be direct and specific. Skip praising obvious things.
|
|
51
|
+
Flag blockers as 🚨, suggestions as 💡, nits as 📝.
|
|
52
|
+
|
|
53
|
+
Use inline comments for specific code issues.
|
|
54
|
+
Use a single summary PR comment for overall assessment.
|
|
55
|
+
|
|
56
|
+
claude_args: |
|
|
57
|
+
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(cat:*),Bash(find:*),Bash(grep:*)"
|
package/lib/mc-db.js
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission Control — SQLite Database Module
|
|
3
|
+
*
|
|
4
|
+
* Separate database for multi-agent coordination.
|
|
5
|
+
* Uses better-sqlite3 for synchronous, fast access.
|
|
6
|
+
* Auto-creates tables on first access.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const Database = require('better-sqlite3');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
|
|
13
|
+
const DB_PATH = path.join(os.homedir(), '.lobstakit', 'mission-control.db');
|
|
14
|
+
|
|
15
|
+
let db = null;
|
|
16
|
+
|
|
17
|
+
function getDb() {
|
|
18
|
+
if (db) return db;
|
|
19
|
+
|
|
20
|
+
// Ensure directory exists
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const dir = path.dirname(DB_PATH);
|
|
23
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
24
|
+
|
|
25
|
+
db = new Database(DB_PATH);
|
|
26
|
+
db.pragma('journal_mode = WAL');
|
|
27
|
+
db.pragma('foreign_keys = ON');
|
|
28
|
+
|
|
29
|
+
initSchema();
|
|
30
|
+
return db;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function initSchema() {
|
|
34
|
+
db.exec(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
36
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
|
+
name TEXT NOT NULL,
|
|
38
|
+
role TEXT,
|
|
39
|
+
level TEXT DEFAULT 'SPC',
|
|
40
|
+
status TEXT DEFAULT 'idle',
|
|
41
|
+
session_key TEXT,
|
|
42
|
+
soul_summary TEXT,
|
|
43
|
+
avatar_emoji TEXT,
|
|
44
|
+
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
45
|
+
updated_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
49
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
+
title TEXT NOT NULL,
|
|
51
|
+
description TEXT,
|
|
52
|
+
status TEXT DEFAULT 'inbox' CHECK(status IN ('inbox','assigned','in_progress','review','done','blocked')),
|
|
53
|
+
priority INTEGER DEFAULT 0,
|
|
54
|
+
tags TEXT,
|
|
55
|
+
created_by INTEGER REFERENCES agents(id) ON DELETE SET NULL,
|
|
56
|
+
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
57
|
+
updated_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS task_assignees (
|
|
61
|
+
task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
|
|
62
|
+
agent_id INTEGER REFERENCES agents(id) ON DELETE CASCADE,
|
|
63
|
+
PRIMARY KEY(task_id, agent_id)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
67
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
+
task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
|
|
69
|
+
from_agent_id INTEGER REFERENCES agents(id) ON DELETE SET NULL,
|
|
70
|
+
content TEXT NOT NULL,
|
|
71
|
+
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS activities (
|
|
75
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
+
type TEXT NOT NULL,
|
|
77
|
+
agent_id INTEGER REFERENCES agents(id) ON DELETE SET NULL,
|
|
78
|
+
task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL,
|
|
79
|
+
message TEXT NOT NULL,
|
|
80
|
+
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
84
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
85
|
+
mentioned_agent_id INTEGER REFERENCES agents(id) ON DELETE CASCADE,
|
|
86
|
+
from_agent_id INTEGER REFERENCES agents(id) ON DELETE SET NULL,
|
|
87
|
+
task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL,
|
|
88
|
+
content TEXT,
|
|
89
|
+
delivered INTEGER DEFAULT 0,
|
|
90
|
+
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_activities_created ON activities(created_at DESC);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent ON notifications(mentioned_agent_id, delivered);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_messages_task ON messages(task_id);
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Helper: parse tags on read ──────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
function parseTags(row) {
|
|
103
|
+
if (!row) return row;
|
|
104
|
+
if (row.tags) {
|
|
105
|
+
try { row.tags = JSON.parse(row.tags); } catch { row.tags = []; }
|
|
106
|
+
}
|
|
107
|
+
return row;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseTagsArray(rows) {
|
|
111
|
+
return rows.map(parseTags);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Agents CRUD ─────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
function listAgents() {
|
|
117
|
+
return getDb().prepare('SELECT * FROM agents ORDER BY id').all();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getAgent(id) {
|
|
121
|
+
return getDb().prepare('SELECT * FROM agents WHERE id = ?').get(id);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function createAgent({ name, role, level, session_key, soul_summary, avatar_emoji }) {
|
|
125
|
+
const result = getDb().prepare(
|
|
126
|
+
`INSERT INTO agents (name, role, level, session_key, soul_summary, avatar_emoji)
|
|
127
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
128
|
+
).run(name, role || null, level || 'SPC', session_key || null, soul_summary || null, avatar_emoji || null);
|
|
129
|
+
return getAgent(result.lastInsertRowid);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function updateAgent(id, fields) {
|
|
133
|
+
const allowed = ['name', 'role', 'level', 'status', 'session_key', 'soul_summary', 'avatar_emoji'];
|
|
134
|
+
const updates = [];
|
|
135
|
+
const values = [];
|
|
136
|
+
for (const key of allowed) {
|
|
137
|
+
if (fields[key] !== undefined) {
|
|
138
|
+
updates.push(`${key} = ?`);
|
|
139
|
+
values.push(fields[key]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (updates.length === 0) return getAgent(id);
|
|
143
|
+
updates.push(`updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')`);
|
|
144
|
+
values.push(id);
|
|
145
|
+
getDb().prepare(`UPDATE agents SET ${updates.join(', ')} WHERE id = ?`).run(...values);
|
|
146
|
+
return getAgent(id);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function deleteAgent(id) {
|
|
150
|
+
return getDb().prepare('DELETE FROM agents WHERE id = ?').run(id);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Tasks CRUD ──────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
function listTasks(statusFilter) {
|
|
156
|
+
const d = getDb();
|
|
157
|
+
let rows;
|
|
158
|
+
if (statusFilter) {
|
|
159
|
+
rows = d.prepare('SELECT * FROM tasks WHERE status = ? ORDER BY priority DESC, id DESC').all(statusFilter);
|
|
160
|
+
} else {
|
|
161
|
+
rows = d.prepare('SELECT * FROM tasks ORDER BY priority DESC, id DESC').all();
|
|
162
|
+
}
|
|
163
|
+
// Attach assignees
|
|
164
|
+
const assigneeStmt = d.prepare(
|
|
165
|
+
`SELECT a.* FROM agents a
|
|
166
|
+
JOIN task_assignees ta ON ta.agent_id = a.id
|
|
167
|
+
WHERE ta.task_id = ?`
|
|
168
|
+
);
|
|
169
|
+
for (const row of rows) {
|
|
170
|
+
parseTags(row);
|
|
171
|
+
row.assignees = assigneeStmt.all(row.id);
|
|
172
|
+
}
|
|
173
|
+
return rows;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getTask(id) {
|
|
177
|
+
const d = getDb();
|
|
178
|
+
const row = d.prepare('SELECT * FROM tasks WHERE id = ?').get(id);
|
|
179
|
+
if (!row) return null;
|
|
180
|
+
parseTags(row);
|
|
181
|
+
row.assignees = d.prepare(
|
|
182
|
+
`SELECT a.* FROM agents a
|
|
183
|
+
JOIN task_assignees ta ON ta.agent_id = a.id
|
|
184
|
+
WHERE ta.task_id = ?`
|
|
185
|
+
).all(id);
|
|
186
|
+
return row;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function createTask({ title, description, status, priority, tags, created_by, assignee_ids }) {
|
|
190
|
+
const d = getDb();
|
|
191
|
+
const tagsJson = tags ? JSON.stringify(tags) : null;
|
|
192
|
+
const result = d.prepare(
|
|
193
|
+
`INSERT INTO tasks (title, description, status, priority, tags, created_by)
|
|
194
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
195
|
+
).run(title, description || null, status || 'inbox', priority || 0, tagsJson, created_by || null);
|
|
196
|
+
|
|
197
|
+
const taskId = result.lastInsertRowid;
|
|
198
|
+
|
|
199
|
+
// Assign agents
|
|
200
|
+
if (assignee_ids && assignee_ids.length > 0) {
|
|
201
|
+
const assignStmt = d.prepare('INSERT OR IGNORE INTO task_assignees (task_id, agent_id) VALUES (?, ?)');
|
|
202
|
+
for (const agentId of assignee_ids) {
|
|
203
|
+
assignStmt.run(taskId, agentId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return getTask(taskId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function updateTask(id, fields) {
|
|
211
|
+
const d = getDb();
|
|
212
|
+
const allowed = ['title', 'description', 'status', 'priority', 'tags'];
|
|
213
|
+
const updates = [];
|
|
214
|
+
const values = [];
|
|
215
|
+
for (const key of allowed) {
|
|
216
|
+
if (fields[key] !== undefined) {
|
|
217
|
+
if (key === 'tags') {
|
|
218
|
+
updates.push(`${key} = ?`);
|
|
219
|
+
values.push(JSON.stringify(fields[key]));
|
|
220
|
+
} else {
|
|
221
|
+
updates.push(`${key} = ?`);
|
|
222
|
+
values.push(fields[key]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (updates.length > 0) {
|
|
227
|
+
updates.push(`updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')`);
|
|
228
|
+
values.push(id);
|
|
229
|
+
d.prepare(`UPDATE tasks SET ${updates.join(', ')} WHERE id = ?`).run(...values);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Update assignees if provided
|
|
233
|
+
if (fields.assignee_ids !== undefined) {
|
|
234
|
+
d.prepare('DELETE FROM task_assignees WHERE task_id = ?').run(id);
|
|
235
|
+
if (fields.assignee_ids && fields.assignee_ids.length > 0) {
|
|
236
|
+
const assignStmt = d.prepare('INSERT OR IGNORE INTO task_assignees (task_id, agent_id) VALUES (?, ?)');
|
|
237
|
+
for (const agentId of fields.assignee_ids) {
|
|
238
|
+
assignStmt.run(id, agentId);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return getTask(id);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function deleteTask(id) {
|
|
247
|
+
return getDb().prepare('DELETE FROM tasks WHERE id = ?').run(id);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Messages ────────────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
function getMessages(taskId) {
|
|
253
|
+
return getDb().prepare(
|
|
254
|
+
`SELECT m.*, a.name as from_agent_name, a.avatar_emoji as from_agent_emoji
|
|
255
|
+
FROM messages m
|
|
256
|
+
LEFT JOIN agents a ON a.id = m.from_agent_id
|
|
257
|
+
WHERE m.task_id = ?
|
|
258
|
+
ORDER BY m.created_at ASC`
|
|
259
|
+
).all(taskId);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function createMessage({ task_id, from_agent_id, content }) {
|
|
263
|
+
const result = getDb().prepare(
|
|
264
|
+
'INSERT INTO messages (task_id, from_agent_id, content) VALUES (?, ?, ?)'
|
|
265
|
+
).run(task_id, from_agent_id || null, content);
|
|
266
|
+
return getDb().prepare(
|
|
267
|
+
`SELECT m.*, a.name as from_agent_name, a.avatar_emoji as from_agent_emoji
|
|
268
|
+
FROM messages m
|
|
269
|
+
LEFT JOIN agents a ON a.id = m.from_agent_id
|
|
270
|
+
WHERE m.id = ?`
|
|
271
|
+
).get(result.lastInsertRowid);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Activities ──────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
function getActivities(limit = 50) {
|
|
277
|
+
return getDb().prepare(
|
|
278
|
+
`SELECT act.*, a.name as agent_name, a.avatar_emoji as agent_emoji, t.title as task_title
|
|
279
|
+
FROM activities act
|
|
280
|
+
LEFT JOIN agents a ON a.id = act.agent_id
|
|
281
|
+
LEFT JOIN tasks t ON t.id = act.task_id
|
|
282
|
+
ORDER BY act.created_at DESC
|
|
283
|
+
LIMIT ?`
|
|
284
|
+
).all(limit);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function logActivity({ type, agent_id, task_id, message }) {
|
|
288
|
+
const result = getDb().prepare(
|
|
289
|
+
'INSERT INTO activities (type, agent_id, task_id, message) VALUES (?, ?, ?, ?)'
|
|
290
|
+
).run(type, agent_id || null, task_id || null, message);
|
|
291
|
+
return result.lastInsertRowid;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ─── Notifications ───────────────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
function getUnreadNotifications(agentId) {
|
|
297
|
+
return getDb().prepare(
|
|
298
|
+
`SELECT n.*, a.name as from_agent_name, a.avatar_emoji as from_agent_emoji, t.title as task_title
|
|
299
|
+
FROM notifications n
|
|
300
|
+
LEFT JOIN agents a ON a.id = n.from_agent_id
|
|
301
|
+
LEFT JOIN tasks t ON t.id = n.task_id
|
|
302
|
+
WHERE n.mentioned_agent_id = ? AND n.delivered = 0
|
|
303
|
+
ORDER BY n.created_at DESC`
|
|
304
|
+
).all(agentId);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function markNotificationRead(id) {
|
|
308
|
+
return getDb().prepare('UPDATE notifications SET delivered = 1 WHERE id = ?').run(id);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function createNotification({ mentioned_agent_id, from_agent_id, task_id, content }) {
|
|
312
|
+
const result = getDb().prepare(
|
|
313
|
+
'INSERT INTO notifications (mentioned_agent_id, from_agent_id, task_id, content) VALUES (?, ?, ?, ?)'
|
|
314
|
+
).run(mentioned_agent_id, from_agent_id || null, task_id || null, content);
|
|
315
|
+
return result.lastInsertRowid;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ─── Standup ─────────────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
function getStandupData() {
|
|
321
|
+
const d = getDb();
|
|
322
|
+
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
323
|
+
|
|
324
|
+
const completedToday = d.prepare(
|
|
325
|
+
`SELECT t.*, GROUP_CONCAT(a.name) as assignee_names
|
|
326
|
+
FROM tasks t
|
|
327
|
+
LEFT JOIN task_assignees ta ON ta.task_id = t.id
|
|
328
|
+
LEFT JOIN agents a ON a.id = ta.agent_id
|
|
329
|
+
WHERE t.status = 'done' AND t.updated_at >= ?
|
|
330
|
+
GROUP BY t.id
|
|
331
|
+
ORDER BY t.updated_at DESC`
|
|
332
|
+
).all(today + 'T00:00:00Z');
|
|
333
|
+
|
|
334
|
+
const inProgress = d.prepare(
|
|
335
|
+
`SELECT t.*, GROUP_CONCAT(a.name) as assignee_names
|
|
336
|
+
FROM tasks t
|
|
337
|
+
LEFT JOIN task_assignees ta ON ta.task_id = t.id
|
|
338
|
+
LEFT JOIN agents a ON a.id = ta.agent_id
|
|
339
|
+
WHERE t.status = 'in_progress'
|
|
340
|
+
GROUP BY t.id
|
|
341
|
+
ORDER BY t.priority DESC`
|
|
342
|
+
).all();
|
|
343
|
+
|
|
344
|
+
const blocked = d.prepare(
|
|
345
|
+
`SELECT t.*, GROUP_CONCAT(a.name) as assignee_names
|
|
346
|
+
FROM tasks t
|
|
347
|
+
LEFT JOIN task_assignees ta ON ta.task_id = t.id
|
|
348
|
+
LEFT JOIN agents a ON a.id = ta.agent_id
|
|
349
|
+
WHERE t.status = 'blocked'
|
|
350
|
+
GROUP BY t.id
|
|
351
|
+
ORDER BY t.priority DESC`
|
|
352
|
+
).all();
|
|
353
|
+
|
|
354
|
+
// Recent activity from today
|
|
355
|
+
const recentActivity = d.prepare(
|
|
356
|
+
`SELECT act.*, a.name as agent_name, a.avatar_emoji as agent_emoji, t.title as task_title
|
|
357
|
+
FROM activities act
|
|
358
|
+
LEFT JOIN agents a ON a.id = act.agent_id
|
|
359
|
+
LEFT JOIN tasks t ON t.id = act.task_id
|
|
360
|
+
WHERE act.created_at >= ?
|
|
361
|
+
ORDER BY act.created_at DESC
|
|
362
|
+
LIMIT 15`
|
|
363
|
+
).all(today + 'T00:00:00Z');
|
|
364
|
+
|
|
365
|
+
const agentCount = d.prepare('SELECT COUNT(*) as count FROM agents').get().count;
|
|
366
|
+
const activeAgents = d.prepare("SELECT COUNT(*) as count FROM agents WHERE status != 'offline'").get().count;
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
date: today,
|
|
370
|
+
summary: {
|
|
371
|
+
completed: completedToday.length,
|
|
372
|
+
inProgress: inProgress.length,
|
|
373
|
+
blocked: blocked.length,
|
|
374
|
+
agents: { total: agentCount, active: activeAgents }
|
|
375
|
+
},
|
|
376
|
+
completedToday: parseTagsArray(completedToday),
|
|
377
|
+
inProgress: parseTagsArray(inProgress),
|
|
378
|
+
blocked: parseTagsArray(blocked),
|
|
379
|
+
recentActivity
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── Seed ────────────────────────────────────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
function seedData() {
|
|
386
|
+
const d = getDb();
|
|
387
|
+
const count = d.prepare('SELECT COUNT(*) as count FROM agents').get().count;
|
|
388
|
+
if (count > 0) return { seeded: false, message: 'Agents already exist, skipping seed' };
|
|
389
|
+
|
|
390
|
+
const coordinator = createAgent({
|
|
391
|
+
name: 'Coordinator',
|
|
392
|
+
role: 'orchestrator',
|
|
393
|
+
level: 'LEAD',
|
|
394
|
+
avatar_emoji: '🦞',
|
|
395
|
+
soul_summary: 'The central coordinator for Mission Control. Manages task assignment and agent communication.'
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
logActivity({
|
|
399
|
+
type: 'system',
|
|
400
|
+
agent_id: coordinator.id,
|
|
401
|
+
message: 'Mission Control initialized. Coordinator agent created.'
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
return { seeded: true, agent: coordinator };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
module.exports = {
|
|
410
|
+
getDb,
|
|
411
|
+
// Agents
|
|
412
|
+
listAgents,
|
|
413
|
+
getAgent,
|
|
414
|
+
createAgent,
|
|
415
|
+
updateAgent,
|
|
416
|
+
deleteAgent,
|
|
417
|
+
// Tasks
|
|
418
|
+
listTasks,
|
|
419
|
+
getTask,
|
|
420
|
+
createTask,
|
|
421
|
+
updateTask,
|
|
422
|
+
deleteTask,
|
|
423
|
+
// Messages
|
|
424
|
+
getMessages,
|
|
425
|
+
createMessage,
|
|
426
|
+
// Activities
|
|
427
|
+
getActivities,
|
|
428
|
+
logActivity,
|
|
429
|
+
// Notifications
|
|
430
|
+
getUnreadNotifications,
|
|
431
|
+
markNotificationRead,
|
|
432
|
+
createNotification,
|
|
433
|
+
// Standup
|
|
434
|
+
getStandupData,
|
|
435
|
+
// Seed
|
|
436
|
+
seedData
|
|
437
|
+
};
|