@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/create-runtime.ts
CHANGED
|
@@ -15,9 +15,11 @@ import { TABLES } from './db/tables'
|
|
|
15
15
|
import type { RedisConnectionManager } from './redis/connection'
|
|
16
16
|
import { createRedisConnectionManager } from './redis/connection'
|
|
17
17
|
import { setRedisConnectionManager } from './redis/index'
|
|
18
|
+
import { closeSharedSubscriber } from './redis/stream-context'
|
|
18
19
|
import type { isApprovalContinuationRequest } from './runtime/approval-continuation'
|
|
19
20
|
import { routeWorkstreamChatMessages } from './runtime/chat-request-routing'
|
|
20
|
-
import
|
|
21
|
+
import { configureGraphDesigner } from './runtime/graph-designer'
|
|
22
|
+
import type { LotaPlugin, SystemNodeExecutor } from './runtime/plugin-types'
|
|
21
23
|
import { configureRuntimeConfig, LOTA_RUNTIME_ENV_KEYS, parseLotaRuntimeConfig } from './runtime/runtime-config'
|
|
22
24
|
import type { LotaRuntimeConfig, ResolvedLotaRuntimeConfig } from './runtime/runtime-config'
|
|
23
25
|
import { configureRuntimeExtensions } from './runtime/runtime-extensions'
|
|
@@ -25,22 +27,27 @@ import type { LotaRuntimeWorkers } from './runtime/runtime-worker-registry'
|
|
|
25
27
|
import { buildRuntimeWorkerRegistry } from './runtime/runtime-worker-registry'
|
|
26
28
|
import type { attachmentService } from './services/attachment.service'
|
|
27
29
|
import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
|
|
30
|
+
import { coordinationRegistryService as coordinationRegistryServiceSingleton } from './services/coordination-registry.service'
|
|
28
31
|
import type { documentChunkService } from './services/document-chunk.service'
|
|
29
32
|
import { documentChunkService as documentChunkServiceSingleton } from './services/document-chunk.service'
|
|
33
|
+
import { domainAgentExecutorService } from './services/domain-agent-executor.service'
|
|
30
34
|
import type { executionPlanService } from './services/execution-plan.service'
|
|
31
35
|
import { executionPlanService as executionPlanServiceSingleton } from './services/execution-plan.service'
|
|
32
36
|
import type { memoryService } from './services/memory.service'
|
|
33
37
|
import { memoryService as memoryServiceSingleton } from './services/memory.service'
|
|
34
38
|
import type { verifyMutatingApproval } from './services/mutating-approval.service'
|
|
35
39
|
import { verifyMutatingApproval as verifyMutatingApprovalSingleton } from './services/mutating-approval.service'
|
|
40
|
+
import { configureNotificationService } from './services/notification.service'
|
|
36
41
|
import type { organizationMemberService } from './services/organization-member.service'
|
|
37
42
|
import { organizationMemberService as organizationMemberServiceSingleton } from './services/organization-member.service'
|
|
38
43
|
import type { organizationService } from './services/organization.service'
|
|
39
44
|
import { organizationService as organizationServiceSingleton } from './services/organization.service'
|
|
45
|
+
import { playbookRegistryService } from './services/playbook-registry.service'
|
|
40
46
|
import type { recentActivityTitleService } from './services/recent-activity-title.service'
|
|
41
47
|
import { recentActivityTitleService as recentActivityTitleServiceSingleton } from './services/recent-activity-title.service'
|
|
42
48
|
import type { recentActivityService } from './services/recent-activity.service'
|
|
43
49
|
import { recentActivityService as recentActivityServiceSingleton } from './services/recent-activity.service'
|
|
50
|
+
import { getBuiltInSystemExecutors } from './services/system-executor.service'
|
|
44
51
|
import type { userService } from './services/user.service'
|
|
45
52
|
import { userService as userServiceSingleton } from './services/user.service'
|
|
46
53
|
import type { workstreamMessageService } from './services/workstream-message.service'
|
|
@@ -75,6 +82,24 @@ type UnarchiveSdkWorkstream = (
|
|
|
75
82
|
status?: 'regular',
|
|
76
83
|
) => ReturnType<typeof workstreamServiceSingleton.updateStatus>
|
|
77
84
|
|
|
85
|
+
let activeRuntimeToken: symbol | null = null
|
|
86
|
+
|
|
87
|
+
function claimRuntimeToken(): symbol {
|
|
88
|
+
if (activeRuntimeToken) {
|
|
89
|
+
throw new Error('createLotaRuntime() is process-scoped. Disconnect the active runtime before creating another one.')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const token = Symbol('lota-runtime')
|
|
93
|
+
activeRuntimeToken = token
|
|
94
|
+
return token
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function releaseRuntimeToken(token: symbol) {
|
|
98
|
+
if (activeRuntimeToken === token) {
|
|
99
|
+
activeRuntimeToken = null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
78
103
|
export interface LotaRuntime {
|
|
79
104
|
services: {
|
|
80
105
|
database: SurrealDBService
|
|
@@ -99,6 +124,7 @@ export interface LotaRuntime {
|
|
|
99
124
|
createWorkstreamTurnStream: typeof createWorkstreamTurnStream
|
|
100
125
|
isApprovalContinuationRequest: typeof isApprovalContinuationRequest
|
|
101
126
|
runWorkstreamTurnInBackground: typeof runWorkstreamTurnInBackground
|
|
127
|
+
syncPlaybookTemplates: typeof playbookRegistryService.syncPlaybookTemplates
|
|
102
128
|
}
|
|
103
129
|
lota: {
|
|
104
130
|
organizations: {
|
|
@@ -162,201 +188,241 @@ export interface LotaRuntime {
|
|
|
162
188
|
contributions: { envKeys: readonly string[]; schemaFiles: Array<string | URL> }
|
|
163
189
|
config: ResolvedLotaRuntimeConfig
|
|
164
190
|
plugins: Record<string, LotaPlugin>
|
|
191
|
+
systemExecutors: Record<string, SystemNodeExecutor>
|
|
165
192
|
connectPluginDatabases(): Promise<void>
|
|
166
193
|
connect(): Promise<void>
|
|
167
194
|
disconnect(): Promise<void>
|
|
168
195
|
}
|
|
169
196
|
|
|
170
197
|
export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
|
|
171
|
-
const
|
|
172
|
-
configureRuntimeConfig(resolvedConfig)
|
|
198
|
+
const runtimeToken = claimRuntimeToken()
|
|
173
199
|
|
|
174
|
-
|
|
200
|
+
try {
|
|
201
|
+
const resolvedConfig = parseLotaRuntimeConfig(config)
|
|
202
|
+
const systemExecutors = { ...getBuiltInSystemExecutors(), ...resolvedConfig.systemExecutors }
|
|
203
|
+
const runtimeConfig = { ...resolvedConfig, systemExecutors } satisfies ResolvedLotaRuntimeConfig
|
|
204
|
+
configureRuntimeConfig(runtimeConfig)
|
|
175
205
|
|
|
176
|
-
|
|
177
|
-
url: resolvedConfig.database.url,
|
|
178
|
-
namespace: resolvedConfig.database.namespace,
|
|
179
|
-
database: LOTA_SDK_DATABASE_NAME,
|
|
180
|
-
username: resolvedConfig.database.username,
|
|
181
|
-
password: resolvedConfig.database.password,
|
|
182
|
-
})
|
|
183
|
-
setDatabaseService(db)
|
|
206
|
+
await configureLotaLogger(runtimeConfig.logging.level)
|
|
184
207
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
208
|
+
const db = new SurrealDBServiceClass({
|
|
209
|
+
url: runtimeConfig.database.url,
|
|
210
|
+
namespace: runtimeConfig.database.namespace,
|
|
211
|
+
database: LOTA_SDK_DATABASE_NAME,
|
|
212
|
+
username: runtimeConfig.database.username,
|
|
213
|
+
password: runtimeConfig.database.password,
|
|
214
|
+
})
|
|
215
|
+
setDatabaseService(db)
|
|
189
216
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
shortDisplayNames: resolvedConfig.agents.shortDisplayNames,
|
|
195
|
-
teamConsultParticipants: resolvedConfig.agents.teamConsultParticipants,
|
|
196
|
-
getCoreWorkstreamProfile: resolvedConfig.agents.getCoreWorkstreamProfile,
|
|
197
|
-
})
|
|
198
|
-
configureAgentFactory({
|
|
199
|
-
createAgent: resolvedConfig.agents.createAgent,
|
|
200
|
-
buildAgentTools: resolvedConfig.agents.buildAgentTools,
|
|
201
|
-
getAgentRuntimeConfig: resolvedConfig.agents.getAgentRuntimeConfig,
|
|
202
|
-
pluginRuntime: resolvedConfig.pluginRuntime,
|
|
203
|
-
})
|
|
204
|
-
configureWorkstreams({ agentRoster: resolvedConfig.agents.roster, config: resolvedConfig.workstreams })
|
|
205
|
-
configureRuntimeExtensions({
|
|
206
|
-
adapters: resolvedConfig.runtimeAdapters,
|
|
207
|
-
turnHooks: resolvedConfig.turnHooks,
|
|
208
|
-
toolProviders: (resolvedConfig.toolProviders ?? {}) as never,
|
|
209
|
-
extraWorkers: resolvedConfig.extraWorkers,
|
|
210
|
-
})
|
|
217
|
+
const redisManager = createRedisConnectionManager({ url: runtimeConfig.redis.url })
|
|
218
|
+
setRedisConnectionManager(redisManager)
|
|
219
|
+
configureEmbeddingCache(redisManager.getConnection(), runtimeConfig.memory.embeddingCacheTtlSeconds)
|
|
220
|
+
configureBackgroundProcessing(runtimeConfig.backgroundProcessing)
|
|
211
221
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
222
|
+
configureAgents({
|
|
223
|
+
roster: runtimeConfig.agents.roster,
|
|
224
|
+
leadAgentId: runtimeConfig.agents.leadAgentId,
|
|
225
|
+
displayNames: runtimeConfig.agents.displayNames,
|
|
226
|
+
shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
|
|
227
|
+
teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
|
|
228
|
+
getCoreWorkstreamProfile: runtimeConfig.agents.getCoreWorkstreamProfile,
|
|
229
|
+
})
|
|
230
|
+
configureAgentFactory({
|
|
231
|
+
createAgent: runtimeConfig.agents.createAgent,
|
|
232
|
+
buildAgentTools: runtimeConfig.agents.buildAgentTools,
|
|
233
|
+
getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
|
|
234
|
+
pluginRuntime: runtimeConfig.pluginRuntime,
|
|
235
|
+
})
|
|
236
|
+
configureWorkstreams({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.workstreams })
|
|
237
|
+
configureNotificationService(runtimeConfig.notificationService ?? null)
|
|
238
|
+
configureRuntimeExtensions({
|
|
239
|
+
adapters: runtimeConfig.runtimeAdapters,
|
|
240
|
+
turnHooks: runtimeConfig.turnHooks,
|
|
241
|
+
toolProviders: (runtimeConfig.toolProviders ?? {}) as never,
|
|
242
|
+
extraWorkers: runtimeConfig.extraWorkers,
|
|
243
|
+
})
|
|
219
244
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
remove: organizationMemberServiceSingleton.removeMembership.bind(organizationMemberServiceSingleton),
|
|
243
|
-
isMember: organizationMemberServiceSingleton.isMember.bind(organizationMemberServiceSingleton),
|
|
244
|
-
},
|
|
245
|
-
workstreams: {
|
|
246
|
-
create: workstreamServiceSingleton.createWorkstream.bind(workstreamServiceSingleton),
|
|
247
|
-
list: workstreamServiceSingleton.listWorkstreams.bind(workstreamServiceSingleton),
|
|
248
|
-
get: workstreamServiceSingleton.getWorkstream.bind(workstreamServiceSingleton),
|
|
249
|
-
update: workstreamServiceSingleton.updateTitle.bind(workstreamServiceSingleton),
|
|
250
|
-
archive: async (workstreamId, status = 'archived') =>
|
|
251
|
-
await workstreamServiceSingleton.updateStatus(workstreamId, status),
|
|
252
|
-
unarchive: async (workstreamId, status = 'regular') =>
|
|
253
|
-
await workstreamServiceSingleton.updateStatus(workstreamId, status),
|
|
254
|
-
delete: workstreamServiceSingleton.deleteWorkstream.bind(workstreamServiceSingleton),
|
|
255
|
-
stop: workstreamServiceSingleton.stopActiveRun.bind(workstreamServiceSingleton),
|
|
256
|
-
listMessages: workstreamMessageServiceSingleton.listMessageHistoryPage.bind(workstreamMessageServiceSingleton),
|
|
257
|
-
getMessage: async ({ workstreamId, messageId }) => {
|
|
258
|
-
const messages = await workstreamMessageServiceSingleton.listMessages(
|
|
259
|
-
ensureRecordId(workstreamId, TABLES.WORKSTREAM),
|
|
260
|
-
)
|
|
261
|
-
const message = messages.find((candidate) => candidate.id === messageId)
|
|
262
|
-
if (!message) {
|
|
263
|
-
throw new Error(`Workstream message not found: ${messageId}`)
|
|
264
|
-
}
|
|
265
|
-
return message
|
|
266
|
-
},
|
|
267
|
-
sendMessage: async ({ workstreamId, organizationId, userId, userName, messages }) => {
|
|
268
|
-
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
269
|
-
const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
|
|
270
|
-
const routed = routeWorkstreamChatMessages(messages)
|
|
271
|
-
if (routed.kind !== 'turn') {
|
|
272
|
-
throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
|
|
273
|
-
}
|
|
245
|
+
const pluginRuntime = runtimeConfig.pluginRuntime ?? {}
|
|
246
|
+
domainAgentExecutorService.configure(pluginRuntime)
|
|
247
|
+
if (runtimeConfig.graphDesigner) {
|
|
248
|
+
configureGraphDesigner(runtimeConfig.graphDesigner)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const [pluginRef, plugin] of Object.entries(pluginRuntime)) {
|
|
252
|
+
const signals = plugin.contributions.signals
|
|
253
|
+
if (signals && signals.length > 0) {
|
|
254
|
+
coordinationRegistryServiceSingleton.register(pluginRef, [...signals])
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
coordinationRegistryServiceSingleton.validate()
|
|
258
|
+
// Collect playbook contributions early to fail fast on misconfiguration
|
|
259
|
+
playbookRegistryService.collectPlaybooks()
|
|
260
|
+
|
|
261
|
+
const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
|
|
262
|
+
const schemaFiles = [...getBuiltInSchemaFiles(), ...(runtimeConfig.extraSchemaFiles ?? [])]
|
|
263
|
+
const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
|
|
264
|
+
const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
|
|
265
|
+
const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
|
|
266
|
+
const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
|
|
274
267
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
268
|
+
const lota = {
|
|
269
|
+
organizations: {
|
|
270
|
+
create: organizationServiceSingleton.createOrganization.bind(organizationServiceSingleton),
|
|
271
|
+
upsert: organizationServiceSingleton.upsertOrganization.bind(organizationServiceSingleton),
|
|
272
|
+
get: organizationServiceSingleton.getOrganization.bind(organizationServiceSingleton),
|
|
273
|
+
list: organizationServiceSingleton.listOrganizations.bind(organizationServiceSingleton),
|
|
274
|
+
update: organizationServiceSingleton.updateOrganization.bind(organizationServiceSingleton),
|
|
275
|
+
delete: organizationServiceSingleton.deleteOrganization.bind(organizationServiceSingleton),
|
|
276
|
+
},
|
|
277
|
+
users: {
|
|
278
|
+
upsert: userServiceSingleton.upsertUser.bind(userServiceSingleton),
|
|
279
|
+
get: userServiceSingleton.getUser.bind(userServiceSingleton),
|
|
280
|
+
list: userServiceSingleton.listUsers.bind(userServiceSingleton),
|
|
281
|
+
update: userServiceSingleton.updateUser.bind(userServiceSingleton),
|
|
282
|
+
delete: userServiceSingleton.deleteUser.bind(userServiceSingleton),
|
|
283
|
+
},
|
|
284
|
+
memberships: {
|
|
285
|
+
add: organizationMemberServiceSingleton.addMembership.bind(organizationMemberServiceSingleton),
|
|
286
|
+
listForOrganization: organizationMemberServiceSingleton.listMembershipsForOrganization.bind(
|
|
287
|
+
organizationMemberServiceSingleton,
|
|
288
|
+
),
|
|
289
|
+
listForUser: organizationMemberServiceSingleton.listMembershipsForUser.bind(organizationMemberServiceSingleton),
|
|
290
|
+
remove: organizationMemberServiceSingleton.removeMembership.bind(organizationMemberServiceSingleton),
|
|
291
|
+
isMember: organizationMemberServiceSingleton.isMember.bind(organizationMemberServiceSingleton),
|
|
283
292
|
},
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
293
|
+
workstreams: {
|
|
294
|
+
create: workstreamServiceSingleton.createWorkstream.bind(workstreamServiceSingleton),
|
|
295
|
+
list: workstreamServiceSingleton.listWorkstreams.bind(workstreamServiceSingleton),
|
|
296
|
+
get: workstreamServiceSingleton.getWorkstream.bind(workstreamServiceSingleton),
|
|
297
|
+
update: workstreamServiceSingleton.updateTitle.bind(workstreamServiceSingleton),
|
|
298
|
+
archive: async (workstreamId, status = 'archived') =>
|
|
299
|
+
await workstreamServiceSingleton.updateStatus(workstreamId, status),
|
|
300
|
+
unarchive: async (workstreamId, status = 'regular') =>
|
|
301
|
+
await workstreamServiceSingleton.updateStatus(workstreamId, status),
|
|
302
|
+
delete: workstreamServiceSingleton.deleteWorkstream.bind(workstreamServiceSingleton),
|
|
303
|
+
stop: workstreamServiceSingleton.stopActiveRun.bind(workstreamServiceSingleton),
|
|
304
|
+
listMessages: workstreamMessageServiceSingleton.listMessageHistoryPage.bind(workstreamMessageServiceSingleton),
|
|
305
|
+
getMessage: async ({ workstreamId, messageId }) => {
|
|
306
|
+
const messages = await workstreamMessageServiceSingleton.listMessages(
|
|
307
|
+
ensureRecordId(workstreamId, TABLES.WORKSTREAM),
|
|
291
308
|
)
|
|
292
|
-
|
|
309
|
+
const message = messages.find((candidate) => candidate.id === messageId)
|
|
310
|
+
if (!message) {
|
|
311
|
+
throw new Error(`Workstream message not found: ${messageId}`)
|
|
312
|
+
}
|
|
313
|
+
return message
|
|
314
|
+
},
|
|
315
|
+
sendMessage: async ({ workstreamId, organizationId, userId, userName, messages }) => {
|
|
316
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
317
|
+
const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
|
|
318
|
+
const routed = routeWorkstreamChatMessages(messages)
|
|
319
|
+
if (routed.kind !== 'turn') {
|
|
320
|
+
throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return createWorkstreamTurnStreamSingleton({
|
|
324
|
+
workstream,
|
|
325
|
+
workstreamRef,
|
|
326
|
+
orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
|
|
327
|
+
userRef: ensureRecordId(userId, TABLES.USER),
|
|
328
|
+
userName,
|
|
329
|
+
inputMessage: routed.inputMessage,
|
|
330
|
+
})
|
|
331
|
+
},
|
|
332
|
+
continueApproval: async ({ workstreamId, organizationId, userId, userName, messages }) => {
|
|
333
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
334
|
+
const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
|
|
335
|
+
const routed = routeWorkstreamChatMessages(messages)
|
|
336
|
+
if (routed.kind !== 'approval-continuation') {
|
|
337
|
+
throw new Error(
|
|
338
|
+
routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return createWorkstreamApprovalContinuationStreamSingleton({
|
|
343
|
+
workstream,
|
|
344
|
+
workstreamRef,
|
|
345
|
+
orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
|
|
346
|
+
userRef: ensureRecordId(userId, TABLES.USER),
|
|
347
|
+
userName,
|
|
348
|
+
approvalMessages: routed.approvalMessages,
|
|
349
|
+
})
|
|
350
|
+
},
|
|
351
|
+
uploadAttachment: attachmentServiceSingleton.uploadWorkstreamAttachment.bind(attachmentServiceSingleton),
|
|
352
|
+
},
|
|
353
|
+
} satisfies LotaRuntime['lota']
|
|
293
354
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
355
|
+
let disconnected = false
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
services: {
|
|
359
|
+
database: db,
|
|
360
|
+
redis: redisManager,
|
|
361
|
+
closeRedisConnection: async () => await redisManager.closeConnection(),
|
|
362
|
+
attachmentService: attachmentServiceSingleton,
|
|
363
|
+
documentChunkService: documentChunkServiceSingleton,
|
|
364
|
+
generatedDocumentStorageService: generatedDocumentStorageServiceSingleton,
|
|
365
|
+
memoryService: memoryServiceSingleton,
|
|
366
|
+
verifyMutatingApproval: verifyMutatingApprovalSingleton,
|
|
367
|
+
organizationService: organizationServiceSingleton,
|
|
368
|
+
organizationMemberService: organizationMemberServiceSingleton,
|
|
369
|
+
userService: userServiceSingleton,
|
|
370
|
+
recentActivityService: recentActivityServiceSingleton,
|
|
371
|
+
recentActivityTitleService: recentActivityTitleServiceSingleton,
|
|
372
|
+
executionPlanService: executionPlanServiceSingleton,
|
|
373
|
+
workstreamMessageService: workstreamMessageServiceSingleton,
|
|
374
|
+
workstreamService: workstreamServiceSingleton,
|
|
375
|
+
workstreamTitleService: workstreamTitleServiceSingleton,
|
|
376
|
+
createWorkstreamApprovalContinuationStream: createWorkstreamApprovalContinuationStreamSingleton,
|
|
377
|
+
createWorkstreamNativeToolApprovalStream: createWorkstreamNativeToolApprovalStreamSingleton,
|
|
378
|
+
createWorkstreamTurnStream: createWorkstreamTurnStreamSingleton,
|
|
379
|
+
isApprovalContinuationRequest: isApprovalContinuationRequestSingleton,
|
|
380
|
+
runWorkstreamTurnInBackground: runWorkstreamTurnInBackgroundSingleton,
|
|
381
|
+
syncPlaybookTemplates: playbookRegistryService.syncPlaybookTemplates.bind(playbookRegistryService),
|
|
382
|
+
},
|
|
383
|
+
lota,
|
|
384
|
+
redis: {
|
|
385
|
+
manager: redisManager,
|
|
386
|
+
getConnection: () => redisManager.getConnection(),
|
|
387
|
+
getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
|
|
388
|
+
closeConnection: async () => await redisManager.closeConnection(),
|
|
389
|
+
},
|
|
390
|
+
workers,
|
|
391
|
+
schemaFiles,
|
|
392
|
+
contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
|
|
393
|
+
config: runtimeConfig,
|
|
394
|
+
plugins: pluginRuntime,
|
|
395
|
+
systemExecutors,
|
|
396
|
+
async connectPluginDatabases() {
|
|
397
|
+
await connectPluginDatabases()
|
|
398
|
+
},
|
|
399
|
+
async connect() {
|
|
400
|
+
await db.connect()
|
|
401
|
+
const bunFiles = schemaFiles.map((schemaFile) =>
|
|
402
|
+
schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
|
|
403
|
+
)
|
|
404
|
+
await db.applySchema(bunFiles)
|
|
405
|
+
const schemaFingerprint = await computeSchemaFingerprint(schemaFiles)
|
|
406
|
+
await publishDatabaseBootstrap({ databaseService: db, schemaFingerprint })
|
|
302
407
|
},
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
408
|
+
async disconnect() {
|
|
409
|
+
if (disconnected) {
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
disconnected = true
|
|
306
413
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
userService: userServiceSingleton,
|
|
320
|
-
recentActivityService: recentActivityServiceSingleton,
|
|
321
|
-
recentActivityTitleService: recentActivityTitleServiceSingleton,
|
|
322
|
-
executionPlanService: executionPlanServiceSingleton,
|
|
323
|
-
workstreamMessageService: workstreamMessageServiceSingleton,
|
|
324
|
-
workstreamService: workstreamServiceSingleton,
|
|
325
|
-
workstreamTitleService: workstreamTitleServiceSingleton,
|
|
326
|
-
createWorkstreamApprovalContinuationStream: createWorkstreamApprovalContinuationStreamSingleton,
|
|
327
|
-
createWorkstreamNativeToolApprovalStream: createWorkstreamNativeToolApprovalStreamSingleton,
|
|
328
|
-
createWorkstreamTurnStream: createWorkstreamTurnStreamSingleton,
|
|
329
|
-
isApprovalContinuationRequest: isApprovalContinuationRequestSingleton,
|
|
330
|
-
runWorkstreamTurnInBackground: runWorkstreamTurnInBackgroundSingleton,
|
|
331
|
-
},
|
|
332
|
-
lota,
|
|
333
|
-
redis: {
|
|
334
|
-
manager: redisManager,
|
|
335
|
-
getConnection: () => redisManager.getConnection(),
|
|
336
|
-
getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
|
|
337
|
-
closeConnection: async () => await redisManager.closeConnection(),
|
|
338
|
-
},
|
|
339
|
-
workers,
|
|
340
|
-
schemaFiles,
|
|
341
|
-
contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
|
|
342
|
-
config: resolvedConfig,
|
|
343
|
-
plugins: pluginRuntime,
|
|
344
|
-
async connectPluginDatabases() {
|
|
345
|
-
await connectPluginDatabases()
|
|
346
|
-
},
|
|
347
|
-
async connect() {
|
|
348
|
-
await db.connect()
|
|
349
|
-
const bunFiles = schemaFiles.map((schemaFile) =>
|
|
350
|
-
schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
|
|
351
|
-
)
|
|
352
|
-
await db.applySchemaAndMigrations(bunFiles)
|
|
353
|
-
const schemaFingerprint = await computeSchemaFingerprint(schemaFiles)
|
|
354
|
-
await publishDatabaseBootstrap({ databaseService: db, schemaFingerprint })
|
|
355
|
-
},
|
|
356
|
-
async disconnect() {
|
|
357
|
-
await db.disconnect()
|
|
358
|
-
await redisManager.closeConnection()
|
|
359
|
-
},
|
|
414
|
+
try {
|
|
415
|
+
await closeSharedSubscriber()
|
|
416
|
+
await db.disconnect()
|
|
417
|
+
await redisManager.closeConnection()
|
|
418
|
+
} finally {
|
|
419
|
+
releaseRuntimeToken(runtimeToken)
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
releaseRuntimeToken(runtimeToken)
|
|
425
|
+
throw error
|
|
360
426
|
}
|
|
361
427
|
}
|
|
362
428
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { toTimestamp } from '@lota-sdk/shared'
|
|
2
1
|
import type { ChatMessage } from '@lota-sdk/shared'
|
|
3
2
|
import type { BoundQuery, RecordId } from 'surrealdb'
|
|
4
3
|
import { z } from 'zod'
|
|
@@ -7,7 +6,7 @@ import type { RecordIdRef } from './record-id'
|
|
|
7
6
|
import { databaseService } from './service'
|
|
8
7
|
import type { DatabaseTable } from './tables'
|
|
9
8
|
|
|
10
|
-
export const CursorRowSchema = z.object({ createdAt: z.
|
|
9
|
+
export const CursorRowSchema = z.object({ createdAt: z.coerce.date() })
|
|
11
10
|
|
|
12
11
|
export interface MessageHistoryPage {
|
|
13
12
|
messages: ChatMessage[]
|
|
@@ -64,10 +63,8 @@ async function listRowsBefore(
|
|
|
64
63
|
throw new Error(`Cursor message not found in ${config.table}: ${params.beforeMessageId}`)
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
const cursorCreatedAt =
|
|
66
|
+
const cursorCreatedAt = cursorRow.createdAt
|
|
68
67
|
const cursorId = config.toRowId(params.parentId, params.beforeMessageId)
|
|
69
68
|
|
|
70
|
-
return
|
|
71
|
-
config.queryBefore(params.parentId, cursorCreatedAt, cursorId, params.take),
|
|
72
|
-
)
|
|
69
|
+
return databaseService.query<unknown>(config.queryBefore(params.parentId, cursorCreatedAt, cursorId, params.take))
|
|
73
70
|
}
|
package/src/db/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './base.service'
|
|
1
2
|
export * from './cursor-pagination'
|
|
2
3
|
export * from './memory'
|
|
3
4
|
export * from './memory-store'
|
|
@@ -7,4 +8,5 @@ export * from './record-id'
|
|
|
7
8
|
export * from './sdk-database'
|
|
8
9
|
export * from './service'
|
|
9
10
|
export * from './startup'
|
|
11
|
+
export * from './surreal-mutation'
|
|
10
12
|
export * from './tables'
|
|
@@ -13,17 +13,17 @@ export interface SurrealMemoryRow {
|
|
|
13
13
|
importance: number
|
|
14
14
|
accessCount: number
|
|
15
15
|
needsReview: boolean
|
|
16
|
-
lastAccessedAt?: Date
|
|
17
|
-
createdAt: Date
|
|
18
|
-
updatedAt?: Date
|
|
19
|
-
validFrom: Date
|
|
20
|
-
validUntil?: Date
|
|
21
|
-
archivedAt?: Date
|
|
16
|
+
lastAccessedAt?: Date
|
|
17
|
+
createdAt: Date
|
|
18
|
+
updatedAt?: Date
|
|
19
|
+
validFrom: Date
|
|
20
|
+
validUntil?: Date
|
|
21
|
+
archivedAt?: Date
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface BasicSearchRow {
|
|
25
25
|
id: RecordIdInput
|
|
26
26
|
content: string
|
|
27
27
|
metadata: Record<string, unknown>
|
|
28
|
-
createdAt?: Date
|
|
28
|
+
createdAt?: Date
|
|
29
29
|
}
|
package/src/db/memory-store.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { BoundQuery, eq, inside } from 'surrealdb'
|
|
|
3
3
|
import { aiLogger } from '../config/logger'
|
|
4
4
|
import { DEFAULT_MEMORY_SEARCH_LIMIT } from '../config/search'
|
|
5
5
|
import { getDefaultEmbeddings } from '../embeddings/provider'
|
|
6
|
+
import { withTimeout } from '../utils/async'
|
|
7
|
+
import { clampImportance, truncateText } from '../utils/string'
|
|
6
8
|
import { memoryQueryBuilder } from './memory-query-builder'
|
|
7
9
|
import type { RelationCounts } from './memory-store.helpers'
|
|
8
10
|
import { hashContent, mapRowToMemoryRecord, processGraphAwareRows } from './memory-store.helpers'
|
|
@@ -92,7 +94,7 @@ export class SurrealMemoryStore {
|
|
|
92
94
|
LIMIT $limit
|
|
93
95
|
`
|
|
94
96
|
|
|
95
|
-
return
|
|
97
|
+
return databaseService.query<BasicSearchRow>(
|
|
96
98
|
new BoundQuery(sql, { scopeId: options.scopeId, memoryType: options.memoryType, limit: options.limit }),
|
|
97
99
|
)
|
|
98
100
|
}
|
|
@@ -184,7 +186,7 @@ export class SurrealMemoryStore {
|
|
|
184
186
|
const normalized = content.trim()
|
|
185
187
|
if (!normalized) return []
|
|
186
188
|
|
|
187
|
-
return
|
|
189
|
+
return this.embeddings.embedQuery(normalized)
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
async warmEmbedding(content: string): Promise<void> {
|
|
@@ -355,11 +357,11 @@ export class SurrealMemoryStore {
|
|
|
355
357
|
const hash = hashContent(content, scopeId, memoryType)
|
|
356
358
|
const embedding = await this.generateEmbedding(content)
|
|
357
359
|
|
|
358
|
-
importance =
|
|
360
|
+
importance = clampImportance(importance)
|
|
359
361
|
|
|
360
362
|
const nearDup = await this.findNearDuplicate(embedding, scopeId, content)
|
|
361
363
|
if (nearDup) {
|
|
362
|
-
const mergedImportance =
|
|
364
|
+
const mergedImportance = clampImportance(Math.max(nearDup.importance, importance))
|
|
363
365
|
const keepNew = content.length >= nearDup.content.length
|
|
364
366
|
const winnerContent = keepNew ? content : nearDup.content
|
|
365
367
|
await this.update(nearDup.id, winnerContent, { importance: mergedImportance })
|
|
@@ -580,12 +582,11 @@ export class SurrealMemoryStore {
|
|
|
580
582
|
|
|
581
583
|
let results: LinearRow[]
|
|
582
584
|
try {
|
|
583
|
-
results = await
|
|
585
|
+
results = await withTimeout(
|
|
584
586
|
this.queryFinalStatement<LinearRow>(sql, bindVars),
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
])
|
|
587
|
+
SurrealMemoryStore.HYBRID_SEARCH_TIMEOUT_MS,
|
|
588
|
+
'Hybrid search',
|
|
589
|
+
)
|
|
589
590
|
} catch {
|
|
590
591
|
const elapsed = performance.now() - searchStart
|
|
591
592
|
aiLogger.warn`Hybrid search timed out after ${elapsed.toFixed(0)}ms (scopeId: ${options.scopeId}). Falling back to vector-only.`
|
|
@@ -669,18 +670,13 @@ export class SurrealMemoryStore {
|
|
|
669
670
|
|
|
670
671
|
const importance =
|
|
671
672
|
typeof options?.importance === 'number'
|
|
672
|
-
? Math.max(existing.importance,
|
|
673
|
+
? Math.max(existing.importance, clampImportance(options.importance))
|
|
673
674
|
: undefined
|
|
674
675
|
|
|
675
676
|
const durability = options?.durability
|
|
676
677
|
const metadata = options?.metadata ? { ...existing.metadata, ...options.metadata } : undefined
|
|
677
678
|
|
|
678
|
-
const updatePayload: Record<string, unknown> = {
|
|
679
|
-
content: newContent,
|
|
680
|
-
embedding: newEmbedding,
|
|
681
|
-
hash: newHash,
|
|
682
|
-
updatedAt: new Date(),
|
|
683
|
-
}
|
|
679
|
+
const updatePayload: Record<string, unknown> = { content: newContent, embedding: newEmbedding, hash: newHash }
|
|
684
680
|
if (importance !== undefined) {
|
|
685
681
|
updatePayload.importance = importance
|
|
686
682
|
}
|
|
@@ -760,7 +756,7 @@ export class SurrealMemoryStore {
|
|
|
760
756
|
}
|
|
761
757
|
|
|
762
758
|
async addRelation(fromId: string, toId: string, relationType: RelationType, confidence: number = 1.0): Promise<void> {
|
|
763
|
-
confidence =
|
|
759
|
+
confidence = clampImportance(confidence)
|
|
764
760
|
const fromRef = ensureRecordId(fromId, TABLES.MEMORY)
|
|
765
761
|
const toRef = ensureRecordId(toId, TABLES.MEMORY)
|
|
766
762
|
await databaseService.relate(fromRef, MEMORY_RELATION_TABLE, toRef, { relationType, confidence })
|
|
@@ -953,7 +949,7 @@ export class SurrealMemoryStore {
|
|
|
953
949
|
if (!neighbor.content || seen.has(neighborId)) continue
|
|
954
950
|
seen.add(neighborId)
|
|
955
951
|
const label = neighbor.relationType ? `[${neighbor.relationType}]` : ''
|
|
956
|
-
const truncated = neighbor.content
|
|
952
|
+
const truncated = truncateText(neighbor.content, 200)
|
|
957
953
|
contexts.push(`${label} ${truncated}`.trim())
|
|
958
954
|
}
|
|
959
955
|
if (contexts.length > 0) {
|