@lota-sdk/core 0.1.5

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.
Files changed (153) hide show
  1. package/infrastructure/schema/00_workstream.surql +55 -0
  2. package/infrastructure/schema/01_memory.surql +47 -0
  3. package/infrastructure/schema/02_execution_plan.surql +62 -0
  4. package/infrastructure/schema/03_learned_skill.surql +32 -0
  5. package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
  6. package/package.json +128 -0
  7. package/src/ai/definitions.ts +308 -0
  8. package/src/bifrost/bifrost.ts +256 -0
  9. package/src/config/agent-defaults.ts +99 -0
  10. package/src/config/constants.ts +33 -0
  11. package/src/config/env-shapes.ts +122 -0
  12. package/src/config/logger.ts +29 -0
  13. package/src/config/model-constants.ts +31 -0
  14. package/src/config/search.ts +17 -0
  15. package/src/config/workstream-defaults.ts +68 -0
  16. package/src/db/base.service.ts +55 -0
  17. package/src/db/cursor-pagination.ts +73 -0
  18. package/src/db/memory-query-builder.ts +207 -0
  19. package/src/db/memory-store.helpers.ts +118 -0
  20. package/src/db/memory-store.rows.ts +29 -0
  21. package/src/db/memory-store.ts +974 -0
  22. package/src/db/memory-types.ts +193 -0
  23. package/src/db/memory.ts +505 -0
  24. package/src/db/record-id.ts +78 -0
  25. package/src/db/service.ts +932 -0
  26. package/src/db/startup.ts +152 -0
  27. package/src/db/tables.ts +20 -0
  28. package/src/document/org-document-chunking.ts +224 -0
  29. package/src/document/parsing.ts +40 -0
  30. package/src/embeddings/provider.ts +76 -0
  31. package/src/index.ts +302 -0
  32. package/src/queues/context-compaction.queue.ts +82 -0
  33. package/src/queues/document-processor.queue.ts +118 -0
  34. package/src/queues/memory-consolidation.queue.ts +65 -0
  35. package/src/queues/post-chat-memory.queue.ts +128 -0
  36. package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
  37. package/src/queues/regular-chat-memory-digest.config.ts +12 -0
  38. package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
  39. package/src/queues/skill-extraction.config.ts +9 -0
  40. package/src/queues/skill-extraction.queue.ts +62 -0
  41. package/src/redis/connection.ts +176 -0
  42. package/src/redis/index.ts +30 -0
  43. package/src/redis/org-memory-lock.ts +43 -0
  44. package/src/redis/redis-lease-lock.ts +158 -0
  45. package/src/runtime/agent-contract.ts +1 -0
  46. package/src/runtime/agent-prompt-context.ts +119 -0
  47. package/src/runtime/agent-runtime-policy.ts +192 -0
  48. package/src/runtime/agent-stream-helpers.ts +117 -0
  49. package/src/runtime/agent-types.ts +22 -0
  50. package/src/runtime/approval-continuation.ts +16 -0
  51. package/src/runtime/chat-attachments.ts +46 -0
  52. package/src/runtime/chat-message.ts +10 -0
  53. package/src/runtime/chat-request-routing.ts +21 -0
  54. package/src/runtime/chat-run-orchestration.ts +25 -0
  55. package/src/runtime/chat-run-registry.ts +20 -0
  56. package/src/runtime/chat-types.ts +18 -0
  57. package/src/runtime/context-compaction-constants.ts +11 -0
  58. package/src/runtime/context-compaction-runtime.ts +86 -0
  59. package/src/runtime/context-compaction.ts +909 -0
  60. package/src/runtime/execution-plan.ts +59 -0
  61. package/src/runtime/helper-model.ts +405 -0
  62. package/src/runtime/indexed-repositories-policy.ts +28 -0
  63. package/src/runtime/instruction-sections.ts +8 -0
  64. package/src/runtime/llm-content.ts +71 -0
  65. package/src/runtime/memory-block.ts +264 -0
  66. package/src/runtime/memory-digest-policy.ts +14 -0
  67. package/src/runtime/memory-format.ts +8 -0
  68. package/src/runtime/memory-pipeline.ts +570 -0
  69. package/src/runtime/memory-prompts-fact.ts +47 -0
  70. package/src/runtime/memory-prompts-parse.ts +3 -0
  71. package/src/runtime/memory-prompts-update.ts +37 -0
  72. package/src/runtime/memory-scope.ts +43 -0
  73. package/src/runtime/plugin-types.ts +10 -0
  74. package/src/runtime/retrieval-adapters.ts +25 -0
  75. package/src/runtime/retrieval-pipeline.ts +3 -0
  76. package/src/runtime/runtime-extensions.ts +154 -0
  77. package/src/runtime/skill-extraction-policy.ts +3 -0
  78. package/src/runtime/team-consultation-orchestrator.ts +245 -0
  79. package/src/runtime/team-consultation-prompts.ts +32 -0
  80. package/src/runtime/title-helpers.ts +12 -0
  81. package/src/runtime/turn-lifecycle.ts +28 -0
  82. package/src/runtime/workstream-chat-helpers.ts +187 -0
  83. package/src/runtime/workstream-routing-policy.ts +301 -0
  84. package/src/runtime/workstream-state.ts +261 -0
  85. package/src/services/attachment.service.ts +159 -0
  86. package/src/services/chat-attachments.service.ts +17 -0
  87. package/src/services/chat-run-registry.service.ts +3 -0
  88. package/src/services/context-compaction-runtime.ts +13 -0
  89. package/src/services/context-compaction.service.ts +115 -0
  90. package/src/services/document-chunk.service.ts +141 -0
  91. package/src/services/execution-plan.service.ts +890 -0
  92. package/src/services/learned-skill.service.ts +328 -0
  93. package/src/services/memory-assessment.service.ts +43 -0
  94. package/src/services/memory.service.ts +807 -0
  95. package/src/services/memory.utils.ts +84 -0
  96. package/src/services/mutating-approval.service.ts +110 -0
  97. package/src/services/recent-activity-title.service.ts +74 -0
  98. package/src/services/recent-activity.service.ts +397 -0
  99. package/src/services/workstream-change-tracker.service.ts +313 -0
  100. package/src/services/workstream-message.service.ts +283 -0
  101. package/src/services/workstream-title.service.ts +58 -0
  102. package/src/services/workstream-turn-preparation.ts +1340 -0
  103. package/src/services/workstream-turn.ts +37 -0
  104. package/src/services/workstream.service.ts +854 -0
  105. package/src/services/workstream.types.ts +118 -0
  106. package/src/storage/attachment-parser.ts +101 -0
  107. package/src/storage/attachment-storage.service.ts +391 -0
  108. package/src/storage/attachments.types.ts +11 -0
  109. package/src/storage/attachments.utils.ts +58 -0
  110. package/src/storage/generated-document-storage.service.ts +55 -0
  111. package/src/system-agents/agent-result.ts +27 -0
  112. package/src/system-agents/context-compacter.agent.ts +46 -0
  113. package/src/system-agents/delegated-agent-factory.ts +177 -0
  114. package/src/system-agents/helper-agent-options.ts +20 -0
  115. package/src/system-agents/memory-reranker.agent.ts +38 -0
  116. package/src/system-agents/memory.agent.ts +58 -0
  117. package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
  118. package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
  119. package/src/system-agents/researcher.agent.ts +34 -0
  120. package/src/system-agents/skill-extractor.agent.ts +88 -0
  121. package/src/system-agents/skill-manager.agent.ts +80 -0
  122. package/src/system-agents/title-generator.agent.ts +42 -0
  123. package/src/system-agents/workstream-tracker.agent.ts +58 -0
  124. package/src/tools/execution-plan.tool.ts +163 -0
  125. package/src/tools/fetch-webpage.tool.ts +132 -0
  126. package/src/tools/firecrawl-client.ts +12 -0
  127. package/src/tools/memory-block.tool.ts +55 -0
  128. package/src/tools/read-file-parts.tool.ts +80 -0
  129. package/src/tools/remember-memory.tool.ts +85 -0
  130. package/src/tools/research-topic.tool.ts +15 -0
  131. package/src/tools/search-tools.ts +55 -0
  132. package/src/tools/search-web.tool.ts +175 -0
  133. package/src/tools/team-think.tool.ts +125 -0
  134. package/src/tools/tool-contract.ts +21 -0
  135. package/src/tools/user-questions.tool.ts +18 -0
  136. package/src/utils/async.ts +50 -0
  137. package/src/utils/date-time.ts +34 -0
  138. package/src/utils/error.ts +10 -0
  139. package/src/utils/errors.ts +28 -0
  140. package/src/utils/hono-error-handler.ts +71 -0
  141. package/src/utils/string.ts +51 -0
  142. package/src/workers/bootstrap.ts +44 -0
  143. package/src/workers/memory-consolidation.worker.ts +318 -0
  144. package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
  145. package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
  146. package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
  147. package/src/workers/skill-extraction.runner.ts +331 -0
  148. package/src/workers/skill-extraction.worker.ts +22 -0
  149. package/src/workers/utils/repo-indexer-chunker.ts +331 -0
  150. package/src/workers/utils/repo-structure-extractor.ts +645 -0
  151. package/src/workers/utils/repomix-process-concurrency.ts +65 -0
  152. package/src/workers/utils/sandbox-error.ts +5 -0
  153. package/src/workers/worker-utils.ts +182 -0
