@lota-sdk/core 0.1.15 → 0.1.16
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/infrastructure/schema/00_identity.surql +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +8 -7
- package/src/ai/definitions.ts +80 -2
- package/src/ai/index.ts +0 -2
- package/src/bifrost/bifrost.ts +2 -7
- package/src/config/agent-defaults.ts +31 -21
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +244 -178
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +14 -18
- package/src/db/memory.ts +13 -13
- package/src/db/service.ts +153 -79
- package/src/db/startup.ts +6 -10
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/queues/context-compaction.queue.ts +15 -46
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +16 -51
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -56
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
- package/src/queues/skill-extraction.queue.ts +15 -47
- package/src/queues/workstream-title-generation.queue.ts +15 -47
- package/src/redis/connection.ts +6 -0
- package/src/redis/index.ts +1 -1
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction.ts +22 -60
- package/src/runtime/execution-plan.ts +22 -18
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +9 -197
- package/src/runtime/index.ts +2 -0
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +9 -11
- package/src/runtime/memory-pipeline.ts +6 -9
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +72 -0
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +2 -2
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -28
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +2 -4
- package/src/runtime/workstream-chat-helpers.ts +1 -1
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +293 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +6 -11
- package/src/services/context-compaction.service.ts +72 -55
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +1 -1
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +269 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +26 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +24 -5
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/memory-utils.ts +3 -8
- package/src/services/memory.service.ts +42 -59
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +11 -4
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +384 -40
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +84 -2
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity.service.ts +27 -31
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +12 -34
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -1
- package/src/services/workstream-turn-preparation.service.ts +34 -66
- package/src/services/workstream.service.ts +33 -55
- package/src/services/workstream.types.ts +9 -9
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-utils.ts +1 -1
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/system-agents/delegated-agent-factory.ts +2 -0
- package/src/tools/execution-plan.tool.ts +17 -23
- package/src/tools/index.ts +0 -1
- package/src/tools/team-think.tool.ts +6 -4
- package/src/utils/async.ts +2 -1
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +42 -10
- package/src/utils/index.ts +9 -0
- package/src/utils/string.ts +114 -1
- package/src/workers/index.ts +1 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +2 -2
- package/src/workers/skill-extraction.runner.ts +1 -1
- package/src/workers/utils/file-section-chunker.ts +2 -1
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +11 -20
- package/src/workers/worker-utils.ts +2 -2
- package/src/tools/log-hello-world.tool.ts +0 -17
package/src/db/memory.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { getFactRetrievalMessages } from '../runtime/memory-prompts-fact'
|
|
|
12
12
|
import { parseMessages } from '../runtime/memory-prompts-parse'
|
|
13
13
|
import { getClassifyMemoryDeltaPrompt } from '../runtime/memory-prompts-update'
|
|
14
14
|
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
15
|
-
import { compactWhitespace } from '../utils/string'
|
|
15
|
+
import { compactWhitespace, truncateText } from '../utils/string'
|
|
16
16
|
import type { SurrealMemoryStore } from './memory-store'
|
|
17
17
|
import { getDefaultMemoryStore } from './memory-store'
|
|
18
18
|
import { hashContent, isUniqueIndexConflict } from './memory-store.helpers'
|
|
@@ -82,7 +82,7 @@ export class Memory {
|
|
|
82
82
|
durability?: Durability
|
|
83
83
|
},
|
|
84
84
|
): Promise<string> {
|
|
85
|
-
return
|
|
85
|
+
return this.store.insert(
|
|
86
86
|
content,
|
|
87
87
|
options.scopeId,
|
|
88
88
|
options.memoryType,
|
|
@@ -137,7 +137,7 @@ export class Memory {
|
|
|
137
137
|
return results
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
return
|
|
140
|
+
return this.store.enrichWithNeighbors(results)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
async listTopMemories(options: {
|
|
@@ -147,11 +147,11 @@ export class Memory {
|
|
|
147
147
|
durability?: Durability
|
|
148
148
|
minImportance?: number
|
|
149
149
|
}): Promise<MemoryRecord[]> {
|
|
150
|
-
return
|
|
150
|
+
return this.store.listTopMemories(options)
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
async list(options: MemoryListOptions): Promise<MemoryRecord[]> {
|
|
154
|
-
return
|
|
154
|
+
return this.store.list(options)
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
async updateMemory(id: string, newContent: string): Promise<void> {
|
|
@@ -163,7 +163,7 @@ export class Memory {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
async getStaleMemories(scopeId: string, limit?: number): Promise<MemorySearchResult[]> {
|
|
166
|
-
return
|
|
166
|
+
return this.store.getStaleMemories(scopeId, limit)
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
async add(
|
|
@@ -244,12 +244,12 @@ export class Memory {
|
|
|
244
244
|
const factMaps = buildMemoryFactMaps(facts)
|
|
245
245
|
|
|
246
246
|
const existingMemories = await this.store.list({ scopeId: options.scopeId, memoryType: options.memoryType })
|
|
247
|
-
const
|
|
247
|
+
const memoryDeltaInput = existingMemories.map((m) => ({ id: m.id, text: m.content }))
|
|
248
248
|
|
|
249
249
|
const factContents = facts.map((f) => f.content)
|
|
250
|
-
const updates = await this.determineUpdates(
|
|
250
|
+
const updates = await this.determineUpdates(memoryDeltaInput, factContents)
|
|
251
251
|
|
|
252
|
-
return { options, updates, factMaps, existingMemories:
|
|
252
|
+
return { options, updates, factMaps, existingMemories: memoryDeltaInput }
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
private async extractFacts(
|
|
@@ -317,8 +317,8 @@ export class Memory {
|
|
|
317
317
|
private normalizeMemoryDeltaText(value: string, maxChars?: number): string {
|
|
318
318
|
const normalized = compactWhitespace(value)
|
|
319
319
|
if (!normalized) return ''
|
|
320
|
-
if (typeof maxChars !== 'number'
|
|
321
|
-
return
|
|
320
|
+
if (typeof maxChars !== 'number') return normalized
|
|
321
|
+
return truncateText(normalized, maxChars)
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
private tokenizeMemoryDeltaText(value: string): string[] {
|
|
@@ -442,7 +442,7 @@ export class Memory {
|
|
|
442
442
|
for (const action of plan.actions) {
|
|
443
443
|
switch (action.type) {
|
|
444
444
|
case 'add': {
|
|
445
|
-
const truncatedContent = action.text
|
|
445
|
+
const truncatedContent = truncateText(action.text, 50)
|
|
446
446
|
const metadata = { ...options.metadata, memoryCategory: action.category }
|
|
447
447
|
const hash = hashContent(action.text, options.scopeId, options.memoryType)
|
|
448
448
|
let newId: string
|
|
@@ -483,7 +483,7 @@ export class Memory {
|
|
|
483
483
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
484
484
|
})
|
|
485
485
|
idMap.set(action.refId, action.refId)
|
|
486
|
-
aiLogger.debug`Updated memory ${action.refId}: ${action.text
|
|
486
|
+
aiLogger.debug`Updated memory ${action.refId}: ${truncateText(action.text, 50)}`
|
|
487
487
|
break
|
|
488
488
|
}
|
|
489
489
|
|
package/src/db/service.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import type { ExprLike, Mutation, SurrealTransaction, Values } from 'surrealdb'
|
|
13
13
|
import type { z } from 'zod'
|
|
14
14
|
|
|
15
|
+
import { serverLogger } from '../config/logger'
|
|
15
16
|
import { withTimeout } from '../utils/async'
|
|
16
17
|
import { isRecord } from '../utils/string'
|
|
17
18
|
import type { RecordIdInput } from './record-id'
|
|
@@ -185,7 +186,8 @@ export class SurrealDBService {
|
|
|
185
186
|
|
|
186
187
|
try {
|
|
187
188
|
await this.client.close()
|
|
188
|
-
} catch {
|
|
189
|
+
} catch (error) {
|
|
190
|
+
serverLogger.warn`Failed to close database client: ${error}`
|
|
189
191
|
} finally {
|
|
190
192
|
this.client = null
|
|
191
193
|
}
|
|
@@ -327,14 +329,39 @@ export class SurrealDBService {
|
|
|
327
329
|
}
|
|
328
330
|
}
|
|
329
331
|
|
|
330
|
-
private normalizeQueryRows
|
|
332
|
+
private normalizeQueryRows(statement: unknown, schema?: z.ZodTypeAny): unknown[] {
|
|
331
333
|
if (Array.isArray(statement)) {
|
|
332
|
-
return schema ? statement.map((row) =>
|
|
334
|
+
return schema ? statement.map((row) => this.parseSchema(schema, row)) : (statement as unknown[])
|
|
333
335
|
}
|
|
334
336
|
if (statement === null || statement === undefined) {
|
|
335
337
|
return []
|
|
336
338
|
}
|
|
337
|
-
return schema ? [
|
|
339
|
+
return schema ? [this.parseSchema(schema, statement)] : [statement]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private normalizeParseValue(value: unknown): unknown {
|
|
343
|
+
if (
|
|
344
|
+
value instanceof Date ||
|
|
345
|
+
value instanceof RecordId ||
|
|
346
|
+
value instanceof StringRecordId ||
|
|
347
|
+
value instanceof Table
|
|
348
|
+
) {
|
|
349
|
+
return value
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (Array.isArray(value)) {
|
|
353
|
+
return value.map((entry) => this.normalizeParseValue(entry))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!isRecord(value)) {
|
|
357
|
+
return value
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, this.normalizeParseValue(entry)]))
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private parseSchema<TSchema extends z.ZodTypeAny>(schema: TSchema, value: unknown): z.infer<TSchema> {
|
|
364
|
+
return schema.parse(this.normalizeParseValue(value))
|
|
338
365
|
}
|
|
339
366
|
|
|
340
367
|
private buildFilterExpression(filter: Record<string, unknown>): ExprLike | undefined {
|
|
@@ -351,6 +378,33 @@ export class SurrealDBService {
|
|
|
351
378
|
return and(...expressions)
|
|
352
379
|
}
|
|
353
380
|
|
|
381
|
+
private assertValidIdentifier(name: string, context: string): void {
|
|
382
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(name)) {
|
|
383
|
+
throw new SurrealDBError(`Invalid ${context}: "${name}"`)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private buildBoundFilterClauses(filter: Record<string, unknown>): {
|
|
388
|
+
clause: string
|
|
389
|
+
bindings: Record<string, unknown>
|
|
390
|
+
} {
|
|
391
|
+
const entries = Object.entries(filter)
|
|
392
|
+
if (entries.length === 0) {
|
|
393
|
+
throw new SurrealDBError('Expected a non-empty filter')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const bindings: Record<string, unknown> = {}
|
|
397
|
+
const clauses = entries.map(([field, value], index) => {
|
|
398
|
+
this.assertValidIdentifier(field, 'filter field')
|
|
399
|
+
|
|
400
|
+
const bindingKey = `filter_${index}`
|
|
401
|
+
bindings[bindingKey] = this.normalizeRuntimeValue(value)
|
|
402
|
+
return `${field} = $${bindingKey}`
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
return { clause: clauses.join(' AND '), bindings }
|
|
406
|
+
}
|
|
407
|
+
|
|
354
408
|
private normalizeBoundQuery(query: BoundQuery | BoundQueryLike): BoundQuery {
|
|
355
409
|
if (!(query instanceof BoundQuery) && !isBoundQueryLike(query)) {
|
|
356
410
|
throw new SurrealDBError('Invalid query object: expected a BoundQuery-like value')
|
|
@@ -385,7 +439,7 @@ export class SurrealDBService {
|
|
|
385
439
|
return value
|
|
386
440
|
}
|
|
387
441
|
|
|
388
|
-
if ('tb' in value && 'id' in value) {
|
|
442
|
+
if ('tb' in value && 'id' in value && Object.keys(value).length === 2) {
|
|
389
443
|
return ensureRecordId(value as RecordIdInput)
|
|
390
444
|
}
|
|
391
445
|
|
|
@@ -412,25 +466,19 @@ export class SurrealDBService {
|
|
|
412
466
|
return this.normalizeRuntimeValue(bindings) as Record<string, unknown>
|
|
413
467
|
}
|
|
414
468
|
|
|
469
|
+
private normalizeMutationFieldValue(value: unknown): unknown {
|
|
470
|
+
if (value === null || value === undefined) {
|
|
471
|
+
return undefined
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return this.normalizeRuntimeValue(value)
|
|
475
|
+
}
|
|
476
|
+
|
|
415
477
|
// Cast is safe: normalizeRuntimeValue preserves Record shape when input is a Record
|
|
416
478
|
private normalizeMutationData(data: Record<string, unknown>): Record<string, unknown> {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* SurrealDB v3 `option<T>` fields expect `NONE` (key omitted), not `null`.
|
|
423
|
-
* Strip top-level `null` values so they are omitted from the serialized payload,
|
|
424
|
-
* which the driver interprets as NONE.
|
|
425
|
-
*/
|
|
426
|
-
private stripNullValues(data: Record<string, unknown>): Record<string, unknown> {
|
|
427
|
-
const result: Record<string, unknown> = {}
|
|
428
|
-
for (const [key, value] of Object.entries(data)) {
|
|
429
|
-
if (value !== null) {
|
|
430
|
-
result[key] = value
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return result
|
|
479
|
+
return Object.fromEntries(
|
|
480
|
+
Object.entries(data).map(([key, value]) => [key, this.normalizeMutationFieldValue(value)]),
|
|
481
|
+
) as Record<string, unknown>
|
|
434
482
|
}
|
|
435
483
|
|
|
436
484
|
private normalizeTableValue(value: unknown): Table {
|
|
@@ -461,7 +509,7 @@ export class SurrealDBService {
|
|
|
461
509
|
|
|
462
510
|
if (value && typeof value === 'object') {
|
|
463
511
|
const record = value as { tb?: unknown; id?: unknown }
|
|
464
|
-
if (typeof record.tb === 'string' && record.id !== undefined) {
|
|
512
|
+
if (typeof record.tb === 'string' && record.id !== undefined && Object.keys(value).length === 2) {
|
|
465
513
|
return true
|
|
466
514
|
}
|
|
467
515
|
|
|
@@ -487,20 +535,38 @@ export class SurrealDBService {
|
|
|
487
535
|
content: (data) => this.wrapMutationBuilder(builder.content(this.normalizeMutationData(data))),
|
|
488
536
|
replace: (data) => this.wrapMutationBuilder(builder.replace(this.normalizeMutationData(data))),
|
|
489
537
|
merge: (data) => this.wrapMutationBuilder(builder.merge(this.normalizeMutationData(data))),
|
|
490
|
-
output: (mode) => builder.output(mode),
|
|
538
|
+
output: async (mode) => this.normalizeParseValue(await builder.output(mode)),
|
|
491
539
|
}
|
|
492
540
|
}
|
|
493
541
|
|
|
494
542
|
private wrapCreateBuilder(builder: CreateMutationBuilder): CreateMutationBuilder {
|
|
495
543
|
return {
|
|
496
544
|
content: (data) => this.wrapCreateBuilder(builder.content(this.normalizeMutationData(data))),
|
|
497
|
-
output: (mode) => builder.output(mode),
|
|
545
|
+
output: async (mode) => this.normalizeParseValue(await builder.output(mode)),
|
|
498
546
|
}
|
|
499
547
|
}
|
|
500
548
|
|
|
501
549
|
private wrapTransaction(tx: SurrealTransaction): DatabaseTransaction {
|
|
502
550
|
return {
|
|
503
|
-
query: (query: unknown) =>
|
|
551
|
+
query: async (query: unknown) => {
|
|
552
|
+
const boundQuery = this.normalizeBoundQuery(query as BoundQuery | BoundQueryLike)
|
|
553
|
+
const queryText = this.resolveQueryText(boundQuery)
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const responses = await tx.query(boundQuery).responses()
|
|
557
|
+
const first = responses.at(0)
|
|
558
|
+
if (!first) {
|
|
559
|
+
return []
|
|
560
|
+
}
|
|
561
|
+
if (!first.success) {
|
|
562
|
+
throw new SurrealDBError(first.error.message, queryText, { cause: first.error })
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return this.normalizeQueryRows(first.result)
|
|
566
|
+
} catch (error) {
|
|
567
|
+
return this.toSurrealError(error, queryText)
|
|
568
|
+
}
|
|
569
|
+
},
|
|
504
570
|
create: (target: unknown) => {
|
|
505
571
|
const normalizedTarget = this.normalizeCreateTarget(target)
|
|
506
572
|
const builder = normalizedTarget instanceof Table ? tx.create(normalizedTarget) : tx.create(normalizedTarget)
|
|
@@ -510,15 +576,18 @@ export class SurrealDBService {
|
|
|
510
576
|
update: (target: unknown) =>
|
|
511
577
|
// Cast needed: SurrealDB SDK transaction builder type differs nominally from internal MutationBuilder interface
|
|
512
578
|
this.wrapMutationBuilder(tx.update(ensureRecordId(target as RecordIdInput)) as unknown as MutationBuilder),
|
|
513
|
-
delete: (target: unknown) =>
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
579
|
+
delete: async (target: unknown) =>
|
|
580
|
+
this.normalizeParseValue(await tx.delete(ensureRecordId(target as RecordIdInput))),
|
|
581
|
+
relate: async (from: unknown, edgeTable: unknown, to: unknown, data?: Values<Record<string, unknown>>) =>
|
|
582
|
+
this.normalizeParseValue(
|
|
583
|
+
await tx.relate(
|
|
584
|
+
ensureRecordId(from as RecordIdInput),
|
|
585
|
+
this.normalizeTableValue(edgeTable),
|
|
586
|
+
ensureRecordId(to as RecordIdInput),
|
|
587
|
+
data
|
|
588
|
+
? (this.normalizeMutationData(data as Record<string, unknown>) as Values<Record<string, unknown>>)
|
|
589
|
+
: undefined,
|
|
590
|
+
),
|
|
522
591
|
),
|
|
523
592
|
commit: () => tx.commit(),
|
|
524
593
|
cancel: () => tx.cancel(),
|
|
@@ -534,7 +603,7 @@ export class SurrealDBService {
|
|
|
534
603
|
return statements.at(0) ?? []
|
|
535
604
|
}
|
|
536
605
|
|
|
537
|
-
async queryAll<T>(query: BoundQuery | BoundQueryLike, schema?: z.
|
|
606
|
+
async queryAll<T>(query: BoundQuery | BoundQueryLike, schema?: z.ZodTypeAny): Promise<T[][]> {
|
|
538
607
|
const client = await this.ensureConnected()
|
|
539
608
|
const boundQuery = this.normalizeBoundQuery(query)
|
|
540
609
|
const queryText = this.resolveQueryText(boundQuery)
|
|
@@ -547,22 +616,22 @@ export class SurrealDBService {
|
|
|
547
616
|
const failure = response.error
|
|
548
617
|
throw new SurrealDBError(`Statement ${index + 1}: ${failure.message}`, queryText, { cause: failure })
|
|
549
618
|
}
|
|
550
|
-
return this.normalizeQueryRows
|
|
619
|
+
return this.normalizeQueryRows(response.result, schema) as T[]
|
|
551
620
|
})
|
|
552
621
|
} catch (error) {
|
|
553
622
|
return this.toSurrealError(error, queryText)
|
|
554
623
|
}
|
|
555
624
|
}
|
|
556
625
|
|
|
557
|
-
async queryOne<T extends z.
|
|
626
|
+
async queryOne<T extends z.ZodTypeAny>(query: BoundQuery | BoundQueryLike, schema: T): Promise<z.infer<T> | null> {
|
|
558
627
|
const results = await this.query<unknown>(query)
|
|
559
628
|
const first = results.at(0)
|
|
560
|
-
return first ?
|
|
629
|
+
return first ? this.parseSchema(schema, first) : null
|
|
561
630
|
}
|
|
562
631
|
|
|
563
|
-
async queryMany<T extends z.
|
|
632
|
+
async queryMany<T extends z.ZodTypeAny>(query: BoundQuery | BoundQueryLike, schema: T): Promise<z.infer<T>[]> {
|
|
564
633
|
const results = await this.query<unknown>(query)
|
|
565
|
-
return results.map((row) =>
|
|
634
|
+
return results.map((row) => this.parseSchema(schema, row))
|
|
566
635
|
}
|
|
567
636
|
|
|
568
637
|
private async runSqlFile(file: Bun.BunFile): Promise<void> {
|
|
@@ -574,13 +643,13 @@ export class SurrealDBService {
|
|
|
574
643
|
await this.queryAll<unknown>(new BoundQuery(sql))
|
|
575
644
|
}
|
|
576
645
|
|
|
577
|
-
async
|
|
646
|
+
async applySchema(schemaFiles: readonly Bun.BunFile[]): Promise<void> {
|
|
578
647
|
for (const schemaFile of schemaFiles) {
|
|
579
648
|
await this.runSqlFile(schemaFile)
|
|
580
649
|
}
|
|
581
650
|
}
|
|
582
651
|
|
|
583
|
-
async findOne<T extends z.
|
|
652
|
+
async findOne<T extends z.ZodTypeAny>(
|
|
584
653
|
table: DatabaseTable,
|
|
585
654
|
filter: Record<string, unknown>,
|
|
586
655
|
schema: T,
|
|
@@ -596,13 +665,13 @@ export class SurrealDBService {
|
|
|
596
665
|
|
|
597
666
|
const rows = await query.limit(1)
|
|
598
667
|
const first = rows.at(0)
|
|
599
|
-
return first ?
|
|
668
|
+
return first ? this.parseSchema(schema, first) : null
|
|
600
669
|
} catch (error) {
|
|
601
670
|
return this.toSurrealError(error, `SELECT * FROM ${table} LIMIT 1`)
|
|
602
671
|
}
|
|
603
672
|
}
|
|
604
673
|
|
|
605
|
-
async findMany<T extends z.
|
|
674
|
+
async findMany<T extends z.ZodTypeAny>(
|
|
606
675
|
table: DatabaseTable,
|
|
607
676
|
filter: Record<string, unknown>,
|
|
608
677
|
schema: T,
|
|
@@ -614,8 +683,15 @@ export class SurrealDBService {
|
|
|
614
683
|
const selection = this.buildFilterExpression(filter)
|
|
615
684
|
const orderBy = options?.orderBy
|
|
616
685
|
|
|
617
|
-
if (
|
|
618
|
-
|
|
686
|
+
if (orderBy !== undefined) {
|
|
687
|
+
this.assertValidIdentifier(orderBy, 'orderBy field')
|
|
688
|
+
this.assertValidIdentifier(table, 'table name')
|
|
689
|
+
for (const key of filterKeys) {
|
|
690
|
+
this.assertValidIdentifier(key, 'filter field')
|
|
691
|
+
}
|
|
692
|
+
const orderDir = options?.orderDir ?? 'ASC'
|
|
693
|
+
const limit = options?.limit
|
|
694
|
+
const offset = options?.offset
|
|
619
695
|
const vars: Record<string, unknown> = this.normalizeMutationData(filter)
|
|
620
696
|
let sql = `SELECT * FROM ${table}`
|
|
621
697
|
if (filterKeys.length > 0) {
|
|
@@ -632,7 +708,7 @@ export class SurrealDBService {
|
|
|
632
708
|
vars.offsetParam = offset
|
|
633
709
|
}
|
|
634
710
|
const rows = await this.query<unknown>(new BoundQuery(sql, vars))
|
|
635
|
-
return rows.map((row) =>
|
|
711
|
+
return rows.map((row) => this.parseSchema(schema, row))
|
|
636
712
|
}
|
|
637
713
|
|
|
638
714
|
try {
|
|
@@ -648,13 +724,13 @@ export class SurrealDBService {
|
|
|
648
724
|
}
|
|
649
725
|
|
|
650
726
|
const rows = await query
|
|
651
|
-
return rows.map((row) =>
|
|
727
|
+
return rows.map((row) => this.parseSchema(schema, row))
|
|
652
728
|
} catch (error) {
|
|
653
729
|
return this.toSurrealError(error, `SELECT * FROM ${table}`)
|
|
654
730
|
}
|
|
655
731
|
}
|
|
656
732
|
|
|
657
|
-
async create<T extends z.
|
|
733
|
+
async create<T extends z.ZodTypeAny>(
|
|
658
734
|
table: DatabaseTable,
|
|
659
735
|
data: Record<string, unknown>,
|
|
660
736
|
schema: T,
|
|
@@ -677,13 +753,13 @@ export class SurrealDBService {
|
|
|
677
753
|
throw new SurrealDBError(`Failed to create record in ${table}`)
|
|
678
754
|
}
|
|
679
755
|
|
|
680
|
-
return
|
|
756
|
+
return this.parseSchema(schema, first)
|
|
681
757
|
} catch (error) {
|
|
682
758
|
return this.toSurrealError(error, `CREATE ${table}`)
|
|
683
759
|
}
|
|
684
760
|
}
|
|
685
761
|
|
|
686
|
-
async createWithId<T extends z.
|
|
762
|
+
async createWithId<T extends z.ZodTypeAny>(
|
|
687
763
|
table: DatabaseTable,
|
|
688
764
|
id: unknown,
|
|
689
765
|
data: Record<string, unknown>,
|
|
@@ -699,13 +775,13 @@ export class SurrealDBService {
|
|
|
699
775
|
|
|
700
776
|
try {
|
|
701
777
|
const created = await client.create<unknown>(recordId).content(this.normalizeMutationData(data)).output('after')
|
|
702
|
-
return
|
|
778
|
+
return this.parseSchema(schema, created)
|
|
703
779
|
} catch (error) {
|
|
704
780
|
return this.toSurrealError(error, `CREATE ${recordId.toString()}`)
|
|
705
781
|
}
|
|
706
782
|
}
|
|
707
783
|
|
|
708
|
-
async update<T extends z.
|
|
784
|
+
async update<T extends z.ZodTypeAny>(
|
|
709
785
|
table: DatabaseTable,
|
|
710
786
|
id: unknown,
|
|
711
787
|
data: Record<string, unknown>,
|
|
@@ -726,13 +802,13 @@ export class SurrealDBService {
|
|
|
726
802
|
const builder = client.update<unknown>(recordId)
|
|
727
803
|
const configured = configureMutation(builder, mutation, this.normalizeMutationData(data))
|
|
728
804
|
const updated = await configured.output('after')
|
|
729
|
-
return updated ?
|
|
805
|
+
return updated ? this.parseSchema(schema, updated) : null
|
|
730
806
|
} catch (error) {
|
|
731
807
|
return this.toSurrealError(error, `UPDATE ${recordId.toString()}`)
|
|
732
808
|
}
|
|
733
809
|
}
|
|
734
810
|
|
|
735
|
-
async upsert<T extends z.
|
|
811
|
+
async upsert<T extends z.ZodTypeAny>(
|
|
736
812
|
table: DatabaseTable,
|
|
737
813
|
id: unknown,
|
|
738
814
|
data: Record<string, unknown>,
|
|
@@ -755,7 +831,7 @@ export class SurrealDBService {
|
|
|
755
831
|
if (!upserted) {
|
|
756
832
|
throw new SurrealDBError(`Failed to upsert record in ${table}`)
|
|
757
833
|
}
|
|
758
|
-
return
|
|
834
|
+
return this.parseSchema(schema, upserted)
|
|
759
835
|
} catch (error) {
|
|
760
836
|
return this.toSurrealError(error, `UPSERT ${recordId.toString()}`)
|
|
761
837
|
}
|
|
@@ -763,45 +839,42 @@ export class SurrealDBService {
|
|
|
763
839
|
|
|
764
840
|
async deleteById(table: DatabaseTable, id: unknown): Promise<boolean> {
|
|
765
841
|
const recordId = this.normalizeRecordId(id, table)
|
|
766
|
-
const client = await this.ensureConnected()
|
|
767
842
|
|
|
768
843
|
try {
|
|
769
|
-
const
|
|
770
|
-
|
|
844
|
+
const result = await this.query<unknown>(new BoundQuery(`DELETE $recordId RETURN BEFORE`, { recordId }))
|
|
845
|
+
return result.length > 0
|
|
846
|
+
} catch (error) {
|
|
847
|
+
if (error instanceof Error && error.message.includes('does not exist')) {
|
|
771
848
|
return false
|
|
772
849
|
}
|
|
773
|
-
await client.delete<unknown>(recordId).output('before')
|
|
774
|
-
return true
|
|
775
|
-
} catch (error) {
|
|
776
850
|
return this.toSurrealError(error, `DELETE ${recordId.toString()}`)
|
|
777
851
|
}
|
|
778
852
|
}
|
|
779
853
|
|
|
780
854
|
async deleteWhere(table: DatabaseTable, filter: Record<string, unknown>): Promise<number> {
|
|
855
|
+
this.assertValidIdentifier(table, 'table name')
|
|
781
856
|
const filterKeys = Object.keys(filter)
|
|
782
857
|
if (filterKeys.length === 0) {
|
|
783
858
|
throw new SurrealDBError(`Refusing to delete all records in ${table} without a filter`)
|
|
784
859
|
}
|
|
785
|
-
|
|
786
|
-
const selection = this.buildFilterExpression(filter)
|
|
787
|
-
if (!selection) {
|
|
788
|
-
throw new SurrealDBError(`Invalid delete filter for table ${table}`)
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
const client = await this.ensureConnected()
|
|
860
|
+
const { clause, bindings } = this.buildBoundFilterClauses(filter)
|
|
792
861
|
|
|
793
862
|
try {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
863
|
+
return await this.withTransaction(async (tx) => {
|
|
864
|
+
const matched = (await tx.query(new BoundQuery(`SELECT id FROM ${table} WHERE ${clause}`, bindings))) as Array<{
|
|
865
|
+
id: unknown
|
|
866
|
+
}>
|
|
798
867
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
868
|
+
if (matched.length === 0) {
|
|
869
|
+
return 0
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
for (const row of matched) {
|
|
873
|
+
await tx.delete(this.normalizeRecordId(row.id, table))
|
|
874
|
+
}
|
|
803
875
|
|
|
804
|
-
|
|
876
|
+
return matched.length
|
|
877
|
+
})
|
|
805
878
|
} catch (error) {
|
|
806
879
|
return this.toSurrealError(error, `DELETE ${table} WHERE ...`)
|
|
807
880
|
}
|
|
@@ -939,6 +1012,7 @@ export const databaseService = new Proxy({} as SurrealDBService, {
|
|
|
939
1012
|
const resolved = resolveConfiguredDatabaseService()
|
|
940
1013
|
const value: unknown = Reflect.get(resolved, property)
|
|
941
1014
|
if (typeof value === 'function') {
|
|
1015
|
+
// SurrealDB SDK type gap — Reflect.get returns unknown, but we know the resolved target shape
|
|
942
1016
|
return bindTargetMethod(
|
|
943
1017
|
resolved as unknown as Record<PropertyKey, unknown>,
|
|
944
1018
|
value as (...args: unknown[]) => unknown,
|
package/src/db/startup.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordIdSchema } from '@lota-sdk/shared'
|
|
1
2
|
import { BoundQuery, RecordId } from 'surrealdb'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
|
|
@@ -11,11 +12,11 @@ const DEFAULT_MAX_WAIT_MS = 3 * 60 * 1_000
|
|
|
11
12
|
const RETRY_LOG_INTERVAL = 5
|
|
12
13
|
|
|
13
14
|
const RuntimeBootstrapRecordSchema = z.object({
|
|
14
|
-
id:
|
|
15
|
+
id: recordIdSchema,
|
|
15
16
|
key: z.string(),
|
|
16
17
|
schemaFingerprint: z.string(),
|
|
17
|
-
readyAt: z.
|
|
18
|
-
updatedAt: z.
|
|
18
|
+
readyAt: z.coerce.date(),
|
|
19
|
+
updatedAt: z.coerce.date().optional().nullable(),
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
type StartupLogger = Pick<SurrealDatabaseLogger, 'info' | 'warn' | 'error'>
|
|
@@ -62,7 +63,7 @@ export async function connectWithStartupRetry(params: {
|
|
|
62
63
|
async function readDatabaseBootstrapRecord(
|
|
63
64
|
databaseService: SurrealDBService,
|
|
64
65
|
): Promise<z.infer<typeof RuntimeBootstrapRecordSchema> | null> {
|
|
65
|
-
return
|
|
66
|
+
return databaseService.queryOne(
|
|
66
67
|
new BoundQuery(
|
|
67
68
|
`SELECT *
|
|
68
69
|
FROM ${TABLES.RUNTIME_BOOTSTRAP}
|
|
@@ -141,12 +142,7 @@ export async function publishDatabaseBootstrap(params: {
|
|
|
141
142
|
await params.databaseService.upsert(
|
|
142
143
|
TABLES.RUNTIME_BOOTSTRAP,
|
|
143
144
|
new RecordId(TABLES.RUNTIME_BOOTSTRAP, DATABASE_BOOTSTRAP_KEY),
|
|
144
|
-
{
|
|
145
|
-
key: DATABASE_BOOTSTRAP_KEY,
|
|
146
|
-
schemaFingerprint: params.schemaFingerprint,
|
|
147
|
-
readyAt: new Date(),
|
|
148
|
-
updatedAt: new Date(),
|
|
149
|
-
},
|
|
145
|
+
{ key: DATABASE_BOOTSTRAP_KEY, schemaFingerprint: params.schemaFingerprint, readyAt: new Date() },
|
|
150
146
|
RuntimeBootstrapRecordSchema,
|
|
151
147
|
)
|
|
152
148
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type SurrealSetClause = { bindingEntries: Record<string, unknown>; statement: string }
|
|
2
|
+
|
|
3
|
+
export function buildRequiredSurrealSetClause(field: string, bindingName: string, value: unknown): SurrealSetClause {
|
|
4
|
+
return { statement: `${field} = $${bindingName}`, bindingEntries: { [bindingName]: value } }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function buildOptionalSurrealSetClause(field: string, value: unknown): SurrealSetClause
|
|
8
|
+
export function buildOptionalSurrealSetClause(field: string, bindingName: string, value: unknown): SurrealSetClause
|
|
9
|
+
export function buildOptionalSurrealSetClause(
|
|
10
|
+
field: string,
|
|
11
|
+
bindingNameOrValue: unknown,
|
|
12
|
+
value?: unknown,
|
|
13
|
+
): SurrealSetClause {
|
|
14
|
+
const hasExplicitBinding = arguments.length === 3
|
|
15
|
+
const resolvedValue = hasExplicitBinding ? value : bindingNameOrValue
|
|
16
|
+
const resolvedBindingName = hasExplicitBinding
|
|
17
|
+
? (bindingNameOrValue as string)
|
|
18
|
+
: `resource_${field.replace(/[^a-zA-Z0-9]+/g, '_')}`
|
|
19
|
+
|
|
20
|
+
if (resolvedValue === null || resolvedValue === undefined) {
|
|
21
|
+
return { statement: `${field} = NONE`, bindingEntries: {} }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return buildRequiredSurrealSetClause(field, resolvedBindingName, resolvedValue)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function combineSurrealSetClauses(...clauses: SurrealSetClause[]): {
|
|
28
|
+
bindings: Record<string, unknown>
|
|
29
|
+
statement: string
|
|
30
|
+
} {
|
|
31
|
+
const bindings: Record<string, unknown> = {}
|
|
32
|
+
for (const clause of clauses) {
|
|
33
|
+
for (const [key, value] of Object.entries(clause.bindingEntries)) {
|
|
34
|
+
bindings[key] = value
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { bindings, statement: clauses.map((clause) => clause.statement).join(', ') }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function compactDefinedRecord<T extends Record<string, unknown>>(value: T): Partial<T> {
|
|
42
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as Partial<T>
|
|
43
|
+
}
|
package/src/db/tables.ts
CHANGED
|
@@ -17,11 +17,18 @@ export const TABLES = {
|
|
|
17
17
|
PLAN_CHECKPOINT: 'planCheckpoint',
|
|
18
18
|
PLAN_VALIDATION_ISSUE: 'planValidationIssue',
|
|
19
19
|
PLAN_EVENT: 'planEvent',
|
|
20
|
+
PLAN_SCHEDULE: 'planSchedule',
|
|
21
|
+
PLAN_TEMPLATE: 'planTemplate',
|
|
22
|
+
PLAN_CYCLE: 'planCycle',
|
|
20
23
|
ORGANIZATION: 'organization',
|
|
21
24
|
ORGANIZATION_MEMBER: 'organizationMember',
|
|
22
25
|
USER: 'user',
|
|
23
26
|
RECENT_ACTIVITY_EVENT: 'recentActivityEvent',
|
|
24
27
|
RECENT_ACTIVITY: 'recentActivity',
|
|
28
|
+
PLAYBOOK: 'playbook',
|
|
29
|
+
PLAYBOOK_VERSION: 'playbookVersion',
|
|
30
|
+
INSTITUTIONAL_MEMORY: 'institutionalMemory',
|
|
31
|
+
QUALITY_METRIC: 'qualityMetric',
|
|
25
32
|
} as const
|
|
26
33
|
|
|
27
34
|
export type DatabaseTable = (typeof TABLES)[keyof typeof TABLES] | (string & {})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { recordIdSchema } from '@lota-sdk/shared'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export const WorkstreamMessageRowSchema = z.object({
|
|
5
|
+
id: recordIdSchema,
|
|
6
|
+
workstreamId: recordIdSchema,
|
|
7
|
+
messageId: z.string(),
|
|
8
|
+
role: z.enum(['system', 'user', 'assistant']),
|
|
9
|
+
parts: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
10
|
+
metadata: z.record(z.string(), z.unknown()).nullish(),
|
|
11
|
+
createdAt: z.coerce.date(),
|
|
12
|
+
updatedAt: z.coerce.date().optional(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export type WorkstreamMessageRow = z.infer<typeof WorkstreamMessageRowSchema>
|