claudeck 1.3.1 → 1.4.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/README.md +7 -3
- package/db/sqlite.js +1697 -0
- package/db.js +3 -1645
- package/package.json +2 -1
- package/plugins/tasks/server.js +21 -21
- package/public/css/ui/messages.css +25 -0
- package/public/js/core/api.js +23 -6
- package/public/js/core/ws.js +12 -0
- package/public/js/features/chat.js +4 -0
- package/public/js/features/sessions.js +102 -10
- package/public/js/ui/messages.js +42 -0
- package/public/js/ui/parallel.js +2 -4
- package/server/agent-loop.js +26 -26
- package/server/memory-extractor.js +4 -4
- package/server/memory-injector.js +11 -11
- package/server/memory-optimizer.js +2 -2
- package/server/notification-logger.js +5 -5
- package/server/orchestrator.js +15 -15
- package/server/push-sender.js +2 -2
- package/server/routes/agents.js +2 -2
- package/server/routes/memory.js +20 -20
- package/server/routes/messages.js +41 -10
- package/server/routes/notifications.js +20 -20
- package/server/routes/sessions.js +17 -17
- package/server/routes/stats.js +37 -37
- package/server/routes/worktrees.js +9 -9
- package/server/summarizer.js +3 -3
- package/server/ws-handler.js +151 -53
- package/server.js +2 -2
|
@@ -107,7 +107,7 @@ export function extractMemories(text) {
|
|
|
107
107
|
* @param {string|null} agentId - Source agent ID (for agent runs)
|
|
108
108
|
* @returns {number} Number of new memories saved
|
|
109
109
|
*/
|
|
110
|
-
export function captureMemories(projectPath, assistantText, sessionId = null, agentId = null) {
|
|
110
|
+
export async function captureMemories(projectPath, assistantText, sessionId = null, agentId = null) {
|
|
111
111
|
if (!projectPath || !assistantText) return 0;
|
|
112
112
|
|
|
113
113
|
const extracted = extractMemories(assistantText);
|
|
@@ -115,7 +115,7 @@ export function captureMemories(projectPath, assistantText, sessionId = null, ag
|
|
|
115
115
|
|
|
116
116
|
for (const { category, content } of extracted) {
|
|
117
117
|
try {
|
|
118
|
-
const result = createMemory(projectPath, category, content, sessionId, agentId);
|
|
118
|
+
const result = await createMemory(projectPath, category, content, sessionId, agentId);
|
|
119
119
|
if (!result.isDuplicate) saved++;
|
|
120
120
|
} catch {
|
|
121
121
|
// Ignore individual save errors
|
|
@@ -129,10 +129,10 @@ export function captureMemories(projectPath, assistantText, sessionId = null, ag
|
|
|
129
129
|
* Run memory maintenance for a project.
|
|
130
130
|
* Call this on session start to decay stale memories and clean expired ones.
|
|
131
131
|
*/
|
|
132
|
-
export function runMaintenance(projectPath) {
|
|
132
|
+
export async function runMaintenance(projectPath) {
|
|
133
133
|
if (!projectPath) return;
|
|
134
134
|
try {
|
|
135
|
-
maintainMemories(projectPath);
|
|
135
|
+
await maintainMemories(projectPath);
|
|
136
136
|
} catch {
|
|
137
137
|
// Non-critical — don't break session startup
|
|
138
138
|
}
|
|
@@ -36,11 +36,11 @@ function dedup(memories) {
|
|
|
36
36
|
* @param {string|null} userMessage - Current user message for relevance matching
|
|
37
37
|
* @returns {{ prompt: string|null, count: number }}
|
|
38
38
|
*/
|
|
39
|
-
export function buildMemoryPrompt(projectPath, limit = 10, userMessage = null) {
|
|
39
|
+
export async function buildMemoryPrompt(projectPath, limit = 10, userMessage = null) {
|
|
40
40
|
if (!projectPath) return { prompt: null, count: 0 };
|
|
41
41
|
|
|
42
42
|
// Get top memories by relevance score
|
|
43
|
-
let memories = getTopMemories(projectPath, limit);
|
|
43
|
+
let memories = await getTopMemories(projectPath, limit);
|
|
44
44
|
|
|
45
45
|
// If we have a user message, also search for query-relevant memories
|
|
46
46
|
if (userMessage && userMessage.length > 10) {
|
|
@@ -53,7 +53,7 @@ export function buildMemoryPrompt(projectPath, limit = 10, userMessage = null) {
|
|
|
53
53
|
.filter(w => w.length > 2 && !stopWords.has(w));
|
|
54
54
|
|
|
55
55
|
if (keywords.length > 0) {
|
|
56
|
-
const queryRelevant = searchMemories(projectPath, keywords.join(' '), limit);
|
|
56
|
+
const queryRelevant = await searchMemories(projectPath, keywords.join(' '), limit);
|
|
57
57
|
memories = dedup([...memories, ...queryRelevant]);
|
|
58
58
|
}
|
|
59
59
|
} catch {
|
|
@@ -68,7 +68,7 @@ export function buildMemoryPrompt(projectPath, limit = 10, userMessage = null) {
|
|
|
68
68
|
|
|
69
69
|
// Touch each memory to boost relevance on access
|
|
70
70
|
for (const m of memories) {
|
|
71
|
-
touchMemory(m.id);
|
|
71
|
+
await touchMemory(m.id);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
let prompt = `## Project Memory (persistent knowledge from previous sessions)\n`;
|
|
@@ -100,14 +100,14 @@ export function buildMemoryPrompt(projectPath, limit = 10, userMessage = null) {
|
|
|
100
100
|
/**
|
|
101
101
|
* Build a shorter memory section for agent prompts (tighter budget).
|
|
102
102
|
*/
|
|
103
|
-
export function buildAgentMemoryPrompt(projectPath, limit = 8) {
|
|
103
|
+
export async function buildAgentMemoryPrompt(projectPath, limit = 8) {
|
|
104
104
|
if (!projectPath) return null;
|
|
105
105
|
|
|
106
|
-
const memories = getTopMemories(projectPath, limit);
|
|
106
|
+
const memories = await getTopMemories(projectPath, limit);
|
|
107
107
|
if (!memories || memories.length === 0) return null;
|
|
108
108
|
|
|
109
109
|
for (const m of memories) {
|
|
110
|
-
touchMemory(m.id);
|
|
110
|
+
await touchMemory(m.id);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
let prompt = `## Prior Knowledge\n`;
|
|
@@ -157,7 +157,7 @@ export function parseMemoryBlocks(text) {
|
|
|
157
157
|
* @param {string|null} sessionId
|
|
158
158
|
* @returns {number} count of saved memories
|
|
159
159
|
*/
|
|
160
|
-
export function saveExplicitMemories(projectPath, assistantText, sessionId = null) {
|
|
160
|
+
export async function saveExplicitMemories(projectPath, assistantText, sessionId = null) {
|
|
161
161
|
if (!projectPath || !assistantText) return 0;
|
|
162
162
|
|
|
163
163
|
const blocks = parseMemoryBlocks(assistantText);
|
|
@@ -165,7 +165,7 @@ export function saveExplicitMemories(projectPath, assistantText, sessionId = nul
|
|
|
165
165
|
|
|
166
166
|
for (const { category, content } of blocks) {
|
|
167
167
|
try {
|
|
168
|
-
const result = createMemory(projectPath, category, content, sessionId, null);
|
|
168
|
+
const result = await createMemory(projectPath, category, content, sessionId, null);
|
|
169
169
|
if (!result.isDuplicate) saved++;
|
|
170
170
|
} catch {
|
|
171
171
|
// Ignore individual save errors
|
|
@@ -188,7 +188,7 @@ export function saveExplicitMemories(projectPath, assistantText, sessionId = nul
|
|
|
188
188
|
* @param {string|null} sessionId
|
|
189
189
|
* @returns {{ saved: boolean, content: string, category: string }|null}
|
|
190
190
|
*/
|
|
191
|
-
export function parseRememberCommand(message, projectPath, sessionId = null) {
|
|
191
|
+
export async function parseRememberCommand(message, projectPath, sessionId = null) {
|
|
192
192
|
if (!message || !projectPath) return null;
|
|
193
193
|
|
|
194
194
|
const trimmed = message.trim();
|
|
@@ -213,7 +213,7 @@ export function parseRememberCommand(message, projectPath, sessionId = null) {
|
|
|
213
213
|
const content = text.slice(0, 300);
|
|
214
214
|
|
|
215
215
|
try {
|
|
216
|
-
const result = createMemory(projectPath, category, content, sessionId, null);
|
|
216
|
+
const result = await createMemory(projectPath, category, content, sessionId, null);
|
|
217
217
|
return { saved: !result.isDuplicate, content, category };
|
|
218
218
|
} catch {
|
|
219
219
|
return null;
|
|
@@ -163,7 +163,7 @@ function parseOptimizerOutput(text) {
|
|
|
163
163
|
*/
|
|
164
164
|
export async function optimizeMemories(projectPath, onProgress = () => {}) {
|
|
165
165
|
// 1. Load all memories
|
|
166
|
-
const allMemories = listMemories(projectPath);
|
|
166
|
+
const allMemories = await listMemories(projectPath);
|
|
167
167
|
if (!allMemories.length) {
|
|
168
168
|
return { preview: { before: 0, after: 0, summary: "No memories to optimize." } };
|
|
169
169
|
}
|
|
@@ -251,7 +251,7 @@ export async function optimizeMemories(projectPath, onProgress = () => {}) {
|
|
|
251
251
|
* @param {Array<{category: string, content: string}>} optimized
|
|
252
252
|
* @returns {{ deleted: number, created: number }}
|
|
253
253
|
*/
|
|
254
|
-
export function applyOptimization(projectPath, optimized) {
|
|
254
|
+
export async function applyOptimization(projectPath, optimized) {
|
|
255
255
|
const db = getDb();
|
|
256
256
|
|
|
257
257
|
// Run in a transaction for atomicity
|
|
@@ -14,14 +14,14 @@ function broadcast(payload) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function logNotification(type, title, body = null, metadata = null, sourceSessionId = null, sourceAgentId = null) {
|
|
18
|
-
const notification = createNotification(type, title, body, metadata, sourceSessionId, sourceAgentId);
|
|
19
|
-
const unreadCount = getUnreadNotificationCount();
|
|
17
|
+
export async function logNotification(type, title, body = null, metadata = null, sourceSessionId = null, sourceAgentId = null) {
|
|
18
|
+
const notification = await createNotification(type, title, body, metadata, sourceSessionId, sourceAgentId);
|
|
19
|
+
const unreadCount = await getUnreadNotificationCount();
|
|
20
20
|
broadcast({ type: "notification:new", notification, unreadCount });
|
|
21
21
|
return notification;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function broadcastReadUpdate(ids) {
|
|
25
|
-
const unreadCount = getUnreadNotificationCount();
|
|
24
|
+
export async function broadcastReadUpdate(ids) {
|
|
25
|
+
const unreadCount = await getUnreadNotificationCount();
|
|
26
26
|
broadcast({ type: "notification:read", ids, unreadCount });
|
|
27
27
|
}
|
package/server/orchestrator.js
CHANGED
|
@@ -68,10 +68,10 @@ For each sub-task you want to delegate, output a fenced code block with the lang
|
|
|
68
68
|
${task}`;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function buildOrchestratorPromptWithMemory(task, agents, cwd) {
|
|
71
|
+
async function buildOrchestratorPromptWithMemory(task, agents, cwd) {
|
|
72
72
|
let prompt = buildOrchestratorPrompt(task, agents);
|
|
73
73
|
if (cwd) {
|
|
74
|
-
const memPrompt = buildAgentMemoryPrompt(cwd, 6);
|
|
74
|
+
const memPrompt = await buildAgentMemoryPrompt(cwd, 6);
|
|
75
75
|
if (memPrompt) {
|
|
76
76
|
prompt += '\n\n' + memPrompt;
|
|
77
77
|
}
|
|
@@ -186,7 +186,7 @@ export async function runOrchestrator({
|
|
|
186
186
|
orchSend({ type: "orchestrator_phase", phase: "planning" });
|
|
187
187
|
|
|
188
188
|
try {
|
|
189
|
-
const prompt = buildOrchestratorPromptWithMemory(task, agents, cwd);
|
|
189
|
+
const prompt = await buildOrchestratorPromptWithMemory(task, agents, cwd);
|
|
190
190
|
const q = query({ prompt, options: plannerOpts });
|
|
191
191
|
|
|
192
192
|
for await (const sdkMsg of q) {
|
|
@@ -198,20 +198,20 @@ export async function runOrchestrator({
|
|
|
198
198
|
resolvedSid = ourSid;
|
|
199
199
|
sessionIds.set(ourSid, claudeSessionId);
|
|
200
200
|
|
|
201
|
-
if (!getSession(ourSid)) {
|
|
202
|
-
createSession(
|
|
201
|
+
if (!await getSession(ourSid)) {
|
|
202
|
+
await createSession(
|
|
203
203
|
ourSid,
|
|
204
204
|
claudeSessionId,
|
|
205
205
|
projectName || "Orchestrator",
|
|
206
206
|
cwd || "",
|
|
207
207
|
);
|
|
208
|
-
updateSessionTitle(ourSid, `Orchestrator: ${task.slice(0, 60)}`);
|
|
208
|
+
await updateSessionTitle(ourSid, `Orchestrator: ${task.slice(0, 60)}`);
|
|
209
209
|
} else {
|
|
210
|
-
updateClaudeSessionId(ourSid, claudeSessionId);
|
|
210
|
+
await updateClaudeSessionId(ourSid, claudeSessionId);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
orchSend({ type: "session", sessionId: ourSid });
|
|
214
|
-
addMessage(
|
|
214
|
+
await addMessage(
|
|
215
215
|
resolvedSid,
|
|
216
216
|
"user",
|
|
217
217
|
JSON.stringify({ text: `[Orchestrator] ${task}` }),
|
|
@@ -226,7 +226,7 @@ export async function runOrchestrator({
|
|
|
226
226
|
plannerText += block.text;
|
|
227
227
|
orchSend({ type: "text", text: block.text });
|
|
228
228
|
if (resolvedSid) {
|
|
229
|
-
addMessage(
|
|
229
|
+
await addMessage(
|
|
230
230
|
resolvedSid,
|
|
231
231
|
"assistant",
|
|
232
232
|
JSON.stringify({ text: block.text }),
|
|
@@ -252,7 +252,7 @@ export async function runOrchestrator({
|
|
|
252
252
|
Object.keys(sdkMsg.modelUsage || {})[0] || null;
|
|
253
253
|
|
|
254
254
|
if (resolvedSid) {
|
|
255
|
-
addCost(
|
|
255
|
+
await addCost(
|
|
256
256
|
resolvedSid,
|
|
257
257
|
costUsd,
|
|
258
258
|
durationMs,
|
|
@@ -274,7 +274,7 @@ export async function runOrchestrator({
|
|
|
274
274
|
duration_ms: durationMs,
|
|
275
275
|
num_turns: numTurns,
|
|
276
276
|
cost_usd: costUsd,
|
|
277
|
-
totalCost: getTotalCost(),
|
|
277
|
+
totalCost: await getTotalCost(),
|
|
278
278
|
input_tokens: inputTokens,
|
|
279
279
|
output_tokens: outputTokens,
|
|
280
280
|
model: resultModel,
|
|
@@ -375,7 +375,7 @@ export async function runOrchestrator({
|
|
|
375
375
|
if (result?.claudeSessionId) chainResumeId = result.claudeSessionId;
|
|
376
376
|
|
|
377
377
|
// Read context that the agent stored
|
|
378
|
-
const ctx = getAllAgentContext(runId).find(
|
|
378
|
+
const ctx = (await getAllAgentContext(runId)).find(
|
|
379
379
|
(c) => c.agent_id === agentDef.id,
|
|
380
380
|
);
|
|
381
381
|
|
|
@@ -439,7 +439,7 @@ export async function runOrchestrator({
|
|
|
439
439
|
if (block.type === "text" && block.text) {
|
|
440
440
|
orchSend({ type: "text", text: block.text });
|
|
441
441
|
if (resolvedSid) {
|
|
442
|
-
addMessage(
|
|
442
|
+
await addMessage(
|
|
443
443
|
resolvedSid,
|
|
444
444
|
"assistant",
|
|
445
445
|
JSON.stringify({ text: block.text }),
|
|
@@ -465,7 +465,7 @@ export async function runOrchestrator({
|
|
|
465
465
|
Object.keys(sdkMsg.modelUsage || {})[0] || null;
|
|
466
466
|
|
|
467
467
|
if (resolvedSid) {
|
|
468
|
-
addCost(
|
|
468
|
+
await addCost(
|
|
469
469
|
resolvedSid,
|
|
470
470
|
costUsd,
|
|
471
471
|
durationMs,
|
|
@@ -487,7 +487,7 @@ export async function runOrchestrator({
|
|
|
487
487
|
duration_ms: durationMs,
|
|
488
488
|
num_turns: numTurns,
|
|
489
489
|
cost_usd: costUsd,
|
|
490
|
-
totalCost: getTotalCost(),
|
|
490
|
+
totalCost: await getTotalCost(),
|
|
491
491
|
input_tokens: inputTokens,
|
|
492
492
|
output_tokens: outputTokens,
|
|
493
493
|
model: resultModel,
|
package/server/push-sender.js
CHANGED
|
@@ -9,7 +9,7 @@ export function initPushSender(webpush) {
|
|
|
9
9
|
export async function sendPushNotification(title, body, tag) {
|
|
10
10
|
if (!webpushInstance) return;
|
|
11
11
|
|
|
12
|
-
const subs = getAllPushSubscriptions();
|
|
12
|
+
const subs = await getAllPushSubscriptions();
|
|
13
13
|
if (!subs.length) return;
|
|
14
14
|
|
|
15
15
|
const payload = JSON.stringify({ title, body, tag });
|
|
@@ -23,7 +23,7 @@ export async function sendPushNotification(title, body, tag) {
|
|
|
23
23
|
);
|
|
24
24
|
} catch (err) {
|
|
25
25
|
if (err.statusCode === 404 || err.statusCode === 410) {
|
|
26
|
-
deletePushSubscription(sub.endpoint);
|
|
26
|
+
await deletePushSubscription(sub.endpoint);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
})
|
package/server/routes/agents.js
CHANGED
|
@@ -46,9 +46,9 @@ function slugify(text) {
|
|
|
46
46
|
|
|
47
47
|
// ── Agent Context (shared memory) ──
|
|
48
48
|
|
|
49
|
-
router.get("/context/:runId", (req, res) => {
|
|
49
|
+
router.get("/context/:runId", async (req, res) => {
|
|
50
50
|
try {
|
|
51
|
-
const rows = getAllAgentContext(req.params.runId);
|
|
51
|
+
const rows = await getAllAgentContext(req.params.runId);
|
|
52
52
|
res.json(rows);
|
|
53
53
|
} catch (err) {
|
|
54
54
|
res.status(500).json({ error: err.message });
|
package/server/routes/memory.js
CHANGED
|
@@ -11,11 +11,11 @@ const router = Router();
|
|
|
11
11
|
const CATEGORIES = ["convention", "decision", "discovery", "warning"];
|
|
12
12
|
|
|
13
13
|
// List memories for a project
|
|
14
|
-
router.get("/", (req, res) => {
|
|
14
|
+
router.get("/", async (req, res) => {
|
|
15
15
|
try {
|
|
16
16
|
const { project, category } = req.query;
|
|
17
17
|
if (!project) return res.status(400).json({ error: "project query param required" });
|
|
18
|
-
const memories = listMemories(project, category || null);
|
|
18
|
+
const memories = await listMemories(project, category || null);
|
|
19
19
|
res.json(memories);
|
|
20
20
|
} catch (err) {
|
|
21
21
|
res.status(500).json({ error: err.message });
|
|
@@ -23,11 +23,11 @@ router.get("/", (req, res) => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
// Search memories
|
|
26
|
-
router.get("/search", (req, res) => {
|
|
26
|
+
router.get("/search", async (req, res) => {
|
|
27
27
|
try {
|
|
28
28
|
const { project, q, limit } = req.query;
|
|
29
29
|
if (!project || !q) return res.status(400).json({ error: "project and q required" });
|
|
30
|
-
const results = searchMemories(project, q, Number(limit) || 20);
|
|
30
|
+
const results = await searchMemories(project, q, Number(limit) || 20);
|
|
31
31
|
res.json(results);
|
|
32
32
|
} catch (err) {
|
|
33
33
|
res.status(500).json({ error: err.message });
|
|
@@ -35,13 +35,13 @@ router.get("/search", (req, res) => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
// Get top relevant memories (used for prompt injection)
|
|
38
|
-
router.get("/top", (req, res) => {
|
|
38
|
+
router.get("/top", async (req, res) => {
|
|
39
39
|
try {
|
|
40
40
|
const { project, limit } = req.query;
|
|
41
41
|
if (!project) return res.status(400).json({ error: "project required" });
|
|
42
|
-
const memories = getTopMemories(project, Number(limit) || 10);
|
|
42
|
+
const memories = await getTopMemories(project, Number(limit) || 10);
|
|
43
43
|
// Touch each memory to boost relevance
|
|
44
|
-
for (const m of memories) touchMemory(m.id);
|
|
44
|
+
for (const m of memories) await touchMemory(m.id);
|
|
45
45
|
res.json(memories);
|
|
46
46
|
} catch (err) {
|
|
47
47
|
res.status(500).json({ error: err.message });
|
|
@@ -49,12 +49,12 @@ router.get("/top", (req, res) => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
// Get stats
|
|
52
|
-
router.get("/stats", (req, res) => {
|
|
52
|
+
router.get("/stats", async (req, res) => {
|
|
53
53
|
try {
|
|
54
54
|
const { project } = req.query;
|
|
55
55
|
if (!project) return res.status(400).json({ error: "project required" });
|
|
56
|
-
const stats = getMemoryStats(project);
|
|
57
|
-
const counts = getMemoryCounts(project);
|
|
56
|
+
const stats = await getMemoryStats(project);
|
|
57
|
+
const counts = await getMemoryCounts(project);
|
|
58
58
|
res.json({ ...stats, categories: counts });
|
|
59
59
|
} catch (err) {
|
|
60
60
|
res.status(500).json({ error: err.message });
|
|
@@ -62,14 +62,14 @@ router.get("/stats", (req, res) => {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
// Create a memory
|
|
65
|
-
router.post("/", (req, res) => {
|
|
65
|
+
router.post("/", async (req, res) => {
|
|
66
66
|
try {
|
|
67
67
|
const { project, category, content, sessionId, agentId } = req.body;
|
|
68
68
|
if (!project || !content) {
|
|
69
69
|
return res.status(400).json({ error: "project and content required" });
|
|
70
70
|
}
|
|
71
71
|
const cat = CATEGORIES.includes(category) ? category : "discovery";
|
|
72
|
-
const info = createMemory(project, cat, content.trim(), sessionId || null, agentId || null);
|
|
72
|
+
const info = await createMemory(project, cat, content.trim(), sessionId || null, agentId || null);
|
|
73
73
|
res.json({ id: info.lastInsertRowid });
|
|
74
74
|
} catch (err) {
|
|
75
75
|
res.status(500).json({ error: err.message });
|
|
@@ -77,13 +77,13 @@ router.post("/", (req, res) => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
// Update a memory
|
|
80
|
-
router.put("/:id", (req, res) => {
|
|
80
|
+
router.put("/:id", async (req, res) => {
|
|
81
81
|
try {
|
|
82
82
|
const id = Number(req.params.id);
|
|
83
83
|
const { content, category } = req.body;
|
|
84
84
|
if (!content) return res.status(400).json({ error: "content required" });
|
|
85
85
|
const cat = CATEGORIES.includes(category) ? category : "discovery";
|
|
86
|
-
updateMemory(id, content.trim(), cat);
|
|
86
|
+
await updateMemory(id, content.trim(), cat);
|
|
87
87
|
res.json({ ok: true });
|
|
88
88
|
} catch (err) {
|
|
89
89
|
res.status(500).json({ error: err.message });
|
|
@@ -91,10 +91,10 @@ router.put("/:id", (req, res) => {
|
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
// Delete a memory
|
|
94
|
-
router.delete("/:id", (req, res) => {
|
|
94
|
+
router.delete("/:id", async (req, res) => {
|
|
95
95
|
try {
|
|
96
96
|
const id = Number(req.params.id);
|
|
97
|
-
deleteMemory(id);
|
|
97
|
+
await deleteMemory(id);
|
|
98
98
|
res.json({ ok: true });
|
|
99
99
|
} catch (err) {
|
|
100
100
|
res.status(500).json({ error: err.message });
|
|
@@ -102,11 +102,11 @@ router.delete("/:id", (req, res) => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
// Decay old memories and clean expired
|
|
105
|
-
router.post("/maintain", (req, res) => {
|
|
105
|
+
router.post("/maintain", async (req, res) => {
|
|
106
106
|
try {
|
|
107
107
|
const { project } = req.body;
|
|
108
108
|
if (!project) return res.status(400).json({ error: "project required" });
|
|
109
|
-
maintainMemories(project);
|
|
109
|
+
await maintainMemories(project);
|
|
110
110
|
res.json({ ok: true });
|
|
111
111
|
} catch (err) {
|
|
112
112
|
res.status(500).json({ error: err.message });
|
|
@@ -129,13 +129,13 @@ router.post("/optimize", async (req, res) => {
|
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
// Apply optimization — replace memories with optimized set
|
|
132
|
-
router.post("/optimize/apply", (req, res) => {
|
|
132
|
+
router.post("/optimize/apply", async (req, res) => {
|
|
133
133
|
try {
|
|
134
134
|
const { project, optimized } = req.body;
|
|
135
135
|
if (!project || !Array.isArray(optimized)) {
|
|
136
136
|
return res.status(400).json({ error: "project and optimized array required" });
|
|
137
137
|
}
|
|
138
|
-
const result = applyOptimization(project, optimized);
|
|
138
|
+
const result = await applyOptimization(project, optimized);
|
|
139
139
|
res.json(result);
|
|
140
140
|
} catch (err) {
|
|
141
141
|
console.error("Apply optimization error:", err);
|
|
@@ -1,32 +1,63 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getMessages, getMessagesByChatId, getMessagesNoChatId,
|
|
4
|
+
getRecentMessages, getRecentMessagesByChatId, getRecentMessagesNoChatId,
|
|
5
|
+
getOlderMessages, getOlderMessagesByChatId, getOlderMessagesNoChatId,
|
|
6
|
+
} from "../../db.js";
|
|
3
7
|
|
|
4
8
|
const router = Router();
|
|
5
9
|
|
|
6
|
-
// Get all messages for a session
|
|
7
|
-
router.get("/:id/messages", (req, res) => {
|
|
10
|
+
// Get all messages for a session (supports ?limit=N&before=ID)
|
|
11
|
+
router.get("/:id/messages", async (req, res) => {
|
|
8
12
|
try {
|
|
9
|
-
const
|
|
13
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 0;
|
|
14
|
+
const before = req.query.before ? parseInt(req.query.before, 10) : 0;
|
|
15
|
+
let messages;
|
|
16
|
+
if (limit > 0 && before > 0) {
|
|
17
|
+
messages = await getOlderMessages(req.params.id, before, limit);
|
|
18
|
+
} else if (limit > 0) {
|
|
19
|
+
messages = await getRecentMessages(req.params.id, limit);
|
|
20
|
+
} else {
|
|
21
|
+
messages = await getMessages(req.params.id);
|
|
22
|
+
}
|
|
10
23
|
res.json(messages);
|
|
11
24
|
} catch (err) {
|
|
12
25
|
res.status(500).json({ error: err.message });
|
|
13
26
|
}
|
|
14
27
|
});
|
|
15
28
|
|
|
16
|
-
// Get messages filtered by chatId
|
|
17
|
-
router.get("/:id/messages/:chatId", (req, res) => {
|
|
29
|
+
// Get messages filtered by chatId (supports ?limit=N&before=ID)
|
|
30
|
+
router.get("/:id/messages/:chatId", async (req, res) => {
|
|
18
31
|
try {
|
|
19
|
-
const
|
|
32
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 0;
|
|
33
|
+
const before = req.query.before ? parseInt(req.query.before, 10) : 0;
|
|
34
|
+
let messages;
|
|
35
|
+
if (limit > 0 && before > 0) {
|
|
36
|
+
messages = await getOlderMessagesByChatId(req.params.id, req.params.chatId, before, limit);
|
|
37
|
+
} else if (limit > 0) {
|
|
38
|
+
messages = await getRecentMessagesByChatId(req.params.id, req.params.chatId, limit);
|
|
39
|
+
} else {
|
|
40
|
+
messages = await getMessagesByChatId(req.params.id, req.params.chatId);
|
|
41
|
+
}
|
|
20
42
|
res.json(messages);
|
|
21
43
|
} catch (err) {
|
|
22
44
|
res.status(500).json({ error: err.message });
|
|
23
45
|
}
|
|
24
46
|
});
|
|
25
47
|
|
|
26
|
-
// Get messages where chat_id IS NULL (
|
|
27
|
-
router.get("/:id/messages-single", (req, res) => {
|
|
48
|
+
// Get messages where chat_id IS NULL (supports ?limit=N&before=ID)
|
|
49
|
+
router.get("/:id/messages-single", async (req, res) => {
|
|
28
50
|
try {
|
|
29
|
-
const
|
|
51
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 0;
|
|
52
|
+
const before = req.query.before ? parseInt(req.query.before, 10) : 0;
|
|
53
|
+
let messages;
|
|
54
|
+
if (limit > 0 && before > 0) {
|
|
55
|
+
messages = await getOlderMessagesNoChatId(req.params.id, before, limit);
|
|
56
|
+
} else if (limit > 0) {
|
|
57
|
+
messages = await getRecentMessagesNoChatId(req.params.id, limit);
|
|
58
|
+
} else {
|
|
59
|
+
messages = await getMessagesNoChatId(req.params.id);
|
|
60
|
+
}
|
|
30
61
|
res.json(messages);
|
|
31
62
|
} catch (err) {
|
|
32
63
|
res.status(500).json({ error: err.message });
|
|
@@ -26,67 +26,67 @@ router.get("/vapid-public-key", (req, res) => {
|
|
|
26
26
|
res.json({ key: vapidPublicKey });
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
router.post("/subscribe", (req, res) => {
|
|
29
|
+
router.post("/subscribe", async (req, res) => {
|
|
30
30
|
const { endpoint, keys } = req.body;
|
|
31
31
|
if (!endpoint || !keys?.p256dh || !keys?.auth) {
|
|
32
32
|
return res.status(400).json({ error: "Invalid subscription" });
|
|
33
33
|
}
|
|
34
|
-
upsertPushSubscription(endpoint, keys.p256dh, keys.auth);
|
|
34
|
+
await upsertPushSubscription(endpoint, keys.p256dh, keys.auth);
|
|
35
35
|
res.json({ ok: true });
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
router.post("/unsubscribe", (req, res) => {
|
|
38
|
+
router.post("/unsubscribe", async (req, res) => {
|
|
39
39
|
const { endpoint } = req.body;
|
|
40
40
|
if (!endpoint) {
|
|
41
41
|
return res.status(400).json({ error: "Missing endpoint" });
|
|
42
42
|
}
|
|
43
|
-
deletePushSubscription(endpoint);
|
|
43
|
+
await deletePushSubscription(endpoint);
|
|
44
44
|
res.json({ ok: true });
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// ── Create notification (from frontend) ───────────────────
|
|
48
|
-
router.post("/create", (req, res) => {
|
|
48
|
+
router.post("/create", async (req, res) => {
|
|
49
49
|
const { type, title, body, metadata, sourceSessionId, sourceAgentId } = req.body;
|
|
50
50
|
if (!type || !title) {
|
|
51
51
|
return res.status(400).json({ error: "type and title are required" });
|
|
52
52
|
}
|
|
53
|
-
const notification = logNotification(type, title, body || null, metadata || null, sourceSessionId || null, sourceAgentId || null);
|
|
53
|
+
const notification = await logNotification(type, title, body || null, metadata || null, sourceSessionId || null, sourceAgentId || null);
|
|
54
54
|
res.json(notification);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
// ── Notification history & management ─────────────────────
|
|
58
|
-
router.get("/history", (req, res) => {
|
|
58
|
+
router.get("/history", async (req, res) => {
|
|
59
59
|
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
|
|
60
60
|
const offset = parseInt(req.query.offset) || 0;
|
|
61
61
|
const unreadOnly = req.query.unread_only === "true";
|
|
62
62
|
const type = req.query.type || null;
|
|
63
|
-
const items = getNotificationHistory(limit, offset, unreadOnly, type);
|
|
63
|
+
const items = await getNotificationHistory(limit, offset, unreadOnly, type);
|
|
64
64
|
res.json(items);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
router.get("/unread-count", (_req, res) => {
|
|
68
|
-
res.json({ count: getUnreadNotificationCount() });
|
|
67
|
+
router.get("/unread-count", async (_req, res) => {
|
|
68
|
+
res.json({ count: await getUnreadNotificationCount() });
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
router.post("/read", (req, res) => {
|
|
71
|
+
router.post("/read", async (req, res) => {
|
|
72
72
|
const { ids, all, before } = req.body;
|
|
73
73
|
if (all) {
|
|
74
|
-
markAllNotificationsRead();
|
|
75
|
-
broadcastReadUpdate([]);
|
|
74
|
+
await markAllNotificationsRead();
|
|
75
|
+
await broadcastReadUpdate([]);
|
|
76
76
|
} else if (before) {
|
|
77
|
-
markNotificationsReadBefore(before);
|
|
78
|
-
broadcastReadUpdate([]);
|
|
77
|
+
await markNotificationsReadBefore(before);
|
|
78
|
+
await broadcastReadUpdate([]);
|
|
79
79
|
} else if (Array.isArray(ids) && ids.length > 0) {
|
|
80
|
-
markNotificationsRead(ids);
|
|
81
|
-
broadcastReadUpdate(ids);
|
|
80
|
+
await markNotificationsRead(ids);
|
|
81
|
+
await broadcastReadUpdate(ids);
|
|
82
82
|
} else {
|
|
83
83
|
return res.status(400).json({ error: "Provide ids, all, or before" });
|
|
84
84
|
}
|
|
85
|
-
res.json({ ok: true, unreadCount: getUnreadNotificationCount() });
|
|
85
|
+
res.json({ ok: true, unreadCount: await getUnreadNotificationCount() });
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
router.delete("/old", (_req, res) => {
|
|
89
|
-
purgeOldNotifications(90);
|
|
88
|
+
router.delete("/old", async (_req, res) => {
|
|
89
|
+
await purgeOldNotifications(90);
|
|
90
90
|
res.json({ ok: true });
|
|
91
91
|
});
|
|
92
92
|
|