@lota-sdk/core 0.4.13 → 0.4.14
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/package.json +4 -4
- package/src/ai/embedding-cache.ts +17 -11
- package/src/ai-gateway/ai-gateway.ts +164 -94
- package/src/ai-gateway/index.ts +4 -1
- package/src/config/agent-defaults.ts +2 -2
- package/src/config/agent-types.ts +1 -1
- package/src/create-runtime.ts +259 -200
- package/src/db/cursor-pagination.ts +2 -9
- package/src/db/memory-store.ts +194 -175
- package/src/db/memory.ts +125 -71
- package/src/db/schema-fingerprint.ts +5 -4
- package/src/db/service-normalization.ts +4 -3
- package/src/db/service.ts +3 -2
- package/src/db/startup.ts +15 -16
- package/src/effect/errors.ts +161 -21
- package/src/effect/index.ts +0 -1
- package/src/embeddings/provider.ts +15 -7
- package/src/queues/autonomous-job.queue.ts +10 -22
- package/src/queues/delayed-node-promotion.queue.ts +8 -14
- package/src/queues/document-processor.queue.ts +13 -4
- package/src/queues/memory-consolidation.queue.ts +26 -14
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
- package/src/queues/plan-scheduler.queue.ts +37 -15
- package/src/queues/queue-factory.ts +59 -35
- package/src/queues/standalone-worker.ts +3 -2
- package/src/redis/connection.ts +10 -3
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +5 -5
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/chat-message.ts +64 -1
- package/src/runtime/chat-run-orchestration.ts +33 -20
- package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
- package/src/runtime/context-compaction/context-compaction.ts +78 -66
- package/src/runtime/domain-layer.ts +13 -7
- package/src/runtime/execution-plan.ts +7 -3
- package/src/runtime/memory/memory-block.ts +3 -9
- package/src/runtime/memory/memory-scope.ts +3 -1
- package/src/runtime/plugin-resolution.ts +2 -1
- package/src/runtime/post-turn-side-effects.ts +6 -5
- package/src/runtime/retrieval-adapters.ts +8 -20
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +2 -4
- package/src/runtime/runtime-lifecycle.ts +56 -16
- package/src/runtime/runtime-services.ts +180 -102
- package/src/runtime/runtime-worker-registry.ts +3 -1
- package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
- package/src/runtime/social-chat/social-chat-history.ts +21 -18
- package/src/runtime/social-chat/social-chat.ts +356 -223
- package/src/runtime/specialist-runner.ts +3 -1
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
- package/src/runtime/thread-turn-context.ts +142 -102
- package/src/runtime/turn-lifecycle.ts +15 -46
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +107 -77
- package/src/services/autonomous-job.service.ts +354 -293
- package/src/services/background-work.service.ts +3 -3
- package/src/services/context-compaction.service.ts +7 -2
- package/src/services/document-chunk.service.ts +50 -32
- package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
- package/src/services/execution-plan/execution-plan.service.ts +162 -179
- package/src/services/feedback-loop.service.ts +5 -4
- package/src/services/graph-full-routing.ts +37 -36
- package/src/services/institutional-memory.service.ts +28 -30
- package/src/services/learned-skill.service.ts +107 -72
- package/src/services/memory/memory-errors.ts +4 -23
- package/src/services/memory/memory-org-memory.ts +10 -5
- package/src/services/memory/memory-rerank.ts +18 -6
- package/src/services/memory/memory.service.ts +170 -111
- package/src/services/memory/rerank.service.ts +29 -20
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +69 -75
- package/src/services/ownership-dispatcher.service.ts +40 -39
- package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
- package/src/services/plan/plan-agent-query.service.ts +39 -31
- package/src/services/plan/plan-completion-side-effects.ts +13 -17
- package/src/services/plan/plan-coordination.service.ts +2 -1
- package/src/services/plan/plan-cycle.service.ts +6 -5
- package/src/services/plan/plan-deadline.service.ts +57 -54
- package/src/services/plan/plan-event-delivery.service.ts +5 -4
- package/src/services/plan/plan-executor-graph.ts +18 -15
- package/src/services/plan/plan-executor.service.ts +235 -262
- package/src/services/plan/plan-run.service.ts +169 -93
- package/src/services/plan/plan-scheduler.service.ts +192 -202
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +23 -14
- package/src/services/plugin-executor.service.ts +5 -9
- package/src/services/queue-job.service.ts +117 -59
- package/src/services/recent-activity-title.service.ts +13 -12
- package/src/services/recent-activity.service.ts +6 -1
- package/src/services/social-chat-history.service.ts +29 -25
- package/src/services/system-executor.service.ts +5 -9
- package/src/services/thread/thread-active-run.ts +2 -2
- package/src/services/thread/thread-listing.ts +61 -57
- package/src/services/thread/thread-memory-block.ts +73 -48
- package/src/services/thread/thread-message.service.ts +76 -65
- package/src/services/thread/thread-record-store.ts +8 -8
- package/src/services/thread/thread-title.service.ts +10 -4
- package/src/services/thread/thread-turn-execution.ts +43 -45
- package/src/services/thread/thread-turn-preparation.service.ts +257 -135
- package/src/services/thread/thread-turn-streaming.ts +82 -85
- package/src/services/thread/thread-turn.ts +8 -8
- package/src/services/thread/thread.service.ts +135 -100
- package/src/services/user.service.ts +45 -48
- package/src/storage/attachment-parser.ts +6 -2
- package/src/storage/attachment-storage.service.ts +5 -6
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +10 -9
- package/src/system-agents/delegated-agent-factory.ts +30 -6
- package/src/system-agents/memory-reranker.agent.ts +10 -9
- package/src/system-agents/memory.agent.ts +10 -9
- package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
- package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
- package/src/system-agents/skill-extractor.agent.ts +13 -12
- package/src/system-agents/skill-manager.agent.ts +13 -12
- package/src/system-agents/thread-router.agent.ts +10 -5
- package/src/system-agents/title-generator.agent.ts +13 -12
- package/src/tools/fetch-webpage.tool.ts +13 -13
- package/src/tools/memory-block.tool.ts +3 -1
- package/src/tools/plan-approval.tool.ts +4 -2
- package/src/tools/read-file-parts.tool.ts +10 -4
- package/src/tools/remember-memory.tool.ts +3 -1
- package/src/tools/research-topic.tool.ts +9 -5
- package/src/tools/search-web.tool.ts +16 -16
- package/src/tools/search.tool.ts +20 -5
- package/src/tools/team-think.tool.ts +61 -38
- package/src/utils/async.ts +5 -5
- package/src/utils/errors.ts +19 -18
- package/src/utils/sse-keepalive.ts +28 -25
- package/src/workers/bootstrap.ts +75 -11
- package/src/workers/memory-consolidation.worker.ts +82 -91
- package/src/workers/organization-learning.worker.ts +14 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
- package/src/workers/skill-extraction.runner.ts +97 -61
- package/src/workers/utils/repo-structure-extractor.ts +13 -8
- package/src/workers/utils/thread-message-query.ts +24 -24
- package/src/workers/worker-utils.ts +23 -4
- package/src/effect/helpers.ts +0 -123
package/src/db/memory.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Effect } from 'effect'
|
|
|
3
3
|
import type { z } from 'zod'
|
|
4
4
|
|
|
5
5
|
import { aiLogger } from '../config/logger'
|
|
6
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
6
7
|
import type { CreateHelperAgentFn, HelperModelRuntime } from '../runtime/helper-model'
|
|
7
8
|
import { formatResults } from '../runtime/memory/memory-format'
|
|
8
9
|
import {
|
|
@@ -16,8 +17,8 @@ import { getFactRetrievalMessages } from '../runtime/memory/memory-prompts-fact'
|
|
|
16
17
|
import { parseMessages } from '../runtime/memory/memory-prompts-parse'
|
|
17
18
|
import { getClassifyMemoryDeltaPrompt } from '../runtime/memory/memory-prompts-update'
|
|
18
19
|
import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
|
|
19
|
-
import type {
|
|
20
|
-
import { MemoryServiceError
|
|
20
|
+
import type { BackgroundWorkServiceTag } from '../services/background-work.service'
|
|
21
|
+
import { MemoryServiceError } from '../services/memory/memory-errors'
|
|
21
22
|
import { sha256Hex } from '../utils/crypto'
|
|
22
23
|
import { compactWhitespace, truncateText } from '../utils/string'
|
|
23
24
|
import type { SurrealMemoryStore } from './memory-store'
|
|
@@ -70,7 +71,8 @@ export class Memory {
|
|
|
70
71
|
db: SurrealDBService
|
|
71
72
|
runtimeConfig: ResolvedLotaRuntimeConfig
|
|
72
73
|
helperModelRuntime: HelperModelRuntime
|
|
73
|
-
background: Context.Service.Shape<typeof
|
|
74
|
+
background: Context.Service.Shape<typeof BackgroundWorkServiceTag>
|
|
75
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>
|
|
74
76
|
},
|
|
75
77
|
agent: { createAgent: CreateHelperAgentFn; maxOutputTokens?: number },
|
|
76
78
|
config: MemoryConfig = {},
|
|
@@ -80,6 +82,7 @@ export class Memory {
|
|
|
80
82
|
{
|
|
81
83
|
embeddingModel: deps.runtimeConfig.aiGateway.embeddingModel,
|
|
82
84
|
openRouterApiKey: deps.runtimeConfig.aiGateway.openRouterApiKey,
|
|
85
|
+
runPromise: deps.runPromise,
|
|
83
86
|
},
|
|
84
87
|
deps.background,
|
|
85
88
|
)
|
|
@@ -115,25 +118,25 @@ export class Memory {
|
|
|
115
118
|
durability?: Durability
|
|
116
119
|
},
|
|
117
120
|
): Effect.Effect<string, MemoryServiceError, never> {
|
|
118
|
-
return
|
|
119
|
-
|
|
121
|
+
return this.store
|
|
122
|
+
.insert(
|
|
120
123
|
content,
|
|
121
124
|
options.scopeId,
|
|
122
125
|
options.memoryType,
|
|
123
126
|
options.metadata ?? {},
|
|
124
127
|
options.importance ?? 1,
|
|
125
128
|
options.durability ?? 'standard',
|
|
126
|
-
)
|
|
127
|
-
|
|
129
|
+
)
|
|
130
|
+
.pipe(Effect.mapError((cause) => new MemoryServiceError({ message: 'Failed to insert memory.', cause })))
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
search(query: string, options: SearchOptions): Effect.Effect<string, MemoryServiceError, never> {
|
|
131
134
|
return Effect.gen(
|
|
132
135
|
function* (this: Memory) {
|
|
133
136
|
const limit = options.limit ?? this.runtimeConfig.memory.searchK
|
|
134
|
-
const results = yield*
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
const results = yield* this.store
|
|
138
|
+
.search(query, options.scopeId, limit, options.memoryType)
|
|
139
|
+
.pipe(Effect.mapError((cause) => new MemoryServiceError({ message: 'Failed to search memory.', cause })))
|
|
137
140
|
return formatResults(results)
|
|
138
141
|
}.bind(this),
|
|
139
142
|
)
|
|
@@ -143,9 +146,11 @@ export class Memory {
|
|
|
143
146
|
return Effect.gen(
|
|
144
147
|
function* (this: Memory) {
|
|
145
148
|
const limit = options.limit ?? this.runtimeConfig.memory.searchK
|
|
146
|
-
const results = yield*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
const results = yield* this.store
|
|
150
|
+
.hybridSearch(query, options.scopeId, limit, options.memoryType)
|
|
151
|
+
.pipe(
|
|
152
|
+
Effect.mapError((cause) => new MemoryServiceError({ message: 'Failed to perform hybrid search.', cause })),
|
|
153
|
+
)
|
|
149
154
|
return formatResults(results)
|
|
150
155
|
}.bind(this),
|
|
151
156
|
)
|
|
@@ -158,15 +163,19 @@ export class Memory {
|
|
|
158
163
|
return Effect.gen(
|
|
159
164
|
function* (this: Memory) {
|
|
160
165
|
const limit = options.limit ?? this.runtimeConfig.memory.searchK
|
|
161
|
-
const results = yield*
|
|
162
|
-
|
|
166
|
+
const results = yield* this.store
|
|
167
|
+
.hybridSearchWeighted(query, {
|
|
163
168
|
scopeId: options.scopeId,
|
|
164
169
|
limit,
|
|
165
170
|
memoryType: options.memoryType,
|
|
166
171
|
weights: options.weights,
|
|
167
172
|
normalization: options.normalization,
|
|
168
|
-
})
|
|
169
|
-
|
|
173
|
+
})
|
|
174
|
+
.pipe(
|
|
175
|
+
Effect.mapError(
|
|
176
|
+
(cause) => new MemoryServiceError({ message: 'Failed to perform weighted hybrid search.', cause }),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
170
179
|
return formatResults(results)
|
|
171
180
|
}.bind(this),
|
|
172
181
|
)
|
|
@@ -179,24 +188,32 @@ export class Memory {
|
|
|
179
188
|
return Effect.gen(
|
|
180
189
|
function* (this: Memory) {
|
|
181
190
|
const limit = options.limit ?? this.runtimeConfig.memory.searchK
|
|
182
|
-
const results = yield*
|
|
183
|
-
|
|
191
|
+
const results = yield* this.store
|
|
192
|
+
.hybridSearchWeighted(query, {
|
|
184
193
|
scopeId: options.scopeId,
|
|
185
194
|
limit,
|
|
186
195
|
memoryType: options.memoryType,
|
|
187
196
|
weights: options.weights,
|
|
188
197
|
normalization: options.normalization,
|
|
189
198
|
fastMode: options.fastMode,
|
|
190
|
-
})
|
|
191
|
-
|
|
199
|
+
})
|
|
200
|
+
.pipe(
|
|
201
|
+
Effect.mapError(
|
|
202
|
+
(cause) => new MemoryServiceError({ message: 'Failed to search memory candidates.', cause }),
|
|
203
|
+
),
|
|
204
|
+
)
|
|
192
205
|
|
|
193
206
|
if (options.fastMode || options.includeNeighborContext === false) {
|
|
194
207
|
return results
|
|
195
208
|
}
|
|
196
209
|
|
|
197
|
-
return yield*
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
return yield* this.store
|
|
211
|
+
.enrichWithNeighbors(results)
|
|
212
|
+
.pipe(
|
|
213
|
+
Effect.mapError(
|
|
214
|
+
(cause) => new MemoryServiceError({ message: 'Failed to enrich memory candidates.', cause }),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
200
217
|
}.bind(this),
|
|
201
218
|
)
|
|
202
219
|
}
|
|
@@ -208,15 +225,21 @@ export class Memory {
|
|
|
208
225
|
durability?: Durability
|
|
209
226
|
minImportance?: number
|
|
210
227
|
}): Effect.Effect<MemoryRecord[], MemoryServiceError, never> {
|
|
211
|
-
return
|
|
228
|
+
return this.store
|
|
229
|
+
.listTopMemories(options)
|
|
230
|
+
.pipe(Effect.mapError((cause) => new MemoryServiceError({ message: 'Failed to list top memories.', cause })))
|
|
212
231
|
}
|
|
213
232
|
|
|
214
233
|
list(options: MemoryListOptions): Effect.Effect<MemoryRecord[], MemoryServiceError, never> {
|
|
215
|
-
return
|
|
234
|
+
return this.store
|
|
235
|
+
.list(options)
|
|
236
|
+
.pipe(Effect.mapError((cause) => new MemoryServiceError({ message: 'Failed to list memories.', cause })))
|
|
216
237
|
}
|
|
217
238
|
|
|
218
239
|
updateMemory(id: string, newContent: string): Effect.Effect<void, MemoryServiceError, never> {
|
|
219
|
-
return
|
|
240
|
+
return this.store
|
|
241
|
+
.update(id, newContent)
|
|
242
|
+
.pipe(Effect.mapError((cause) => new MemoryServiceError({ message: `Failed to update memory ${id}.`, cause })))
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
addRelation(
|
|
@@ -225,13 +248,19 @@ export class Memory {
|
|
|
225
248
|
relationType: RelationType,
|
|
226
249
|
confidence = 1,
|
|
227
250
|
): Effect.Effect<void, MemoryServiceError, never> {
|
|
228
|
-
return
|
|
229
|
-
|
|
230
|
-
|
|
251
|
+
return this.store
|
|
252
|
+
.addRelation(fromId, toId, relationType, confidence)
|
|
253
|
+
.pipe(
|
|
254
|
+
Effect.mapError(
|
|
255
|
+
(cause) => new MemoryServiceError({ message: `Failed to add ${relationType} relation.`, cause }),
|
|
256
|
+
),
|
|
257
|
+
)
|
|
231
258
|
}
|
|
232
259
|
|
|
233
260
|
getStaleMemories(scopeId: string, limit?: number): Effect.Effect<MemorySearchResult[], MemoryServiceError, never> {
|
|
234
|
-
return
|
|
261
|
+
return this.store
|
|
262
|
+
.getStaleMemories(scopeId, limit)
|
|
263
|
+
.pipe(Effect.mapError((cause) => new MemoryServiceError({ message: 'Failed to load stale memories.', cause })))
|
|
235
264
|
}
|
|
236
265
|
|
|
237
266
|
extractFactsFromMessages(
|
|
@@ -277,8 +306,8 @@ export class Memory {
|
|
|
277
306
|
return Effect.forEach(
|
|
278
307
|
prepared,
|
|
279
308
|
(item) =>
|
|
280
|
-
|
|
281
|
-
|
|
309
|
+
this.applyUpdates(item.updates, item.options, item.factMaps, item.existingMemories).pipe(
|
|
310
|
+
Effect.mapError((cause) => new MemoryServiceError({ message: 'Failed to apply memory updates', cause })),
|
|
282
311
|
),
|
|
283
312
|
{ concurrency: 2, discard: true },
|
|
284
313
|
)
|
|
@@ -296,9 +325,14 @@ export class Memory {
|
|
|
296
325
|
const factContents = facts.map((fact) => fact.content)
|
|
297
326
|
const scopePayloads = yield* Effect.all(
|
|
298
327
|
scopes.map((scopeOptions) =>
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
328
|
+
this.store.list({ scopeId: scopeOptions.scopeId, memoryType: scopeOptions.memoryType }).pipe(
|
|
329
|
+
Effect.mapError(
|
|
330
|
+
(cause) =>
|
|
331
|
+
new MemoryServiceError({
|
|
332
|
+
message: `Failed to list memories for scope ${scopeOptions.scopeId}.`,
|
|
333
|
+
cause,
|
|
334
|
+
}),
|
|
335
|
+
),
|
|
302
336
|
Effect.map((existingMemories) => {
|
|
303
337
|
const normalizedMemories = existingMemories.map((memory) => ({ id: memory.id, text: memory.content }))
|
|
304
338
|
const scopeMemoryIdsByUnionId: Record<string, string[]> = {}
|
|
@@ -378,7 +412,7 @@ export class Memory {
|
|
|
378
412
|
)
|
|
379
413
|
}.bind(this),
|
|
380
414
|
).pipe(
|
|
381
|
-
Effect.catchTag(
|
|
415
|
+
Effect.catchTag(ERROR_TAGS.MemoryServiceError, (error) =>
|
|
382
416
|
Effect.sync(() => {
|
|
383
417
|
aiLogger.warn`Failed to extract facts: ${error.message}`
|
|
384
418
|
return []
|
|
@@ -564,37 +598,45 @@ export class Memory {
|
|
|
564
598
|
const metadata = { ...options.metadata, memoryCategory: action.category }
|
|
565
599
|
const hash = hashContent(action.text, options.scopeId, options.memoryType)
|
|
566
600
|
|
|
567
|
-
const newId = yield*
|
|
568
|
-
|
|
601
|
+
const newId = yield* this.store
|
|
602
|
+
.insert(
|
|
569
603
|
action.text,
|
|
570
604
|
options.scopeId,
|
|
571
605
|
options.memoryType,
|
|
572
606
|
metadata,
|
|
573
607
|
action.importance,
|
|
574
608
|
action.durability as Durability,
|
|
575
|
-
)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
609
|
+
)
|
|
610
|
+
.pipe(
|
|
611
|
+
Effect.mapError(
|
|
612
|
+
(cause) =>
|
|
613
|
+
new MemoryServiceError({
|
|
614
|
+
message: `Failed to insert memory for scope ${options.scopeId}.`,
|
|
615
|
+
cause,
|
|
616
|
+
}),
|
|
617
|
+
),
|
|
618
|
+
Effect.catchTag(ERROR_TAGS.MemoryServiceError, (error) => {
|
|
619
|
+
if (!isUniqueIndexConflict(error.cause, 'memoryHashIdx')) {
|
|
620
|
+
return Effect.fail(error)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return this.store.getByHash(hash).pipe(
|
|
624
|
+
Effect.mapError(
|
|
625
|
+
(cause) => new MemoryServiceError({ message: `Failed to look up memory hash ${hash}.`, cause }),
|
|
626
|
+
),
|
|
627
|
+
Effect.flatMap((existing) => {
|
|
628
|
+
if (!existing) {
|
|
629
|
+
return Effect.fail(error)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return Effect.sync(() => {
|
|
633
|
+
aiLogger.debug`Skipped duplicate memory insert due to hash conflict: ${existing.id}`
|
|
634
|
+
return existing.id
|
|
635
|
+
})
|
|
636
|
+
}),
|
|
637
|
+
)
|
|
638
|
+
}),
|
|
639
|
+
)
|
|
598
640
|
|
|
599
641
|
idMap.set(action.refId, newId)
|
|
600
642
|
aiLogger.debug`Added new memory (memoryType: ${options.memoryType}, category: ${action.category}, durability: ${action.durability}, importance: ${action.importance.toFixed(2)}, content: ${truncatedContent})`
|
|
@@ -603,20 +645,30 @@ export class Memory {
|
|
|
603
645
|
|
|
604
646
|
case 'update': {
|
|
605
647
|
const metadata = { ...options.metadata, ...(action.category ? { memoryCategory: action.category } : {}) }
|
|
606
|
-
yield*
|
|
607
|
-
|
|
648
|
+
yield* this.store
|
|
649
|
+
.update(action.refId, action.text, {
|
|
608
650
|
importance: action.importance,
|
|
609
651
|
durability: action.durability as Durability | undefined,
|
|
610
652
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
611
|
-
})
|
|
612
|
-
|
|
653
|
+
})
|
|
654
|
+
.pipe(
|
|
655
|
+
Effect.mapError(
|
|
656
|
+
(cause) => new MemoryServiceError({ message: `Failed to update memory ${action.refId}.`, cause }),
|
|
657
|
+
),
|
|
658
|
+
)
|
|
613
659
|
idMap.set(action.refId, action.refId)
|
|
614
660
|
aiLogger.debug`Updated memory ${action.refId}: ${truncateText(action.text, 50)}`
|
|
615
661
|
break
|
|
616
662
|
}
|
|
617
663
|
|
|
618
664
|
case 'delete':
|
|
619
|
-
yield*
|
|
665
|
+
yield* this.store
|
|
666
|
+
.delete(action.refId)
|
|
667
|
+
.pipe(
|
|
668
|
+
Effect.mapError(
|
|
669
|
+
(cause) => new MemoryServiceError({ message: `Failed to delete memory ${action.refId}.`, cause }),
|
|
670
|
+
),
|
|
671
|
+
)
|
|
620
672
|
aiLogger.debug`Deleted memory ${action.refId}`
|
|
621
673
|
break
|
|
622
674
|
}
|
|
@@ -627,15 +679,17 @@ export class Memory {
|
|
|
627
679
|
if (!fromId) continue
|
|
628
680
|
|
|
629
681
|
const toId = idMap.get(relation.toRefId) ?? relation.toRefId
|
|
630
|
-
yield*
|
|
631
|
-
|
|
632
|
-
|
|
682
|
+
yield* this.store.addRelation(fromId, toId, relation.relation).pipe(
|
|
683
|
+
Effect.mapError(
|
|
684
|
+
(cause) =>
|
|
685
|
+
new MemoryServiceError({ message: `Failed to create memory relation ${relation.relation}.`, cause }),
|
|
686
|
+
),
|
|
633
687
|
Effect.tap(() =>
|
|
634
688
|
Effect.sync(() => {
|
|
635
689
|
aiLogger.debug`Created ${relation.relation} relation: ${fromId} -> ${toId}`
|
|
636
690
|
}),
|
|
637
691
|
),
|
|
638
|
-
Effect.catchTag(
|
|
692
|
+
Effect.catchTag(ERROR_TAGS.MemoryServiceError, (error) =>
|
|
639
693
|
Effect.sync(() => {
|
|
640
694
|
aiLogger.warn`Failed to create memory relation (non-fatal, graph may be incomplete): ${error.message}`
|
|
641
695
|
}),
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Schema, Effect } from 'effect'
|
|
2
2
|
|
|
3
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
3
4
|
import { createSha256Hasher } from '../utils/crypto'
|
|
4
5
|
|
|
5
6
|
function toSchemaFilePath(value: string | URL): string {
|
|
6
7
|
return value instanceof URL ? value.pathname : value
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
class SchemaFingerprintError extends Schema.TaggedErrorClass<SchemaFingerprintError>()(
|
|
10
|
-
|
|
11
|
-
cause: Schema.Defect,
|
|
12
|
-
|
|
10
|
+
class SchemaFingerprintError extends Schema.TaggedErrorClass<SchemaFingerprintError>()(
|
|
11
|
+
ERROR_TAGS.SchemaFingerprintError,
|
|
12
|
+
{ message: Schema.String, cause: Schema.Defect },
|
|
13
|
+
) {}
|
|
13
14
|
|
|
14
15
|
export function computeSchemaFingerprint(schemaFiles: readonly (string | URL)[]): Promise<string> {
|
|
15
16
|
return Effect.runPromise(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Normalization helpers for SurrealDB service inputs.
|
|
3
3
|
*
|
|
4
4
|
* Helpers that can fail return `Effect<T, SurrealDBError>` so callers can
|
|
5
|
-
* `yield*` them inside `Effect.gen` and use `catchTag('SurrealDBError')`
|
|
5
|
+
* `yield*` them inside `Effect.gen` and use `catchTag('@lota-sdk/core/SurrealDBError')`
|
|
6
6
|
* without losing the typed failure channel.
|
|
7
7
|
*/
|
|
8
8
|
import { Effect, Schema } from 'effect'
|
|
@@ -10,12 +10,13 @@ import { BoundQuery, RecordId, StringRecordId, Table, and, eq } from 'surrealdb'
|
|
|
10
10
|
import type { ExprLike, Mutation } from 'surrealdb'
|
|
11
11
|
import type { z } from 'zod'
|
|
12
12
|
|
|
13
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
13
14
|
import { isRecord } from '../utils/string'
|
|
14
15
|
import type { RecordIdInput } from './record-id'
|
|
15
16
|
import { ensureRecordId, isRecordIdInput, isSurrealRecordIdValue } from './record-id'
|
|
16
17
|
import type { DatabaseTable } from './tables'
|
|
17
18
|
|
|
18
|
-
export class SurrealDBError extends Schema.TaggedErrorClass<SurrealDBError>()(
|
|
19
|
+
export class SurrealDBError extends Schema.TaggedErrorClass<SurrealDBError>()(ERROR_TAGS.SurrealDBError, {
|
|
19
20
|
message: Schema.String,
|
|
20
21
|
query: Schema.optional(Schema.String),
|
|
21
22
|
cause: Schema.optional(Schema.Defect),
|
|
@@ -28,7 +29,7 @@ type TransactionQueryDescriptor = { query: string; bindings?: Record<string, unk
|
|
|
28
29
|
type ParseSchema = <TSchema extends z.ZodTypeAny>(schema: TSchema, value: unknown) => z.infer<TSchema>
|
|
29
30
|
|
|
30
31
|
function isSurrealDBError(error: unknown): error is SurrealDBError {
|
|
31
|
-
return isRecord(error) && error._tag ===
|
|
32
|
+
return isRecord(error) && error._tag === ERROR_TAGS.SurrealDBError
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function toSurrealDBError(error: unknown, fallbackMessage: string): SurrealDBError {
|
package/src/db/service.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { ZodError } from 'zod'
|
|
|
5
5
|
import type { z } from 'zod'
|
|
6
6
|
|
|
7
7
|
import { serverLogger } from '../config/logger'
|
|
8
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
8
9
|
import { getErrorMessage } from '../utils/errors'
|
|
9
10
|
import type { RecordIdInput, ensureRecordId } from './record-id'
|
|
10
11
|
import { ensureRecordIdEffect } from './record-id'
|
|
@@ -107,7 +108,7 @@ function isRetriableConnectError(error: unknown): boolean {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
function isSurrealDBError(error: unknown): error is SurrealDBError {
|
|
110
|
-
return typeof error === 'object' && error !== null && (error as { _tag?: unknown })._tag ===
|
|
111
|
+
return typeof error === 'object' && error !== null && (error as { _tag?: unknown })._tag === ERROR_TAGS.SurrealDBError
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
export class SurrealDBService {
|
|
@@ -926,7 +927,7 @@ export class SurrealDBService {
|
|
|
926
927
|
const recordId = yield* this.normalizeRecordIdEffect(id, table)
|
|
927
928
|
return yield* this.query<unknown>(new BoundQuery(`DELETE $recordId RETURN BEFORE`, { recordId })).pipe(
|
|
928
929
|
Effect.map((result) => result.length > 0),
|
|
929
|
-
Effect.catchTag(
|
|
930
|
+
Effect.catchTag(ERROR_TAGS.SurrealDBError, (error) => {
|
|
930
931
|
if (error.message.includes('does not exist')) {
|
|
931
932
|
return Effect.succeed(false)
|
|
932
933
|
}
|
package/src/db/startup.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { BoundQuery, RecordId } from 'surrealdb'
|
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
|
|
6
6
|
import { DatabaseError } from '../effect/errors'
|
|
7
|
-
import { effectTryPromise } from '../effect/helpers'
|
|
8
7
|
import { nowDate } from '../utils/date-time'
|
|
9
8
|
import { getErrorMessage } from '../utils/errors'
|
|
10
9
|
import type { SurrealDBService, SurrealDatabaseLogger } from './service'
|
|
@@ -35,7 +34,7 @@ function shouldLogRetry(attempt: number): boolean {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
function connectWithStartupRetryEffectInternal(params: {
|
|
38
|
-
connect: () =>
|
|
37
|
+
connect: () => Promise<void>
|
|
39
38
|
label: string
|
|
40
39
|
logger?: StartupLogger
|
|
41
40
|
retryDelayMs?: number
|
|
@@ -46,10 +45,11 @@ function connectWithStartupRetryEffectInternal(params: {
|
|
|
46
45
|
|
|
47
46
|
let attempt = 0
|
|
48
47
|
|
|
49
|
-
const connectEffect =
|
|
50
|
-
() => params.connect(),
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
const connectEffect = Effect.tryPromise({
|
|
49
|
+
try: () => params.connect(),
|
|
50
|
+
catch: (error) =>
|
|
51
|
+
new DatabaseError({ message: `Waiting for ${params.label}: ${getErrorMessage(error)}`, cause: error }),
|
|
52
|
+
})
|
|
53
53
|
|
|
54
54
|
return connectEffect.pipe(
|
|
55
55
|
Effect.tapError((error) =>
|
|
@@ -75,7 +75,7 @@ function connectWithStartupRetryEffectInternal(params: {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export function connectWithStartupRetry(params: {
|
|
78
|
-
connect: () =>
|
|
78
|
+
connect: () => Promise<void>
|
|
79
79
|
label: string
|
|
80
80
|
logger?: StartupLogger
|
|
81
81
|
retryDelayMs?: number
|
|
@@ -104,7 +104,7 @@ function waitForDatabaseBootstrapEffectInternal(params: {
|
|
|
104
104
|
expectedFingerprint?: string | null
|
|
105
105
|
label: string
|
|
106
106
|
logger?: StartupLogger
|
|
107
|
-
connect?: () =>
|
|
107
|
+
connect?: () => Promise<void>
|
|
108
108
|
retryDelayMs?: number
|
|
109
109
|
maxWaitMs?: number
|
|
110
110
|
}): Effect.Effect<void, DatabaseError> {
|
|
@@ -120,15 +120,14 @@ function waitForDatabaseBootstrapEffectInternal(params: {
|
|
|
120
120
|
const readBootstrapRecord = Effect.gen(function* () {
|
|
121
121
|
const connect = params.connect
|
|
122
122
|
if (connect) {
|
|
123
|
-
yield*
|
|
124
|
-
() => connect(),
|
|
125
|
-
(error): BootstrapWaitFailure => ({ _tag: 'read-failed', cause: error }),
|
|
126
|
-
)
|
|
123
|
+
yield* Effect.tryPromise({
|
|
124
|
+
try: () => connect(),
|
|
125
|
+
catch: (error): BootstrapWaitFailure => ({ _tag: 'read-failed', cause: error }),
|
|
126
|
+
})
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
return yield*
|
|
130
|
-
() =>
|
|
131
|
-
(error): BootstrapWaitFailure => ({ _tag: 'read-failed', cause: error }),
|
|
129
|
+
return yield* readDatabaseBootstrapRecord(params.databaseService).pipe(
|
|
130
|
+
Effect.mapError((error): BootstrapWaitFailure => ({ _tag: 'read-failed', cause: error })),
|
|
132
131
|
)
|
|
133
132
|
})
|
|
134
133
|
|
|
@@ -194,7 +193,7 @@ export function waitForDatabaseBootstrap(params: {
|
|
|
194
193
|
expectedFingerprint?: string | null
|
|
195
194
|
label: string
|
|
196
195
|
logger?: StartupLogger
|
|
197
|
-
connect?: () =>
|
|
196
|
+
connect?: () => Promise<void>
|
|
198
197
|
retryDelayMs?: number
|
|
199
198
|
maxWaitMs?: number
|
|
200
199
|
}): Promise<void> {
|