@vox-ai-app/storage 1.0.2 → 1.0.3
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 +109 -0
- package/package.json +10 -4
- package/src/db.js +6 -107
- package/src/index.js +9 -3
- package/src/migrations/001_initial_schema.js +217 -0
- package/src/migrations/002_task_activity_types.js +45 -0
- package/src/migrations/runner.js +31 -0
- package/src/repos/mcp-servers.js +81 -0
- package/src/repos/messages.js +219 -0
- package/src/repos/patterns.js +54 -0
- package/src/repos/schedules.js +58 -0
- package/src/repos/settings.js +38 -0
- package/src/repos/tasks.js +195 -0
- package/src/repos/tool-secrets.js +26 -0
- package/src/repos/tools.js +104 -0
- package/src/repos/vectors.js +105 -0
- package/src/config.js +0 -62
- package/src/messages.js +0 -213
- package/src/tasks.js +0 -219
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export function vectorUpsert(db, collection, id, embedding, metadata = {}) {
|
|
2
|
+
const buffer = Buffer.from(new Float32Array(embedding).buffer)
|
|
3
|
+
const now = new Date().toISOString()
|
|
4
|
+
|
|
5
|
+
db.prepare(
|
|
6
|
+
`INSERT INTO vectors (id, collection, embedding, metadata, created_at, updated_at)
|
|
7
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
8
|
+
ON CONFLICT (id, collection)
|
|
9
|
+
DO UPDATE SET embedding = excluded.embedding, metadata = excluded.metadata, updated_at = excluded.updated_at`
|
|
10
|
+
).run(id, collection, buffer, JSON.stringify(metadata), now, now)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function vectorSearch(db, collection, queryEmbedding, queryText, topK = 5) {
|
|
14
|
+
const rows = db
|
|
15
|
+
.prepare(`SELECT id, embedding, metadata FROM vectors WHERE collection = ?`)
|
|
16
|
+
.all(collection)
|
|
17
|
+
|
|
18
|
+
if (rows.length === 0) return []
|
|
19
|
+
|
|
20
|
+
const candidates = rows.map((row) => {
|
|
21
|
+
const stored = new Float32Array(
|
|
22
|
+
row.embedding.buffer,
|
|
23
|
+
row.embedding.byteOffset,
|
|
24
|
+
row.embedding.byteLength / 4
|
|
25
|
+
)
|
|
26
|
+
const meta = JSON.parse(row.metadata)
|
|
27
|
+
return {
|
|
28
|
+
id: row.id,
|
|
29
|
+
metadata: meta,
|
|
30
|
+
score: cosineSimilarity(queryEmbedding, stored)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const candidateLimit = Math.max(topK * 5, topK)
|
|
35
|
+
candidates.sort((a, b) => b.score - a.score)
|
|
36
|
+
const topCandidates = candidates.slice(0, candidateLimit)
|
|
37
|
+
|
|
38
|
+
if (!queryText) return topCandidates.slice(0, topK)
|
|
39
|
+
return rerankRows(topCandidates, queryText, topK)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function vectorRemove(db, collection, id) {
|
|
43
|
+
db.prepare(`DELETE FROM vectors WHERE collection = ? AND id = ?`).run(collection, id)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function vectorCount(db, collection) {
|
|
47
|
+
const row = db.prepare(`SELECT COUNT(*) as cnt FROM vectors WHERE collection = ?`).get(collection)
|
|
48
|
+
return row?.cnt || 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cosineSimilarity(a, b) {
|
|
52
|
+
let dot = 0
|
|
53
|
+
let normA = 0
|
|
54
|
+
let normB = 0
|
|
55
|
+
for (let i = 0; i < a.length; i++) {
|
|
56
|
+
dot += a[i] * b[i]
|
|
57
|
+
normA += a[i] * a[i]
|
|
58
|
+
normB += b[i] * b[i]
|
|
59
|
+
}
|
|
60
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB)
|
|
61
|
+
return denom === 0 ? 0 : dot / denom
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function tokenize(value) {
|
|
65
|
+
if (typeof value !== 'string') return []
|
|
66
|
+
return value
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.split(/[^\p{L}\p{N}]+/gu)
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function lexicalScore(queryText, candidateText) {
|
|
73
|
+
const queryTokens = new Set(tokenize(queryText))
|
|
74
|
+
if (queryTokens.size === 0) return 0
|
|
75
|
+
const candidateTokens = new Set(tokenize(candidateText))
|
|
76
|
+
if (candidateTokens.size === 0) return 0
|
|
77
|
+
let overlap = 0
|
|
78
|
+
for (const t of queryTokens) {
|
|
79
|
+
if (candidateTokens.has(t)) overlap++
|
|
80
|
+
}
|
|
81
|
+
return overlap / Math.sqrt(queryTokens.size * candidateTokens.size)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function rerankRows(rows, queryText, topK) {
|
|
85
|
+
const VECTOR_WEIGHT = 0.6
|
|
86
|
+
const LEXICAL_WEIGHT = 0.3
|
|
87
|
+
const PHRASE_BOOST = 0.1
|
|
88
|
+
const normalizedQuery = String(queryText || '')
|
|
89
|
+
.toLowerCase()
|
|
90
|
+
.trim()
|
|
91
|
+
|
|
92
|
+
return rows
|
|
93
|
+
.map((row) => {
|
|
94
|
+
const text = row.metadata?.text || row.metadata?.instructions || ''
|
|
95
|
+
const lexical = lexicalScore(queryText, text)
|
|
96
|
+
const vector = Math.max(0, row.score || 0)
|
|
97
|
+
const phraseMatch = normalizedQuery.length > 0 && text.toLowerCase().includes(normalizedQuery)
|
|
98
|
+
return {
|
|
99
|
+
...row,
|
|
100
|
+
score: VECTOR_WEIGHT * vector + LEXICAL_WEIGHT * lexical + (phraseMatch ? PHRASE_BOOST : 0)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
.sort((a, b) => b.score - a.score)
|
|
104
|
+
.slice(0, topK)
|
|
105
|
+
}
|
package/src/config.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
function resolveConfigPath(configPath) {
|
|
5
|
-
const normalized = String(configPath || '').trim()
|
|
6
|
-
if (!normalized) {
|
|
7
|
-
throw new Error('A config path is required.')
|
|
8
|
-
}
|
|
9
|
-
return path.resolve(normalized)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function readConfigFile(configPath) {
|
|
13
|
-
const resolvedPath = resolveConfigPath(configPath)
|
|
14
|
-
if (!existsSync(resolvedPath)) {
|
|
15
|
-
return {}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const raw = readFileSync(resolvedPath, 'utf8')
|
|
20
|
-
if (!raw.trim()) return {}
|
|
21
|
-
const parsed = JSON.parse(raw)
|
|
22
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
23
|
-
return {}
|
|
24
|
-
}
|
|
25
|
-
return parsed
|
|
26
|
-
} catch {
|
|
27
|
-
return {}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function writeConfigFile(configPath, value) {
|
|
32
|
-
const resolvedPath = resolveConfigPath(configPath)
|
|
33
|
-
mkdirSync(path.dirname(resolvedPath), { recursive: true })
|
|
34
|
-
const tempPath = `${resolvedPath}.tmp`
|
|
35
|
-
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
|
|
36
|
-
renameSync(tempPath, resolvedPath)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function configGet(configPath, key) {
|
|
40
|
-
const config = readConfigFile(configPath)
|
|
41
|
-
return config[String(key)]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function configSet(configPath, key, value) {
|
|
45
|
-
const config = readConfigFile(configPath)
|
|
46
|
-
config[String(key)] = value
|
|
47
|
-
writeConfigFile(configPath, config)
|
|
48
|
-
return value
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function configDelete(configPath, key) {
|
|
52
|
-
const config = readConfigFile(configPath)
|
|
53
|
-
const normalizedKey = String(key)
|
|
54
|
-
const existed = Object.prototype.hasOwnProperty.call(config, normalizedKey)
|
|
55
|
-
delete config[normalizedKey]
|
|
56
|
-
writeConfigFile(configPath, config)
|
|
57
|
-
return existed
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function configGetAll(configPath) {
|
|
61
|
-
return { ...readConfigFile(configPath) }
|
|
62
|
-
}
|
package/src/messages.js
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_CONVERSATION_ID = 'main'
|
|
2
|
-
|
|
3
|
-
function getConversationId(conversationId) {
|
|
4
|
-
const normalized = String(conversationId || '').trim()
|
|
5
|
-
return normalized || DEFAULT_CONVERSATION_ID
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function normalizeLimit(limit) {
|
|
9
|
-
const parsed = Number.parseInt(limit, 10)
|
|
10
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function normalizeBeforeId(beforeId) {
|
|
14
|
-
const parsed = Number.parseInt(beforeId, 10)
|
|
15
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function mapRow(row) {
|
|
19
|
-
if (!row) return null
|
|
20
|
-
return {
|
|
21
|
-
id: row.id,
|
|
22
|
-
conversationId: row.conversation_id,
|
|
23
|
-
role: row.role,
|
|
24
|
-
content: row.content,
|
|
25
|
-
createdAt: row.created_at
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function ensureConversation(db, conversationId = DEFAULT_CONVERSATION_ID) {
|
|
30
|
-
const id = getConversationId(conversationId)
|
|
31
|
-
const now = new Date().toISOString()
|
|
32
|
-
|
|
33
|
-
db.prepare(
|
|
34
|
-
`
|
|
35
|
-
INSERT INTO conversations (id, created_at, updated_at)
|
|
36
|
-
VALUES (?, ?, ?)
|
|
37
|
-
ON CONFLICT(id) DO NOTHING
|
|
38
|
-
`
|
|
39
|
-
).run(id, now, now)
|
|
40
|
-
|
|
41
|
-
return db
|
|
42
|
-
.prepare(
|
|
43
|
-
`
|
|
44
|
-
SELECT id, created_at, updated_at
|
|
45
|
-
FROM conversations
|
|
46
|
-
WHERE id = ?
|
|
47
|
-
`
|
|
48
|
-
)
|
|
49
|
-
.get(id)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function touchConversation(db, conversationId = DEFAULT_CONVERSATION_ID) {
|
|
53
|
-
const id = getConversationId(conversationId)
|
|
54
|
-
const now = new Date().toISOString()
|
|
55
|
-
|
|
56
|
-
db.prepare(
|
|
57
|
-
`
|
|
58
|
-
INSERT INTO conversations (id, created_at, updated_at)
|
|
59
|
-
VALUES (?, ?, ?)
|
|
60
|
-
ON CONFLICT(id) DO UPDATE SET updated_at = excluded.updated_at
|
|
61
|
-
`
|
|
62
|
-
).run(id, now, now)
|
|
63
|
-
|
|
64
|
-
return db
|
|
65
|
-
.prepare(
|
|
66
|
-
`
|
|
67
|
-
SELECT id, created_at, updated_at
|
|
68
|
-
FROM conversations
|
|
69
|
-
WHERE id = ?
|
|
70
|
-
`
|
|
71
|
-
)
|
|
72
|
-
.get(id)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function appendMessage(db, role, content, conversationId = DEFAULT_CONVERSATION_ID) {
|
|
76
|
-
const id = getConversationId(conversationId)
|
|
77
|
-
const now = new Date().toISOString()
|
|
78
|
-
const normalizedRole = String(role || '').trim() || 'user'
|
|
79
|
-
const normalizedContent = String(content ?? '')
|
|
80
|
-
|
|
81
|
-
touchConversation(db, id)
|
|
82
|
-
|
|
83
|
-
const result = db
|
|
84
|
-
.prepare(
|
|
85
|
-
`
|
|
86
|
-
INSERT INTO messages (conversation_id, role, content, created_at)
|
|
87
|
-
VALUES (?, ?, ?, ?)
|
|
88
|
-
`
|
|
89
|
-
)
|
|
90
|
-
.run(id, normalizedRole, normalizedContent, now)
|
|
91
|
-
|
|
92
|
-
return mapRow(
|
|
93
|
-
db
|
|
94
|
-
.prepare(
|
|
95
|
-
`
|
|
96
|
-
SELECT id, conversation_id, role, content, created_at
|
|
97
|
-
FROM messages
|
|
98
|
-
WHERE id = ?
|
|
99
|
-
`
|
|
100
|
-
)
|
|
101
|
-
.get(result.lastInsertRowid)
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function getMessages(db, conversationId = DEFAULT_CONVERSATION_ID, limit) {
|
|
106
|
-
const id = getConversationId(conversationId)
|
|
107
|
-
const normalizedLimit = normalizeLimit(limit)
|
|
108
|
-
|
|
109
|
-
if (!normalizedLimit) {
|
|
110
|
-
return db
|
|
111
|
-
.prepare(
|
|
112
|
-
`
|
|
113
|
-
SELECT id, conversation_id, role, content, created_at
|
|
114
|
-
FROM messages
|
|
115
|
-
WHERE conversation_id = ?
|
|
116
|
-
ORDER BY id ASC
|
|
117
|
-
`
|
|
118
|
-
)
|
|
119
|
-
.all(id)
|
|
120
|
-
.map(mapRow)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return db
|
|
124
|
-
.prepare(
|
|
125
|
-
`
|
|
126
|
-
SELECT id, conversation_id, role, content, created_at
|
|
127
|
-
FROM (
|
|
128
|
-
SELECT id, conversation_id, role, content, created_at
|
|
129
|
-
FROM messages
|
|
130
|
-
WHERE conversation_id = ?
|
|
131
|
-
ORDER BY id DESC
|
|
132
|
-
LIMIT ?
|
|
133
|
-
)
|
|
134
|
-
ORDER BY id ASC
|
|
135
|
-
`
|
|
136
|
-
)
|
|
137
|
-
.all(id, normalizedLimit)
|
|
138
|
-
.map(mapRow)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function getMessagesBeforeId(
|
|
142
|
-
db,
|
|
143
|
-
beforeId,
|
|
144
|
-
conversationId = DEFAULT_CONVERSATION_ID,
|
|
145
|
-
limit = 50
|
|
146
|
-
) {
|
|
147
|
-
const id = getConversationId(conversationId)
|
|
148
|
-
const normalizedBeforeId = normalizeBeforeId(beforeId)
|
|
149
|
-
const normalizedLimit = normalizeLimit(limit) || 50
|
|
150
|
-
|
|
151
|
-
if (!normalizedBeforeId) {
|
|
152
|
-
return []
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return db
|
|
156
|
-
.prepare(
|
|
157
|
-
`
|
|
158
|
-
SELECT id, conversation_id, role, content, created_at
|
|
159
|
-
FROM (
|
|
160
|
-
SELECT id, conversation_id, role, content, created_at
|
|
161
|
-
FROM messages
|
|
162
|
-
WHERE conversation_id = ? AND id < ?
|
|
163
|
-
ORDER BY id DESC
|
|
164
|
-
LIMIT ?
|
|
165
|
-
)
|
|
166
|
-
ORDER BY id ASC
|
|
167
|
-
`
|
|
168
|
-
)
|
|
169
|
-
.all(id, normalizedBeforeId, normalizedLimit)
|
|
170
|
-
.map(mapRow)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function clearMessages(db, conversationId = DEFAULT_CONVERSATION_ID) {
|
|
174
|
-
const id = getConversationId(conversationId)
|
|
175
|
-
ensureConversation(db, id)
|
|
176
|
-
return db
|
|
177
|
-
.prepare(
|
|
178
|
-
`
|
|
179
|
-
DELETE FROM messages
|
|
180
|
-
WHERE conversation_id = ?
|
|
181
|
-
`
|
|
182
|
-
)
|
|
183
|
-
.run(id)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function saveSummaryCheckpoint(
|
|
187
|
-
db,
|
|
188
|
-
summary,
|
|
189
|
-
checkpointId,
|
|
190
|
-
conversationId = DEFAULT_CONVERSATION_ID
|
|
191
|
-
) {
|
|
192
|
-
const id = getConversationId(conversationId)
|
|
193
|
-
ensureConversation(db, id)
|
|
194
|
-
db.prepare(
|
|
195
|
-
`UPDATE conversations SET context_summary = ?, context_checkpoint_id = ?, updated_at = ? WHERE id = ?`
|
|
196
|
-
).run(summary, checkpointId, new Date().toISOString(), id)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function loadSummaryCheckpoint(db, conversationId = DEFAULT_CONVERSATION_ID) {
|
|
200
|
-
const id = getConversationId(conversationId)
|
|
201
|
-
const row = db
|
|
202
|
-
.prepare(`SELECT context_summary, context_checkpoint_id FROM conversations WHERE id = ?`)
|
|
203
|
-
.get(id)
|
|
204
|
-
if (!row || !row.context_summary) return null
|
|
205
|
-
return { summary: row.context_summary, checkpointId: row.context_checkpoint_id }
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export function clearSummaryCheckpoint(db, conversationId = DEFAULT_CONVERSATION_ID) {
|
|
209
|
-
const id = getConversationId(conversationId)
|
|
210
|
-
db.prepare(
|
|
211
|
-
`UPDATE conversations SET context_summary = NULL, context_checkpoint_id = NULL, updated_at = ? WHERE id = ?`
|
|
212
|
-
).run(new Date().toISOString(), id)
|
|
213
|
-
}
|
package/src/tasks.js
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
function parseJson(value, fallback) {
|
|
2
|
-
if (value === null || value === undefined || value === '') {
|
|
3
|
-
return fallback
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
return JSON.parse(value)
|
|
8
|
-
} catch {
|
|
9
|
-
return fallback
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function stringifyJson(value, fallback = null) {
|
|
14
|
-
if (value === null || value === undefined) {
|
|
15
|
-
return fallback
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
return JSON.stringify(value)
|
|
20
|
-
} catch {
|
|
21
|
-
return fallback
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function mapTask(row) {
|
|
26
|
-
if (!row) return null
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
taskId: row.task_id,
|
|
30
|
-
instructions: row.instructions,
|
|
31
|
-
context: row.context,
|
|
32
|
-
status: row.status,
|
|
33
|
-
createdAt: row.created_at,
|
|
34
|
-
updatedAt: row.updated_at,
|
|
35
|
-
currentPlan: row.current_plan,
|
|
36
|
-
message: row.message,
|
|
37
|
-
result: row.result,
|
|
38
|
-
completedAt: row.completed_at,
|
|
39
|
-
failedAt: row.failed_at
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function mapActivity(row) {
|
|
44
|
-
if (!row) return null
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
id: row.id,
|
|
48
|
-
taskId: row.task_id,
|
|
49
|
-
type: row.type,
|
|
50
|
-
name: row.name || null,
|
|
51
|
-
rawResult: parseJson(row.raw_result, row.raw_result),
|
|
52
|
-
timestamp: row.timestamp,
|
|
53
|
-
data: parseJson(row.data, {})
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function upsertTask(db, task) {
|
|
58
|
-
const taskId = String(task?.taskId || '').trim()
|
|
59
|
-
if (!taskId) {
|
|
60
|
-
throw new Error('taskId is required.')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const createdAt = String(task?.createdAt || new Date().toISOString())
|
|
64
|
-
const updatedAt = String(task?.updatedAt || createdAt)
|
|
65
|
-
|
|
66
|
-
db.prepare(
|
|
67
|
-
`
|
|
68
|
-
INSERT INTO tasks (
|
|
69
|
-
task_id,
|
|
70
|
-
instructions,
|
|
71
|
-
context,
|
|
72
|
-
status,
|
|
73
|
-
created_at,
|
|
74
|
-
updated_at,
|
|
75
|
-
current_plan,
|
|
76
|
-
message,
|
|
77
|
-
result,
|
|
78
|
-
completed_at,
|
|
79
|
-
failed_at
|
|
80
|
-
)
|
|
81
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
82
|
-
ON CONFLICT(task_id) DO UPDATE SET
|
|
83
|
-
instructions = excluded.instructions,
|
|
84
|
-
context = excluded.context,
|
|
85
|
-
status = excluded.status,
|
|
86
|
-
updated_at = excluded.updated_at,
|
|
87
|
-
current_plan = excluded.current_plan,
|
|
88
|
-
message = excluded.message,
|
|
89
|
-
result = excluded.result,
|
|
90
|
-
completed_at = excluded.completed_at,
|
|
91
|
-
failed_at = excluded.failed_at
|
|
92
|
-
`
|
|
93
|
-
).run(
|
|
94
|
-
taskId,
|
|
95
|
-
String(task?.instructions || ''),
|
|
96
|
-
String(task?.context || ''),
|
|
97
|
-
String(task?.status || 'queued'),
|
|
98
|
-
createdAt,
|
|
99
|
-
updatedAt,
|
|
100
|
-
String(task?.currentPlan || ''),
|
|
101
|
-
String(task?.message || ''),
|
|
102
|
-
task?.result === null || task?.result === undefined ? null : String(task.result),
|
|
103
|
-
String(task?.completedAt || ''),
|
|
104
|
-
String(task?.failedAt || '')
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
return getTask(db, taskId)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function getTask(db, taskId) {
|
|
111
|
-
return mapTask(
|
|
112
|
-
db
|
|
113
|
-
.prepare(
|
|
114
|
-
`
|
|
115
|
-
SELECT
|
|
116
|
-
task_id,
|
|
117
|
-
instructions,
|
|
118
|
-
context,
|
|
119
|
-
status,
|
|
120
|
-
created_at,
|
|
121
|
-
updated_at,
|
|
122
|
-
current_plan,
|
|
123
|
-
message,
|
|
124
|
-
result,
|
|
125
|
-
completed_at,
|
|
126
|
-
failed_at
|
|
127
|
-
FROM tasks
|
|
128
|
-
WHERE task_id = ?
|
|
129
|
-
`
|
|
130
|
-
)
|
|
131
|
-
.get(String(taskId || '').trim())
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function loadTasks(db) {
|
|
136
|
-
return db
|
|
137
|
-
.prepare(
|
|
138
|
-
`
|
|
139
|
-
SELECT
|
|
140
|
-
task_id,
|
|
141
|
-
instructions,
|
|
142
|
-
context,
|
|
143
|
-
status,
|
|
144
|
-
created_at,
|
|
145
|
-
updated_at,
|
|
146
|
-
current_plan,
|
|
147
|
-
message,
|
|
148
|
-
result,
|
|
149
|
-
completed_at,
|
|
150
|
-
failed_at
|
|
151
|
-
FROM tasks
|
|
152
|
-
ORDER BY created_at DESC, task_id DESC
|
|
153
|
-
`
|
|
154
|
-
)
|
|
155
|
-
.all()
|
|
156
|
-
.map(mapTask)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function appendTaskActivity(db, activity) {
|
|
160
|
-
const id = String(activity?.id || '').trim()
|
|
161
|
-
const taskId = String(activity?.taskId || '').trim()
|
|
162
|
-
if (!id || !taskId) {
|
|
163
|
-
throw new Error('Task activity requires id and taskId.')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
db.prepare(
|
|
167
|
-
`
|
|
168
|
-
INSERT INTO task_activity (id, task_id, type, name, raw_result, timestamp, data)
|
|
169
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
170
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
171
|
-
task_id = excluded.task_id,
|
|
172
|
-
type = excluded.type,
|
|
173
|
-
name = excluded.name,
|
|
174
|
-
raw_result = excluded.raw_result,
|
|
175
|
-
timestamp = excluded.timestamp,
|
|
176
|
-
data = excluded.data
|
|
177
|
-
`
|
|
178
|
-
).run(
|
|
179
|
-
id,
|
|
180
|
-
taskId,
|
|
181
|
-
String(activity?.type || ''),
|
|
182
|
-
activity?.name ? String(activity.name) : null,
|
|
183
|
-
stringifyJson(
|
|
184
|
-
activity?.rawResult,
|
|
185
|
-
activity?.rawResult === undefined ? null : String(activity.rawResult)
|
|
186
|
-
),
|
|
187
|
-
String(activity?.timestamp || new Date().toISOString()),
|
|
188
|
-
stringifyJson(activity?.data || {}, '{}')
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
return id
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function loadTaskActivity(db, taskId) {
|
|
195
|
-
return db
|
|
196
|
-
.prepare(
|
|
197
|
-
`
|
|
198
|
-
SELECT id, task_id, type, name, raw_result, timestamp, data
|
|
199
|
-
FROM task_activity
|
|
200
|
-
WHERE task_id = ?
|
|
201
|
-
ORDER BY timestamp ASC, id ASC
|
|
202
|
-
`
|
|
203
|
-
)
|
|
204
|
-
.all(String(taskId || '').trim())
|
|
205
|
-
.map(mapActivity)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export function loadAllTaskActivity(db) {
|
|
209
|
-
return db
|
|
210
|
-
.prepare(
|
|
211
|
-
`
|
|
212
|
-
SELECT id, task_id, type, name, raw_result, timestamp, data
|
|
213
|
-
FROM task_activity
|
|
214
|
-
ORDER BY timestamp ASC, id ASC
|
|
215
|
-
`
|
|
216
|
-
)
|
|
217
|
-
.all()
|
|
218
|
-
.map(mapActivity)
|
|
219
|
-
}
|