@geekbeer/minion 2.68.6 → 2.73.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/core/api.js +28 -29
- package/core/db.js +297 -0
- package/core/lib/thread-watcher.js +24 -15
- package/core/routes/daily-logs.js +17 -11
- package/core/routes/memory.js +16 -10
- package/core/routes/{help-threads.js → threads.js} +30 -30
- package/core/stores/chat-store.js +138 -76
- package/core/stores/daily-log-store.js +84 -105
- package/core/stores/execution-store.js +59 -84
- package/core/stores/memory-store.js +84 -177
- package/core/stores/routine-store.js +55 -72
- package/core/stores/workflow-store.js +52 -69
- package/docs/api-reference.md +34 -31
- package/linux/routes/chat.js +54 -7
- package/linux/server.js +5 -3
- package/package.json +2 -1
- package/roles/engineer.md +12 -0
- package/roles/pm.md +16 -0
- package/rules/core.md +66 -0
- package/win/routes/chat.js +54 -7
- package/win/server.js +4 -2
package/core/routes/memory.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory management endpoints
|
|
3
3
|
*
|
|
4
|
-
* Provides CRUD for the minion's long-term memory entries.
|
|
5
|
-
* Memory is stored
|
|
4
|
+
* Provides CRUD + search for the minion's long-term memory entries.
|
|
5
|
+
* Memory is stored in SQLite with FTS5 full-text search.
|
|
6
6
|
*
|
|
7
7
|
* Endpoints:
|
|
8
|
-
* GET /api/memory - List all
|
|
8
|
+
* GET /api/memory - List all entries (or search with ?search=keyword)
|
|
9
9
|
* GET /api/memory/:id - Get a single entry
|
|
10
10
|
* POST /api/memory - Create a new entry
|
|
11
11
|
* PUT /api/memory/:id - Update an entry
|
|
@@ -21,14 +21,20 @@ const memoryStore = require('../stores/memory-store')
|
|
|
21
21
|
*/
|
|
22
22
|
async function memoryRoutes(fastify) {
|
|
23
23
|
|
|
24
|
-
// GET /api/memory - List
|
|
24
|
+
// GET /api/memory - List or search entries
|
|
25
25
|
fastify.get('/api/memory', async (request, reply) => {
|
|
26
26
|
if (!verifyToken(request)) {
|
|
27
27
|
reply.code(401)
|
|
28
28
|
return { success: false, error: 'Unauthorized' }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const { search } = request.query || {}
|
|
32
|
+
if (search) {
|
|
33
|
+
const entries = memoryStore.searchEntries(search)
|
|
34
|
+
return { success: true, entries }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const entries = memoryStore.listEntries()
|
|
32
38
|
return { success: true, entries }
|
|
33
39
|
})
|
|
34
40
|
|
|
@@ -39,7 +45,7 @@ async function memoryRoutes(fastify) {
|
|
|
39
45
|
return { success: false, error: 'Unauthorized' }
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
const entry =
|
|
48
|
+
const entry = memoryStore.loadEntry(request.params.id)
|
|
43
49
|
if (!entry) {
|
|
44
50
|
reply.code(404)
|
|
45
51
|
return { success: false, error: 'Entry not found' }
|
|
@@ -61,7 +67,7 @@ async function memoryRoutes(fastify) {
|
|
|
61
67
|
return { success: false, error: 'title and content are required' }
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
const entry =
|
|
70
|
+
const entry = memoryStore.saveEntry({ title, category, content })
|
|
65
71
|
console.log(`[Memory] Created entry: ${entry.id} (${entry.title})`)
|
|
66
72
|
return { success: true, entry }
|
|
67
73
|
})
|
|
@@ -73,14 +79,14 @@ async function memoryRoutes(fastify) {
|
|
|
73
79
|
return { success: false, error: 'Unauthorized' }
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
const existing =
|
|
82
|
+
const existing = memoryStore.loadEntry(request.params.id)
|
|
77
83
|
if (!existing) {
|
|
78
84
|
reply.code(404)
|
|
79
85
|
return { success: false, error: 'Entry not found' }
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
const { title, category, content } = request.body || {}
|
|
83
|
-
const entry =
|
|
89
|
+
const entry = memoryStore.saveEntry({
|
|
84
90
|
id: request.params.id,
|
|
85
91
|
title: title || existing.title,
|
|
86
92
|
category: category || existing.category,
|
|
@@ -98,7 +104,7 @@ async function memoryRoutes(fastify) {
|
|
|
98
104
|
return { success: false, error: 'Unauthorized' }
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
const deleted =
|
|
107
|
+
const deleted = memoryStore.deleteEntry(request.params.id)
|
|
102
108
|
if (!deleted) {
|
|
103
109
|
reply.code(404)
|
|
104
110
|
return { success: false, error: 'Entry not found' }
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Project thread routes (local API on minion)
|
|
3
3
|
*
|
|
4
4
|
* Endpoints:
|
|
5
|
-
* - GET /api/
|
|
6
|
-
* - POST /api/
|
|
7
|
-
* - GET /api/
|
|
8
|
-
* - POST /api/
|
|
9
|
-
* - POST /api/
|
|
10
|
-
* - POST /api/
|
|
11
|
-
* - DELETE /api/
|
|
5
|
+
* - GET /api/threads - List open threads in minion's projects
|
|
6
|
+
* - POST /api/threads - Create a new thread (proxied to HQ)
|
|
7
|
+
* - GET /api/threads/:id - Get thread detail with messages
|
|
8
|
+
* - POST /api/threads/:id/messages - Post a message to a thread
|
|
9
|
+
* - POST /api/threads/:id/resolve - Resolve a thread
|
|
10
|
+
* - POST /api/threads/:id/cancel - Cancel a thread
|
|
11
|
+
* - DELETE /api/threads/:id - Permanently delete a thread (PM only)
|
|
12
12
|
* - GET /api/project-memories - Search project memories
|
|
13
13
|
* - POST /api/project-memories - Create a project memory
|
|
14
14
|
*/
|
|
@@ -19,77 +19,77 @@ const api = require('../api')
|
|
|
19
19
|
/**
|
|
20
20
|
* @param {import('fastify').FastifyInstance} fastify
|
|
21
21
|
*/
|
|
22
|
-
async function
|
|
22
|
+
async function threadRoutes(fastify) {
|
|
23
23
|
// List open threads
|
|
24
|
-
fastify.get('/api/
|
|
24
|
+
fastify.get('/api/threads', async (request, reply) => {
|
|
25
25
|
if (!verifyToken(request)) {
|
|
26
26
|
reply.code(401)
|
|
27
27
|
return { success: false, error: 'Unauthorized' }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
|
-
const data = await api.
|
|
31
|
+
const data = await api.getOpenThreads()
|
|
32
32
|
return { success: true, threads: data.threads || [] }
|
|
33
33
|
} catch (error) {
|
|
34
|
-
console.error(`[
|
|
34
|
+
console.error(`[Threads] List error: ${error.message}`)
|
|
35
35
|
reply.code(500)
|
|
36
36
|
return { success: false, error: error.message }
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
39
|
|
|
40
40
|
// Create thread
|
|
41
|
-
fastify.post('/api/
|
|
41
|
+
fastify.post('/api/threads', async (request, reply) => {
|
|
42
42
|
if (!verifyToken(request)) {
|
|
43
43
|
reply.code(401)
|
|
44
44
|
return { success: false, error: 'Unauthorized' }
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
try {
|
|
48
|
-
const data = await api.
|
|
48
|
+
const data = await api.createThread(request.body)
|
|
49
49
|
return { success: true, thread: data.thread }
|
|
50
50
|
} catch (error) {
|
|
51
|
-
console.error(`[
|
|
51
|
+
console.error(`[Threads] Create error: ${error.message}`)
|
|
52
52
|
reply.code(error.statusCode || 500)
|
|
53
53
|
return { success: false, error: error.message }
|
|
54
54
|
}
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
// Get thread detail
|
|
58
|
-
fastify.get('/api/
|
|
58
|
+
fastify.get('/api/threads/:id', async (request, reply) => {
|
|
59
59
|
if (!verifyToken(request)) {
|
|
60
60
|
reply.code(401)
|
|
61
61
|
return { success: false, error: 'Unauthorized' }
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
|
-
const data = await api.
|
|
65
|
+
const data = await api.getThread(request.params.id)
|
|
66
66
|
return { success: true, thread: data.thread, messages: data.messages }
|
|
67
67
|
} catch (error) {
|
|
68
|
-
console.error(`[
|
|
68
|
+
console.error(`[Threads] Get error: ${error.message}`)
|
|
69
69
|
reply.code(error.statusCode || 500)
|
|
70
70
|
return { success: false, error: error.message }
|
|
71
71
|
}
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
// Post message
|
|
75
|
-
fastify.post('/api/
|
|
75
|
+
fastify.post('/api/threads/:id/messages', async (request, reply) => {
|
|
76
76
|
if (!verifyToken(request)) {
|
|
77
77
|
reply.code(401)
|
|
78
78
|
return { success: false, error: 'Unauthorized' }
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
try {
|
|
82
|
-
const data = await api.
|
|
82
|
+
const data = await api.postThreadMessage(request.params.id, request.body)
|
|
83
83
|
return { success: true, message: data.message }
|
|
84
84
|
} catch (error) {
|
|
85
|
-
console.error(`[
|
|
85
|
+
console.error(`[Threads] Message error: ${error.message}`)
|
|
86
86
|
reply.code(error.statusCode || 500)
|
|
87
87
|
return { success: false, error: error.message }
|
|
88
88
|
}
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
// Resolve thread
|
|
92
|
-
fastify.post('/api/
|
|
92
|
+
fastify.post('/api/threads/:id/resolve', async (request, reply) => {
|
|
93
93
|
if (!verifyToken(request)) {
|
|
94
94
|
reply.code(401)
|
|
95
95
|
return { success: false, error: 'Unauthorized' }
|
|
@@ -97,34 +97,34 @@ async function helpThreadRoutes(fastify) {
|
|
|
97
97
|
|
|
98
98
|
try {
|
|
99
99
|
const { resolution } = request.body || {}
|
|
100
|
-
const data = await api.
|
|
100
|
+
const data = await api.resolveThread(request.params.id, resolution)
|
|
101
101
|
return { success: true, thread: data.thread }
|
|
102
102
|
} catch (error) {
|
|
103
|
-
console.error(`[
|
|
103
|
+
console.error(`[Threads] Resolve error: ${error.message}`)
|
|
104
104
|
reply.code(error.statusCode || 500)
|
|
105
105
|
return { success: false, error: error.message }
|
|
106
106
|
}
|
|
107
107
|
})
|
|
108
108
|
|
|
109
109
|
// Permanently delete thread (PM only)
|
|
110
|
-
fastify.delete('/api/
|
|
110
|
+
fastify.delete('/api/threads/:id', async (request, reply) => {
|
|
111
111
|
if (!verifyToken(request)) {
|
|
112
112
|
reply.code(401)
|
|
113
113
|
return { success: false, error: 'Unauthorized' }
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
|
-
const data = await api.
|
|
117
|
+
const data = await api.deleteThread(request.params.id)
|
|
118
118
|
return { success: true, deleted: data.deleted }
|
|
119
119
|
} catch (error) {
|
|
120
|
-
console.error(`[
|
|
120
|
+
console.error(`[Threads] Delete error: ${error.message}`)
|
|
121
121
|
reply.code(error.statusCode || 500)
|
|
122
122
|
return { success: false, error: error.message }
|
|
123
123
|
}
|
|
124
124
|
})
|
|
125
125
|
|
|
126
126
|
// Cancel thread
|
|
127
|
-
fastify.post('/api/
|
|
127
|
+
fastify.post('/api/threads/:id/cancel', async (request, reply) => {
|
|
128
128
|
if (!verifyToken(request)) {
|
|
129
129
|
reply.code(401)
|
|
130
130
|
return { success: false, error: 'Unauthorized' }
|
|
@@ -132,10 +132,10 @@ async function helpThreadRoutes(fastify) {
|
|
|
132
132
|
|
|
133
133
|
try {
|
|
134
134
|
const { reason } = request.body || {}
|
|
135
|
-
const data = await api.
|
|
135
|
+
const data = await api.cancelThread(request.params.id, reason)
|
|
136
136
|
return { success: true, thread: data.thread }
|
|
137
137
|
} catch (error) {
|
|
138
|
-
console.error(`[
|
|
138
|
+
console.error(`[Threads] Cancel error: ${error.message}`)
|
|
139
139
|
reply.code(error.statusCode || 500)
|
|
140
140
|
return { success: false, error: error.message }
|
|
141
141
|
}
|
|
@@ -186,4 +186,4 @@ async function helpThreadRoutes(fastify) {
|
|
|
186
186
|
})
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
module.exports = {
|
|
189
|
+
module.exports = { threadRoutes }
|
|
@@ -1,113 +1,175 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Chat Session Store
|
|
3
|
-
* Persists
|
|
4
|
-
*
|
|
2
|
+
* Chat Session Store (SQLite)
|
|
3
|
+
* Persists chat sessions and messages.
|
|
4
|
+
* Supports multiple sessions: one active + archived past sessions.
|
|
5
|
+
* Claude CLI manages conversation context via --resume.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
const path = require('path')
|
|
9
|
-
|
|
10
|
-
const { config } = require('../config')
|
|
11
|
-
const { DATA_DIR } = require('../lib/platform')
|
|
8
|
+
const { getDb } = require('../db')
|
|
12
9
|
|
|
13
10
|
const MAX_MESSAGES = 100
|
|
14
11
|
|
|
15
12
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Load the active (most recent) chat session
|
|
14
|
+
* @returns {object|null} Session object or null if none exists
|
|
18
15
|
*/
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
function load() {
|
|
17
|
+
const db = getDb()
|
|
18
|
+
const session = db.prepare(
|
|
19
|
+
'SELECT * FROM chat_sessions ORDER BY updated_at DESC LIMIT 1'
|
|
20
|
+
).get()
|
|
21
|
+
if (!session) return null
|
|
22
|
+
|
|
23
|
+
const messages = db.prepare(
|
|
24
|
+
'SELECT role, content, timestamp FROM chat_messages WHERE session_id = ? ORDER BY id'
|
|
25
|
+
).all(session.session_id)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
session_id: session.session_id,
|
|
29
|
+
messages,
|
|
30
|
+
turn_count: session.turn_count,
|
|
31
|
+
created_at: session.created_at,
|
|
32
|
+
updated_at: session.updated_at,
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
const SESSION_FILE = getFilePath()
|
|
29
|
-
|
|
30
36
|
/**
|
|
31
|
-
* Load
|
|
32
|
-
* @
|
|
37
|
+
* Load a specific session by ID
|
|
38
|
+
* @param {string} sessionId
|
|
39
|
+
* @returns {object|null}
|
|
33
40
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
function loadById(sessionId) {
|
|
42
|
+
const db = getDb()
|
|
43
|
+
const session = db.prepare('SELECT * FROM chat_sessions WHERE session_id = ?').get(sessionId)
|
|
44
|
+
if (!session) return null
|
|
45
|
+
|
|
46
|
+
const messages = db.prepare(
|
|
47
|
+
'SELECT role, content, timestamp FROM chat_messages WHERE session_id = ? ORDER BY id'
|
|
48
|
+
).all(session.session_id)
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
session_id: session.session_id,
|
|
52
|
+
messages,
|
|
53
|
+
turn_count: session.turn_count,
|
|
54
|
+
created_at: session.created_at,
|
|
55
|
+
updated_at: session.updated_at,
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
/**
|
|
48
|
-
*
|
|
60
|
+
* List all sessions (metadata only, no messages)
|
|
61
|
+
* @param {number} limit
|
|
62
|
+
* @returns {Array<{ session_id, turn_count, message_count, created_at, updated_at }>}
|
|
63
|
+
*/
|
|
64
|
+
function listSessions(limit = 50) {
|
|
65
|
+
const db = getDb()
|
|
66
|
+
return db.prepare(`
|
|
67
|
+
SELECT
|
|
68
|
+
s.session_id,
|
|
69
|
+
s.turn_count,
|
|
70
|
+
s.created_at,
|
|
71
|
+
s.updated_at,
|
|
72
|
+
(SELECT COUNT(*) FROM chat_messages WHERE session_id = s.session_id) as message_count
|
|
73
|
+
FROM chat_sessions s
|
|
74
|
+
ORDER BY s.updated_at DESC
|
|
75
|
+
LIMIT ?
|
|
76
|
+
`).all(limit)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Save the active chat session (full replace of this session only)
|
|
49
81
|
* @param {object} session - Session object
|
|
50
82
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
83
|
+
function save(session) {
|
|
84
|
+
const db = getDb()
|
|
85
|
+
const tx = db.transaction(() => {
|
|
86
|
+
// Remove only this session (preserve others)
|
|
87
|
+
db.prepare('DELETE FROM chat_sessions WHERE session_id = ?').run(session.session_id)
|
|
88
|
+
|
|
89
|
+
// Insert session
|
|
90
|
+
db.prepare(
|
|
91
|
+
'INSERT INTO chat_sessions (session_id, turn_count, created_at, updated_at) VALUES (?, ?, ?, ?)'
|
|
92
|
+
).run(session.session_id, session.turn_count || 0, session.created_at, session.updated_at)
|
|
93
|
+
|
|
94
|
+
// Insert messages
|
|
95
|
+
const insertMsg = db.prepare(
|
|
96
|
+
'INSERT INTO chat_messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)'
|
|
97
|
+
)
|
|
98
|
+
for (const msg of (session.messages || [])) {
|
|
99
|
+
insertMsg.run(session.session_id, msg.role, msg.content, msg.timestamp)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
tx()
|
|
57
103
|
}
|
|
58
104
|
|
|
59
105
|
/**
|
|
60
|
-
* Add a message to the active session
|
|
61
|
-
* Creates a new session if none exists
|
|
106
|
+
* Add a message to the active session.
|
|
107
|
+
* Creates a new session if none exists or session_id changed.
|
|
108
|
+
* Past sessions are preserved (not deleted).
|
|
62
109
|
* @param {string} sessionId - Claude CLI session ID
|
|
63
110
|
* @param {{ role: string, content: string }} msg - Message to add
|
|
64
|
-
* @param {number} [turnCount] - Optional turn count to
|
|
111
|
+
* @param {number} [turnCount] - Optional turn count to add
|
|
65
112
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
turn_count
|
|
75
|
-
|
|
76
|
-
updated_at: Date.now(),
|
|
77
|
-
}
|
|
113
|
+
function addMessage(sessionId, msg, turnCount) {
|
|
114
|
+
const db = getDb()
|
|
115
|
+
const existing = db.prepare('SELECT * FROM chat_sessions WHERE session_id = ?').get(sessionId)
|
|
116
|
+
|
|
117
|
+
// If session doesn't exist, create a new one (past sessions remain in DB)
|
|
118
|
+
if (!existing) {
|
|
119
|
+
const now = Date.now()
|
|
120
|
+
db.prepare(
|
|
121
|
+
'INSERT INTO chat_sessions (session_id, turn_count, created_at, updated_at) VALUES (?, ?, ?, ?)'
|
|
122
|
+
).run(sessionId, 0, now, now)
|
|
78
123
|
}
|
|
79
124
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
})
|
|
85
|
-
session.updated_at = Date.now()
|
|
125
|
+
// Insert message
|
|
126
|
+
db.prepare(
|
|
127
|
+
'INSERT INTO chat_messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)'
|
|
128
|
+
).run(sessionId, msg.role, msg.content, Date.now())
|
|
86
129
|
|
|
87
|
-
// Update
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
130
|
+
// Update session metadata
|
|
131
|
+
const newTurnCount = (turnCount && turnCount > 0) ? turnCount : 0
|
|
132
|
+
db.prepare(`
|
|
133
|
+
UPDATE chat_sessions
|
|
134
|
+
SET updated_at = ?,
|
|
135
|
+
turn_count = turn_count + ?
|
|
136
|
+
WHERE session_id = ?
|
|
137
|
+
`).run(Date.now(), newTurnCount, sessionId)
|
|
91
138
|
|
|
92
|
-
// Prune old messages
|
|
93
|
-
|
|
94
|
-
|
|
139
|
+
// Prune old messages for this session (keep only MAX_MESSAGES most recent)
|
|
140
|
+
const count = db.prepare('SELECT COUNT(*) as cnt FROM chat_messages WHERE session_id = ?').get(sessionId).cnt
|
|
141
|
+
if (count > MAX_MESSAGES) {
|
|
142
|
+
db.prepare(`
|
|
143
|
+
DELETE FROM chat_messages WHERE id IN (
|
|
144
|
+
SELECT id FROM chat_messages WHERE session_id = ?
|
|
145
|
+
ORDER BY id ASC LIMIT ?
|
|
146
|
+
)
|
|
147
|
+
`).run(sessionId, count - MAX_MESSAGES)
|
|
95
148
|
}
|
|
96
|
-
|
|
97
|
-
await save(session)
|
|
98
149
|
}
|
|
99
150
|
|
|
100
151
|
/**
|
|
101
|
-
* Clear the active session
|
|
152
|
+
* Clear the active session (remove from DB).
|
|
153
|
+
* This deletes only the most recent session.
|
|
154
|
+
* For reset flows, use clearActive() which preserves the session in history.
|
|
102
155
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.error(`[ChatStore] Failed to clear session: ${err.message}`)
|
|
109
|
-
}
|
|
156
|
+
function clear() {
|
|
157
|
+
const db = getDb()
|
|
158
|
+
const active = db.prepare('SELECT session_id FROM chat_sessions ORDER BY updated_at DESC LIMIT 1').get()
|
|
159
|
+
if (active) {
|
|
160
|
+
db.prepare('DELETE FROM chat_sessions WHERE session_id = ?').run(active.session_id)
|
|
110
161
|
}
|
|
111
162
|
}
|
|
112
163
|
|
|
113
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Delete a specific session by ID
|
|
166
|
+
* @param {string} sessionId
|
|
167
|
+
* @returns {boolean}
|
|
168
|
+
*/
|
|
169
|
+
function deleteSession(sessionId) {
|
|
170
|
+
const db = getDb()
|
|
171
|
+
const result = db.prepare('DELETE FROM chat_sessions WHERE session_id = ?').run(sessionId)
|
|
172
|
+
return result.changes > 0
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { load, loadById, listSessions, save, addMessage, clear, deleteSession }
|