claudeck 1.3.0 → 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 +27 -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 +16 -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 +153 -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
|
}
|
|
@@ -158,6 +158,7 @@ export async function runOrchestrator({
|
|
|
158
158
|
abortController,
|
|
159
159
|
maxTurns: 3, // Planner should just think, not use many tools
|
|
160
160
|
executable: execPath,
|
|
161
|
+
settingSources: ["user", "project", "local"],
|
|
161
162
|
};
|
|
162
163
|
|
|
163
164
|
if (!useBypass && !usePlan) {
|
|
@@ -185,7 +186,7 @@ export async function runOrchestrator({
|
|
|
185
186
|
orchSend({ type: "orchestrator_phase", phase: "planning" });
|
|
186
187
|
|
|
187
188
|
try {
|
|
188
|
-
const prompt = buildOrchestratorPromptWithMemory(task, agents, cwd);
|
|
189
|
+
const prompt = await buildOrchestratorPromptWithMemory(task, agents, cwd);
|
|
189
190
|
const q = query({ prompt, options: plannerOpts });
|
|
190
191
|
|
|
191
192
|
for await (const sdkMsg of q) {
|
|
@@ -197,20 +198,20 @@ export async function runOrchestrator({
|
|
|
197
198
|
resolvedSid = ourSid;
|
|
198
199
|
sessionIds.set(ourSid, claudeSessionId);
|
|
199
200
|
|
|
200
|
-
if (!getSession(ourSid)) {
|
|
201
|
-
createSession(
|
|
201
|
+
if (!await getSession(ourSid)) {
|
|
202
|
+
await createSession(
|
|
202
203
|
ourSid,
|
|
203
204
|
claudeSessionId,
|
|
204
205
|
projectName || "Orchestrator",
|
|
205
206
|
cwd || "",
|
|
206
207
|
);
|
|
207
|
-
updateSessionTitle(ourSid, `Orchestrator: ${task.slice(0, 60)}`);
|
|
208
|
+
await updateSessionTitle(ourSid, `Orchestrator: ${task.slice(0, 60)}`);
|
|
208
209
|
} else {
|
|
209
|
-
updateClaudeSessionId(ourSid, claudeSessionId);
|
|
210
|
+
await updateClaudeSessionId(ourSid, claudeSessionId);
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
orchSend({ type: "session", sessionId: ourSid });
|
|
213
|
-
addMessage(
|
|
214
|
+
await addMessage(
|
|
214
215
|
resolvedSid,
|
|
215
216
|
"user",
|
|
216
217
|
JSON.stringify({ text: `[Orchestrator] ${task}` }),
|
|
@@ -225,7 +226,7 @@ export async function runOrchestrator({
|
|
|
225
226
|
plannerText += block.text;
|
|
226
227
|
orchSend({ type: "text", text: block.text });
|
|
227
228
|
if (resolvedSid) {
|
|
228
|
-
addMessage(
|
|
229
|
+
await addMessage(
|
|
229
230
|
resolvedSid,
|
|
230
231
|
"assistant",
|
|
231
232
|
JSON.stringify({ text: block.text }),
|
|
@@ -251,7 +252,7 @@ export async function runOrchestrator({
|
|
|
251
252
|
Object.keys(sdkMsg.modelUsage || {})[0] || null;
|
|
252
253
|
|
|
253
254
|
if (resolvedSid) {
|
|
254
|
-
addCost(
|
|
255
|
+
await addCost(
|
|
255
256
|
resolvedSid,
|
|
256
257
|
costUsd,
|
|
257
258
|
durationMs,
|
|
@@ -273,7 +274,7 @@ export async function runOrchestrator({
|
|
|
273
274
|
duration_ms: durationMs,
|
|
274
275
|
num_turns: numTurns,
|
|
275
276
|
cost_usd: costUsd,
|
|
276
|
-
totalCost: getTotalCost(),
|
|
277
|
+
totalCost: await getTotalCost(),
|
|
277
278
|
input_tokens: inputTokens,
|
|
278
279
|
output_tokens: outputTokens,
|
|
279
280
|
model: resultModel,
|
|
@@ -374,7 +375,7 @@ export async function runOrchestrator({
|
|
|
374
375
|
if (result?.claudeSessionId) chainResumeId = result.claudeSessionId;
|
|
375
376
|
|
|
376
377
|
// Read context that the agent stored
|
|
377
|
-
const ctx = getAllAgentContext(runId).find(
|
|
378
|
+
const ctx = (await getAllAgentContext(runId)).find(
|
|
378
379
|
(c) => c.agent_id === agentDef.id,
|
|
379
380
|
);
|
|
380
381
|
|
|
@@ -438,7 +439,7 @@ export async function runOrchestrator({
|
|
|
438
439
|
if (block.type === "text" && block.text) {
|
|
439
440
|
orchSend({ type: "text", text: block.text });
|
|
440
441
|
if (resolvedSid) {
|
|
441
|
-
addMessage(
|
|
442
|
+
await addMessage(
|
|
442
443
|
resolvedSid,
|
|
443
444
|
"assistant",
|
|
444
445
|
JSON.stringify({ text: block.text }),
|
|
@@ -464,7 +465,7 @@ export async function runOrchestrator({
|
|
|
464
465
|
Object.keys(sdkMsg.modelUsage || {})[0] || null;
|
|
465
466
|
|
|
466
467
|
if (resolvedSid) {
|
|
467
|
-
addCost(
|
|
468
|
+
await addCost(
|
|
468
469
|
resolvedSid,
|
|
469
470
|
costUsd,
|
|
470
471
|
durationMs,
|
|
@@ -486,7 +487,7 @@ export async function runOrchestrator({
|
|
|
486
487
|
duration_ms: durationMs,
|
|
487
488
|
num_turns: numTurns,
|
|
488
489
|
cost_usd: costUsd,
|
|
489
|
-
totalCost: getTotalCost(),
|
|
490
|
+
totalCost: await getTotalCost(),
|
|
490
491
|
input_tokens: inputTokens,
|
|
491
492
|
output_tokens: outputTokens,
|
|
492
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
|
|