@@ -0,0 +1,55 @@
1
+ import type { z } from 'zod'
2
+
3
+ import { NotFoundError } from '../utils/errors'
4
+ import { databaseService as defaultDatabaseService } from './service'
5
+ import type { SurrealDBService } from './service'
6
+ import type { DatabaseTable } from './tables'
7
+
8
+ export abstract class BaseService<T extends z.ZodType> {
9
+ protected readonly db: SurrealDBService
10
+ constructor(
11
+ protected readonly table: DatabaseTable,
12
+ protected readonly schema: T,
13
+ protected readonly databaseService: SurrealDBService = defaultDatabaseService,
14
+ ) {
15
+ this.db = this.databaseService
16
+ }
17
+
18
+ async findById(id: unknown): Promise<z.infer<T> | null> {
19
+ return this.databaseService.findOne(this.table, { id }, this.schema)
20
+ }
21
+
22
+ async getById(id: unknown): Promise<z.infer<T>> {
23
+ const record = await this.findById(id)
24
+ if (!record) {
25
+ throw new NotFoundError(`${this.table} record not found`)
26
+ }
27
+ return record
28
+ }
29
+
30
+ async findAll(
31
+ filter: Record<string, unknown> = {},
32
+ options?: { limit?: number; offset?: number; orderBy?: keyof z.infer<T> & string; orderDir?: 'ASC' | 'DESC' },
33
+ ): Promise<z.infer<T>[]> {
34
+ return this.databaseService.findMany(this.table, filter, this.schema, options)
35
+ }
36
+
37
+ async create(data: Record<string, unknown>): Promise<z.infer<T>> {
38
+ return this.databaseService.create(this.table, data, this.schema)
39
+ }
40
+
41
+ async update(id: unknown, data: Record<string, unknown>): Promise<z.infer<T>> {
42
+ const updated = await this.databaseService.update(this.table, id, data, this.schema)
43
+ if (!updated) {
44
+ throw new NotFoundError(`${this.table} record not found`)
45
+ }
46
+ return updated
47
+ }
48
+
49
+ async delete(id: unknown): Promise<void> {
50
+ const deleted = await this.databaseService.deleteById(this.table, id)
51
+ if (!deleted) {
52
+ throw new NotFoundError(`${this.table} record not found`)
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,73 @@
1
+ import { toTimestamp } from '@lota-sdk/shared/runtime/chat-message-metadata'
2
+ import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
3
+ import type { BoundQuery, RecordId } from 'surrealdb'
4
+ import { z } from 'zod'
5
+
6
+ import type { RecordIdRef } from './record-id'
7
+ import { databaseService } from './service'
8
+ import type { DatabaseTable } from './tables'
9
+
10
+ export const CursorRowSchema = z.object({ createdAt: z.union([z.date(), z.string(), z.number()]) })
11
+
12
+ export interface MessageHistoryPage {
13
+ messages: ChatMessage[]
14
+ hasMore: boolean
15
+ prevCursor: string | null
16
+ }
17
+
18
+ export interface CursorPaginationConfig {
19
+ table: DatabaseTable
20
+ parentFilterField: string
21
+ toRowId: (parentId: RecordIdRef, messageId: string) => RecordId
22
+ parseRow: (row: unknown) => { messageId: string }
23
+ toMessage: (row: unknown) => ChatMessage
24
+ queryLatest: (parentId: RecordIdRef, limit: number) => BoundQuery
25
+ queryBefore: (parentId: RecordIdRef, cursorCreatedAt: Date, cursorId: RecordId, limit: number) => BoundQuery
26
+ }
27
+
28
+ export async function listMessageHistoryPage(
29
+ config: CursorPaginationConfig,
30
+ params: { parentId: RecordIdRef; take: number; beforeMessageId?: string },
31
+ ): Promise<MessageHistoryPage> {
32
+ const take = Math.max(1, params.take)
33
+ const beforeMessageId = params.beforeMessageId?.trim()
34
+ const limit = take + 1
35
+
36
+ const rows = beforeMessageId
37
+ ? await listRowsBefore(config, { parentId: params.parentId, beforeMessageId, take: limit })
38
+ : await databaseService.query<unknown>(config.queryLatest(params.parentId, limit))
39
+
40
+ const parsedRows = rows.map((row) => config.parseRow(row))
41
+ const hasMore = parsedRows.length > take
42
+ const pageRows = hasMore ? rows.slice(0, take) : rows
43
+
44
+ pageRows.reverse()
45
+
46
+ return {
47
+ messages: pageRows.map((row) => config.toMessage(row)),
48
+ hasMore,
49
+ prevCursor: hasMore ? config.parseRow(pageRows[0]).messageId : null,
50
+ }
51
+ }
52
+
53
+ async function listRowsBefore(
54
+ config: CursorPaginationConfig,
55
+ params: { parentId: RecordIdRef; beforeMessageId: string; take: number },
56
+ ): Promise<unknown[]> {
57
+ const cursorRow = await databaseService.findOne(
58
+ config.table,
59
+ { [config.parentFilterField]: params.parentId, messageId: params.beforeMessageId },
60
+ CursorRowSchema,
61
+ )
62
+
63
+ if (!cursorRow) {
64
+ throw new Error(`Cursor message not found in ${config.table}: ${params.beforeMessageId}`)
65
+ }
66
+
67
+ const cursorCreatedAt = new Date(toTimestamp(cursorRow.createdAt))
68
+ const cursorId = config.toRowId(params.parentId, params.beforeMessageId)
69
+
70
+ return await databaseService.query<unknown>(
71
+ config.queryBefore(params.parentId, cursorCreatedAt, cursorId, params.take),
72
+ )
73
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Query builder for SurrealDB memory operations
3
+ * Builds type-safe SQL queries with proper parameterization
4
+ */
5
+
6
+ import { validateKnnLimit } from '../config/constants'
7
+ import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '../config/search'
8
+ import { TABLES } from './tables'
9
+
10
+ const MEMORY_TABLE = TABLES.MEMORY
11
+
12
+ interface VectorSearchParams {
13
+ embedding: number[]
14
+ scopeId: string
15
+ limit: number
16
+ memoryType?: string
17
+ pointInTime?: string
18
+ }
19
+
20
+ interface HybridSearchParams extends VectorSearchParams {
21
+ query: string
22
+ }
23
+
24
+ interface LinearSearchParams extends HybridSearchParams {
25
+ weights: [number, number]
26
+ normalization: 'minmax' | 'zscore' | 'none'
27
+ }
28
+
29
+ class MemoryQueryBuilder {
30
+ private buildTypeFilter(memoryType?: string): string {
31
+ return memoryType ? 'AND memoryType = $memoryType' : ''
32
+ }
33
+
34
+ private buildTemporalPredicate(pointInTime?: string): string {
35
+ if (pointInTime) {
36
+ return 'archivedAt IS NONE AND validFrom <= $pointInTime AND IF validUntil IS NONE { true } ELSE { validUntil > $pointInTime }'
37
+ }
38
+ return 'archivedAt IS NONE AND IF validUntil IS NONE { true } ELSE { validUntil > time::now() }'
39
+ }
40
+
41
+ private buildTemporalFilter(pointInTime?: string): string {
42
+ if (pointInTime) {
43
+ return 'AND archivedAt IS NONE AND validFrom <= $pointInTime AND IF validUntil IS NONE { true } ELSE { validUntil > $pointInTime }'
44
+ }
45
+ return 'AND archivedAt IS NONE AND IF validUntil IS NONE { true } ELSE { validUntil > time::now() }'
46
+ }
47
+
48
+ /**
49
+ * Build vector similarity search query
50
+ */
51
+ buildVectorSearch(params: VectorSearchParams): { sql: string; bindVars: Record<string, unknown> } {
52
+ const typeFilter = this.buildTypeFilter(params.memoryType)
53
+ const temporalPredicate = this.buildTemporalPredicate(params.pointInTime)
54
+ const fetchLimit = validateKnnLimit(params.limit * VECTOR_SEARCH_OVERFETCH_MULTIPLIER)
55
+
56
+ const sql = `
57
+ LET $vectorCandidates = (
58
+ SELECT
59
+ id,
60
+ content,
61
+ metadata,
62
+ archivedAt,
63
+ validFrom,
64
+ validUntil,
65
+ vector::distance::knn() AS distance
66
+ FROM ${MEMORY_TABLE}
67
+ WHERE scopeId = $scopeId ${typeFilter}
68
+ AND embedding <|${fetchLimit}|> $embedding
69
+ ORDER BY distance ASC
70
+ LIMIT $fetchLimit
71
+ );
72
+
73
+ SELECT
74
+ id,
75
+ content,
76
+ metadata,
77
+ distance
78
+ FROM $vectorCandidates
79
+ WHERE ${temporalPredicate}
80
+ ORDER BY distance ASC
81
+ LIMIT $fetchLimit
82
+ `
83
+
84
+ return {
85
+ sql,
86
+ bindVars: {
87
+ embedding: params.embedding,
88
+ scopeId: params.scopeId,
89
+ memoryType: params.memoryType,
90
+ pointInTime: params.pointInTime,
91
+ fetchLimit,
92
+ },
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Build hybrid search query using RRF (Reciprocal Rank Fusion)
98
+ * Combines vector similarity + full-text search
99
+ */
100
+ buildHybridSearch(params: HybridSearchParams): { sql: string; bindVars: Record<string, unknown> } {
101
+ const typeFilter = this.buildTypeFilter(params.memoryType)
102
+ const temporalFilter = this.buildTemporalFilter(params.pointInTime)
103
+ const fetchLimit = validateKnnLimit(params.limit * VECTOR_SEARCH_OVERFETCH_MULTIPLIER)
104
+
105
+ const sql = `
106
+ LET $vs = (
107
+ SELECT
108
+ id,
109
+ content,
110
+ metadata,
111
+ vector::distance::knn() AS distance,
112
+ (1 / (1 + vector::distance::knn())) AS score
113
+ FROM ${MEMORY_TABLE}
114
+ WHERE scopeId = $scopeId ${typeFilter} ${temporalFilter}
115
+ AND embedding <|${fetchLimit}|> $embedding
116
+ ORDER BY distance ASC
117
+ LIMIT $fetchLimit
118
+ );
119
+
120
+ LET $ft_raw = (
121
+ SELECT id, content, metadata, search::score(1) AS score
122
+ FROM ${MEMORY_TABLE}
123
+ WHERE scopeId = $scopeId ${typeFilter} ${temporalFilter}
124
+ AND content @1@ $query
125
+ ORDER BY score DESC
126
+ LIMIT $fetchLimit
127
+ );
128
+
129
+ LET $ft = IF array::len($ft_raw) > 0 { $ft_raw } ELSE { (SELECT id, content, metadata, 0 AS score FROM $vs) };
130
+
131
+ SELECT id, content, metadata, rrf_score AS rrfScore
132
+ FROM search::rrf([$vs, $ft], ${fetchLimit}, 60)
133
+ ORDER BY rrfScore DESC
134
+ LIMIT $fetchLimit
135
+ `
136
+
137
+ return {
138
+ sql,
139
+ bindVars: {
140
+ embedding: params.embedding,
141
+ query: params.query,
142
+ scopeId: params.scopeId,
143
+ memoryType: params.memoryType,
144
+ pointInTime: params.pointInTime,
145
+ fetchLimit,
146
+ },
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Build linear combination search query
152
+ * Allows weighted combination of vector + text scores
153
+ */
154
+ buildLinearSearch(params: LinearSearchParams): { sql: string; bindVars: Record<string, unknown> } {
155
+ const typeFilter = this.buildTypeFilter(params.memoryType)
156
+ const temporalFilter = this.buildTemporalFilter(params.pointInTime)
157
+ const fetchLimit = validateKnnLimit(params.limit * VECTOR_SEARCH_OVERFETCH_MULTIPLIER)
158
+
159
+ const sql = `
160
+ LET $vs = (
161
+ SELECT
162
+ id,
163
+ content,
164
+ metadata,
165
+ vector::distance::knn() AS distance,
166
+ (1 / (1 + vector::distance::knn())) AS score
167
+ FROM ${MEMORY_TABLE}
168
+ WHERE scopeId = $scopeId ${typeFilter} ${temporalFilter}
169
+ AND embedding <|${fetchLimit}|> $embedding
170
+ ORDER BY distance ASC
171
+ LIMIT $fetchLimit
172
+ );
173
+
174
+ LET $ft_raw = (
175
+ SELECT id, content, metadata, search::score(1) AS score
176
+ FROM ${MEMORY_TABLE}
177
+ WHERE scopeId = $scopeId ${typeFilter} ${temporalFilter}
178
+ AND content @1@ $query
179
+ ORDER BY score DESC
180
+ LIMIT $fetchLimit
181
+ );
182
+
183
+ LET $ft = IF array::len($ft_raw) > 0 { $ft_raw } ELSE { (SELECT id, content, metadata, 0 AS score FROM $vs) };
184
+
185
+ SELECT id, content, metadata, linear_score AS linearScore
186
+ FROM search::linear([$vs, $ft], $weights, ${fetchLimit}, $normalization)
187
+ ORDER BY linearScore DESC
188
+ LIMIT $fetchLimit
189
+ `
190
+
191
+ return {
192
+ sql,
193
+ bindVars: {
194
+ embedding: params.embedding,
195
+ query: params.query,
196
+ scopeId: params.scopeId,
197
+ memoryType: params.memoryType,
198
+ pointInTime: params.pointInTime,
199
+ weights: params.weights,
200
+ normalization: params.normalization,
201
+ fetchLimit,
202
+ },
203
+ }
204
+ }
205
+ }
206
+
207
+ export const memoryQueryBuilder = new MemoryQueryBuilder()
@@ -0,0 +1,118 @@
1
+ import { createHash } from 'node:crypto'
2
+
3
+ import type { BasicSearchRow, SurrealMemoryRow } from './memory-store.rows'
4
+ import type { MemoryRecord, MemorySearchResult } from './memory-types'
5
+ import { recordIdToString } from './record-id'
6
+ import { TABLES } from './tables'
7
+
8
+ export function isUniqueIndexConflict(error: unknown, indexName: string): boolean {
9
+ if (!(error instanceof Error)) return false
10
+ const message = error.message
11
+ return message.includes(indexName) && message.includes('already contains')
12
+ }
13
+
14
+ const coerceDate = (value: unknown): Date => {
15
+ if (value instanceof Date) return value
16
+ if (typeof value === 'number') {
17
+ const millis = value < 1_000_000_000_000 ? value * 1000 : value
18
+ return new Date(millis)
19
+ }
20
+ return new Date(String(value))
21
+ }
22
+
23
+ export function mapRowToMemoryRecord(row: SurrealMemoryRow): MemoryRecord {
24
+ return {
25
+ id: recordIdToString(row.id, TABLES.MEMORY),
26
+ content: row.content,
27
+ embedding: row.embedding,
28
+ hash: row.hash,
29
+ scopeId: row.scopeId,
30
+ memoryType: row.memoryType,
31
+ durability: row.durability,
32
+ metadata: row.metadata,
33
+ importance: row.importance,
34
+ accessCount: row.accessCount,
35
+ needsReview: row.needsReview,
36
+ lastAccessedAt: row.lastAccessedAt !== undefined ? coerceDate(row.lastAccessedAt) : undefined,
37
+ createdAt: coerceDate(row.createdAt),
38
+ updatedAt: row.updatedAt !== undefined ? coerceDate(row.updatedAt) : undefined,
39
+ validFrom: coerceDate(row.validFrom),
40
+ validUntil: row.validUntil !== undefined ? coerceDate(row.validUntil) : undefined,
41
+ archivedAt: row.archivedAt !== undefined ? coerceDate(row.archivedAt) : undefined,
42
+ }
43
+ }
44
+
45
+ function calculateAdjustedScore(options: {
46
+ baseScore: number
47
+ supportCount: number
48
+ contradictCount: number
49
+ supportMultiplier: number
50
+ contradictMultiplier: number
51
+ }): number {
52
+ const supportBoost = options.supportCount * options.supportMultiplier
53
+ const contradictPenalty = options.contradictCount * options.contradictMultiplier
54
+ return Math.max(0, options.baseScore + supportBoost - contradictPenalty)
55
+ }
56
+
57
+ export interface RelationCounts {
58
+ supersedeCount: number
59
+ supportCount: number
60
+ contradictCount: number
61
+ contradictions: string[]
62
+ }
63
+
64
+ export function processGraphAwareRows<T extends BasicSearchRow>(
65
+ rows: T[],
66
+ relationCounts: Map<string, RelationCounts>,
67
+ limit: number,
68
+ baseScore: (row: T) => number,
69
+ multipliers: { support: number; contradict: number },
70
+ minimumScore: number,
71
+ ): MemorySearchResult[] {
72
+ return rows
73
+ .filter((row) => {
74
+ const id = recordIdToString(row.id, TABLES.MEMORY)
75
+ const counts = relationCounts.get(id)
76
+ return !counts || counts.supersedeCount === 0
77
+ })
78
+ .map((row) => {
79
+ const id = recordIdToString(row.id, TABLES.MEMORY)
80
+ const counts = relationCounts.get(id) ?? {
81
+ supportCount: 0,
82
+ contradictCount: 0,
83
+ supersedeCount: 0,
84
+ contradictions: [],
85
+ }
86
+ const score = calculateAdjustedScore({
87
+ baseScore: baseScore(row),
88
+ supportCount: counts.supportCount,
89
+ contradictCount: counts.contradictCount,
90
+ supportMultiplier: multipliers.support,
91
+ contradictMultiplier: multipliers.contradict,
92
+ })
93
+ const relationCount = counts.supportCount + counts.contradictCount + counts.supersedeCount
94
+ const result: MemorySearchResult = {
95
+ id,
96
+ content: row.content,
97
+ score,
98
+ metadata: {
99
+ ...row.metadata,
100
+ supportCount: counts.supportCount,
101
+ contradictCount: counts.contradictCount,
102
+ supersedeCount: counts.supersedeCount,
103
+ relationCount,
104
+ },
105
+ }
106
+ if (counts.contradictions.length > 0) {
107
+ result.contradictions = counts.contradictions
108
+ }
109
+ return result
110
+ })
111
+ .filter((result) => result.score >= minimumScore)
112
+ .sort((a, b) => b.score - a.score)
113
+ .slice(0, limit)
114
+ }
115
+
116
+ export function hashContent(content: string, scopeId: string, memoryType: string): string {
117
+ return createHash('sha256').update(`${scopeId}:${memoryType}:${content}`).digest('hex')
118
+ }
@@ -0,0 +1,29 @@
1
+ import type { Durability, MemoryType } from './memory-types'
2
+ import type { RecordIdInput } from './record-id'
3
+
4
+ export interface SurrealMemoryRow {
5
+ id: RecordIdInput
6
+ content: string
7
+ embedding: number[]
8
+ hash: string
9
+ scopeId: string
10
+ memoryType: MemoryType
11
+ durability: Durability
12
+ metadata: Record<string, unknown>
13
+ importance: number
14
+ accessCount: number
15
+ needsReview: boolean
16
+ lastAccessedAt?: Date | string
17
+ createdAt: Date | string
18
+ updatedAt?: Date | string
19
+ validFrom: Date | string
20
+ validUntil?: Date | string
21
+ archivedAt?: Date | string
22
+ }
23
+
24
+ export interface BasicSearchRow {
25
+ id: RecordIdInput
26
+ content: string
27
+ metadata: Record<string, unknown>
28
+ createdAt?: Date | string
29
+ }