@lota-sdk/core 0.1.15 → 0.1.17

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 (159) hide show
  1. package/infrastructure/schema/00_identity.surql +0 -2
  2. package/infrastructure/schema/01_memory.surql +1 -1
  3. package/infrastructure/schema/02_execution_plan.surql +62 -1
  4. package/infrastructure/schema/03_learned_skill.surql +1 -1
  5. package/infrastructure/schema/06_playbook.surql +25 -0
  6. package/infrastructure/schema/07_institutional_memory.surql +13 -0
  7. package/infrastructure/schema/08_quality_metrics.surql +17 -0
  8. package/package.json +12 -8
  9. package/src/ai/definitions.ts +81 -3
  10. package/src/ai/embedding-cache.ts +2 -4
  11. package/src/ai/index.ts +0 -2
  12. package/src/bifrost/bifrost.ts +2 -7
  13. package/src/bifrost/cache-headers.ts +8 -0
  14. package/src/bifrost/index.ts +1 -0
  15. package/src/config/agent-defaults.ts +31 -21
  16. package/src/config/agent-types.ts +11 -0
  17. package/src/config/constants.ts +2 -14
  18. package/src/config/debug-logger.ts +5 -1
  19. package/src/config/index.ts +3 -0
  20. package/src/config/model-constants.ts +16 -34
  21. package/src/config/search.ts +1 -15
  22. package/src/create-runtime.ts +269 -178
  23. package/src/db/cursor-pagination.ts +3 -6
  24. package/src/db/index.ts +2 -0
  25. package/src/db/memory-store.helpers.ts +1 -3
  26. package/src/db/memory-store.rows.ts +7 -7
  27. package/src/db/memory-store.ts +14 -18
  28. package/src/db/memory.ts +13 -13
  29. package/src/db/schema-fingerprint.ts +1 -3
  30. package/src/db/service.ts +153 -79
  31. package/src/db/startup.ts +6 -10
  32. package/src/db/surreal-mutation.ts +43 -0
  33. package/src/db/tables.ts +7 -0
  34. package/src/db/workstream-message-row.ts +15 -0
  35. package/src/embeddings/provider.ts +1 -1
  36. package/src/queues/context-compaction.queue.ts +15 -46
  37. package/src/queues/delayed-node-promotion.queue.ts +41 -0
  38. package/src/queues/document-processor.queue.ts +2 -4
  39. package/src/queues/index.ts +3 -0
  40. package/src/queues/memory-consolidation.queue.ts +16 -51
  41. package/src/queues/plan-scheduler.queue.ts +97 -0
  42. package/src/queues/post-chat-memory.queue.ts +20 -55
  43. package/src/queues/queue-factory.ts +100 -0
  44. package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
  45. package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
  46. package/src/queues/skill-extraction.queue.ts +15 -47
  47. package/src/queues/workstream-title-generation.queue.ts +15 -47
  48. package/src/redis/connection.ts +6 -0
  49. package/src/redis/index.ts +1 -1
  50. package/src/redis/redis-lease-lock.ts +1 -2
  51. package/src/redis/stream-context.ts +11 -0
  52. package/src/runtime/agent-runtime-policy.ts +109 -35
  53. package/src/runtime/approval-continuation.ts +12 -6
  54. package/src/runtime/context-compaction-runtime.ts +1 -1
  55. package/src/runtime/context-compaction.ts +24 -64
  56. package/src/runtime/execution-plan.ts +22 -18
  57. package/src/runtime/graph-designer.ts +15 -0
  58. package/src/runtime/helper-model.ts +9 -197
  59. package/src/runtime/index.ts +3 -1
  60. package/src/runtime/llm-content.ts +1 -1
  61. package/src/runtime/memory-block.ts +9 -11
  62. package/src/runtime/memory-pipeline.ts +6 -9
  63. package/src/runtime/plugin-resolution.ts +35 -0
  64. package/src/runtime/plugin-types.ts +72 -0
  65. package/src/runtime/retrieval-adapters.ts +1 -1
  66. package/src/runtime/runtime-config.ts +111 -14
  67. package/src/runtime/runtime-extensions.ts +2 -3
  68. package/src/runtime/runtime-worker-registry.ts +6 -0
  69. package/src/runtime/social-chat.ts +752 -0
  70. package/src/runtime/team-consultation-orchestrator.ts +45 -32
  71. package/src/runtime/team-consultation-prompts.ts +11 -2
  72. package/src/runtime/title-helpers.ts +2 -4
  73. package/src/runtime/workstream-chat-helpers.ts +1 -1
  74. package/src/services/adaptive-playbook.service.ts +152 -0
  75. package/src/services/agent-executor.service.ts +292 -0
  76. package/src/services/artifact-provenance.service.ts +172 -0
  77. package/src/services/attachment.service.ts +6 -11
  78. package/src/services/context-compaction.service.ts +72 -55
  79. package/src/services/context-enrichment.service.ts +33 -0
  80. package/src/services/coordination-registry.service.ts +117 -0
  81. package/src/services/document-chunk.service.ts +2 -4
  82. package/src/services/domain-agent-executor.service.ts +71 -0
  83. package/src/services/execution-plan.service.ts +269 -50
  84. package/src/services/feedback-loop.service.ts +96 -0
  85. package/src/services/global-orchestrator.service.ts +148 -0
  86. package/src/services/index.ts +27 -0
  87. package/src/services/institutional-memory.service.ts +145 -0
  88. package/src/services/learned-skill.service.ts +24 -5
  89. package/src/services/memory-assessment.service.ts +3 -2
  90. package/src/services/memory-utils.ts +3 -8
  91. package/src/services/memory.service.ts +49 -61
  92. package/src/services/monitoring-window.service.ts +86 -0
  93. package/src/services/mutating-approval.service.ts +1 -1
  94. package/src/services/node-workspace.service.ts +155 -0
  95. package/src/services/notification.service.ts +39 -0
  96. package/src/services/organization-member.service.ts +11 -4
  97. package/src/services/organization.service.ts +5 -5
  98. package/src/services/ownership-dispatcher.service.ts +403 -0
  99. package/src/services/plan-approval.service.ts +1 -1
  100. package/src/services/plan-builder.service.ts +1 -0
  101. package/src/services/plan-checkpoint.service.ts +30 -2
  102. package/src/services/plan-compiler.service.ts +5 -0
  103. package/src/services/plan-coordination.service.ts +152 -0
  104. package/src/services/plan-cycle.service.ts +284 -0
  105. package/src/services/plan-deadline.service.ts +287 -0
  106. package/src/services/plan-executor.service.ts +384 -40
  107. package/src/services/plan-run.service.ts +41 -7
  108. package/src/services/plan-scheduler.service.ts +240 -0
  109. package/src/services/plan-template.service.ts +117 -0
  110. package/src/services/plan-validator.service.ts +84 -2
  111. package/src/services/plan-workspace.service.ts +83 -0
  112. package/src/services/playbook-registry.service.ts +67 -0
  113. package/src/services/plugin-executor.service.ts +103 -0
  114. package/src/services/quality-metrics.service.ts +132 -0
  115. package/src/services/recent-activity.service.ts +28 -34
  116. package/src/services/skill-resolver.service.ts +19 -0
  117. package/src/services/social-chat-history.service.ts +197 -0
  118. package/src/services/system-executor.service.ts +105 -0
  119. package/src/services/workstream-message.service.ts +13 -37
  120. package/src/services/workstream-plan-registry.service.ts +22 -0
  121. package/src/services/workstream-title.service.ts +3 -1
  122. package/src/services/workstream-turn-preparation.service.ts +34 -89
  123. package/src/services/workstream.service.ts +33 -55
  124. package/src/services/workstream.types.ts +9 -9
  125. package/src/services/write-intent-validator.service.ts +81 -0
  126. package/src/storage/attachment-parser.ts +1 -1
  127. package/src/storage/attachment-utils.ts +1 -1
  128. package/src/storage/generated-document-storage.service.ts +3 -2
  129. package/src/system-agents/context-compaction.agent.ts +2 -0
  130. package/src/system-agents/delegated-agent-factory.ts +5 -0
  131. package/src/system-agents/memory-reranker.agent.ts +4 -2
  132. package/src/system-agents/memory.agent.ts +2 -0
  133. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
  134. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
  135. package/src/system-agents/skill-extractor.agent.ts +2 -0
  136. package/src/system-agents/skill-manager.agent.ts +2 -0
  137. package/src/system-agents/title-generator.agent.ts +2 -0
  138. package/src/tools/execution-plan.tool.ts +17 -23
  139. package/src/tools/index.ts +0 -1
  140. package/src/tools/research-topic.tool.ts +2 -0
  141. package/src/tools/team-think.tool.ts +5 -6
  142. package/src/utils/async.ts +2 -1
  143. package/src/utils/date-time.ts +4 -32
  144. package/src/utils/env.ts +8 -0
  145. package/src/utils/errors.ts +42 -10
  146. package/src/utils/index.ts +9 -0
  147. package/src/utils/string.ts +114 -1
  148. package/src/workers/index.ts +1 -0
  149. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  150. package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
  151. package/src/workers/skill-extraction.runner.ts +26 -6
  152. package/src/workers/utils/file-section-chunker.ts +2 -1
  153. package/src/workers/utils/repo-structure-extractor.ts +2 -2
  154. package/src/workers/utils/repomix-file-sections.ts +2 -2
  155. package/src/workers/utils/sandbox-error.ts +11 -2
  156. package/src/workers/utils/workstream-message-query.ts +14 -25
  157. package/src/workers/worker-utils.ts +2 -2
  158. package/src/runtime/workstream-routing-policy.ts +0 -267
  159. package/src/tools/log-hello-world.tool.ts +0 -17
@@ -1,17 +1,3 @@
1
- /**
2
- * Search-related constants for vector search and hybrid search
3
- */
1
+ export { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '@lota-sdk/shared'
4
2
 
5
- /**
6
- * Multiplier for vector search fetch limit.
7
- * We fetch more candidates than needed (limit * multiplier) to allow for:
8
- * - RRF (Reciprocal Rank Fusion) re-ranking
9
- * - Hybrid search combining vector + full-text results
10
- * - Filtering out low-quality matches before returning final results
11
- */
12
- export const VECTOR_SEARCH_OVERFETCH_MULTIPLIER = 2
13
-
14
- /**
15
- * Default limit for memory search queries
16
- */
17
3
  export const DEFAULT_MEMORY_SEARCH_LIMIT = 10
@@ -15,32 +15,45 @@ 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 type { LotaPlugin } from './runtime/plugin-types'
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'
24
26
  import type { LotaRuntimeWorkers } from './runtime/runtime-worker-registry'
25
27
  import { buildRuntimeWorkerRegistry } from './runtime/runtime-worker-registry'
28
+ import type { LotaRuntimeSocialChat } from './runtime/social-chat'
29
+ import { createSocialChatRuntime } from './runtime/social-chat'
26
30
  import type { attachmentService } from './services/attachment.service'
27
31
  import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
32
+ import { coordinationRegistryService as coordinationRegistryServiceSingleton } from './services/coordination-registry.service'
28
33
  import type { documentChunkService } from './services/document-chunk.service'
29
34
  import { documentChunkService as documentChunkServiceSingleton } from './services/document-chunk.service'
35
+ import { domainAgentExecutorService } from './services/domain-agent-executor.service'
30
36
  import type { executionPlanService } from './services/execution-plan.service'
31
37
  import { executionPlanService as executionPlanServiceSingleton } from './services/execution-plan.service'
32
38
  import type { memoryService } from './services/memory.service'
33
39
  import { memoryService as memoryServiceSingleton } from './services/memory.service'
34
40
  import type { verifyMutatingApproval } from './services/mutating-approval.service'
35
41
  import { verifyMutatingApproval as verifyMutatingApprovalSingleton } from './services/mutating-approval.service'
42
+ import { configureNotificationService } from './services/notification.service'
36
43
  import type { organizationMemberService } from './services/organization-member.service'
37
44
  import { organizationMemberService as organizationMemberServiceSingleton } from './services/organization-member.service'
38
45
  import type { organizationService } from './services/organization.service'
39
46
  import { organizationService as organizationServiceSingleton } from './services/organization.service'
47
+ import { playbookRegistryService } from './services/playbook-registry.service'
40
48
  import type { recentActivityTitleService } from './services/recent-activity-title.service'
41
49
  import { recentActivityTitleService as recentActivityTitleServiceSingleton } from './services/recent-activity-title.service'
42
50
  import type { recentActivityService } from './services/recent-activity.service'
43
51
  import { recentActivityService as recentActivityServiceSingleton } from './services/recent-activity.service'
52
+ import {
53
+ configureSocialChatHistory,
54
+ socialChatHistoryService as socialChatHistoryServiceSingleton,
55
+ } from './services/social-chat-history.service'
56
+ import { getBuiltInSystemExecutors } from './services/system-executor.service'
44
57
  import type { userService } from './services/user.service'
45
58
  import { userService as userServiceSingleton } from './services/user.service'
46
59
  import type { workstreamMessageService } from './services/workstream-message.service'
@@ -75,6 +88,24 @@ type UnarchiveSdkWorkstream = (
75
88
  status?: 'regular',
76
89
  ) => ReturnType<typeof workstreamServiceSingleton.updateStatus>
77
90
 
91
+ let activeRuntimeToken: symbol | null = null
92
+
93
+ function claimRuntimeToken(): symbol {
94
+ if (activeRuntimeToken) {
95
+ throw new Error('createLotaRuntime() is process-scoped. Disconnect the active runtime before creating another one.')
96
+ }
97
+
98
+ const token = Symbol('lota-runtime')
99
+ activeRuntimeToken = token
100
+ return token
101
+ }
102
+
103
+ function releaseRuntimeToken(token: symbol) {
104
+ if (activeRuntimeToken === token) {
105
+ activeRuntimeToken = null
106
+ }
107
+ }
108
+
78
109
  export interface LotaRuntime {
79
110
  services: {
80
111
  database: SurrealDBService
@@ -90,6 +121,7 @@ export interface LotaRuntime {
90
121
  userService: typeof userService
91
122
  recentActivityService: typeof recentActivityService
92
123
  recentActivityTitleService: typeof recentActivityTitleService
124
+ socialChatHistoryService: typeof socialChatHistoryServiceSingleton
93
125
  executionPlanService: typeof executionPlanService
94
126
  workstreamMessageService: typeof workstreamMessageService
95
127
  workstreamService: typeof workstreamService
@@ -99,6 +131,7 @@ export interface LotaRuntime {
99
131
  createWorkstreamTurnStream: typeof createWorkstreamTurnStream
100
132
  isApprovalContinuationRequest: typeof isApprovalContinuationRequest
101
133
  runWorkstreamTurnInBackground: typeof runWorkstreamTurnInBackground
134
+ syncPlaybookTemplates: typeof playbookRegistryService.syncPlaybookTemplates
102
135
  }
103
136
  lota: {
104
137
  organizations: {
@@ -158,205 +191,263 @@ export interface LotaRuntime {
158
191
  closeConnection: () => Promise<void>
159
192
  }
160
193
  workers: LotaRuntimeWorkers
194
+ socialChat: LotaRuntimeSocialChat
161
195
  schemaFiles: Array<string | URL>
162
196
  contributions: { envKeys: readonly string[]; schemaFiles: Array<string | URL> }
163
197
  config: ResolvedLotaRuntimeConfig
164
198
  plugins: Record<string, LotaPlugin>
199
+ systemExecutors: Record<string, SystemNodeExecutor>
165
200
  connectPluginDatabases(): Promise<void>
166
201
  connect(): Promise<void>
167
202
  disconnect(): Promise<void>
168
203
  }
169
204
 
170
205
  export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
171
- const resolvedConfig = parseLotaRuntimeConfig(config)
172
- configureRuntimeConfig(resolvedConfig)
206
+ const runtimeToken = claimRuntimeToken()
207
+
208
+ try {
209
+ const resolvedConfig = parseLotaRuntimeConfig(config)
210
+ const systemExecutors = { ...getBuiltInSystemExecutors(), ...resolvedConfig.systemExecutors }
211
+ const runtimeConfig = { ...resolvedConfig, systemExecutors } satisfies ResolvedLotaRuntimeConfig
212
+ configureRuntimeConfig(runtimeConfig)
173
213
 
174
- await configureLotaLogger(resolvedConfig.logging.level)
214
+ await configureLotaLogger(runtimeConfig.logging.level)
175
215
 
176
- const db = new SurrealDBServiceClass({
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)
216
+ const db = new SurrealDBServiceClass({
217
+ url: runtimeConfig.database.url,
218
+ namespace: runtimeConfig.database.namespace,
219
+ database: LOTA_SDK_DATABASE_NAME,
220
+ username: runtimeConfig.database.username,
221
+ password: runtimeConfig.database.password,
222
+ })
223
+ setDatabaseService(db)
184
224
 
185
- const redisManager = createRedisConnectionManager({ url: resolvedConfig.redis.url })
186
- setRedisConnectionManager(redisManager)
187
- configureEmbeddingCache(redisManager.getConnection(), resolvedConfig.memory.embeddingCacheTtlSeconds)
188
- configureBackgroundProcessing(resolvedConfig.backgroundProcessing)
225
+ const redisManager = createRedisConnectionManager({ url: runtimeConfig.redis.url })
226
+ setRedisConnectionManager(redisManager)
227
+ configureEmbeddingCache(redisManager.getConnection(), runtimeConfig.memory.embeddingCacheTtlSeconds)
228
+ configureBackgroundProcessing(runtimeConfig.backgroundProcessing)
229
+ configureSocialChatHistory({ keyPrefix: runtimeConfig.socialChat?.historyRedisKeyPrefix })
189
230
 
190
- configureAgents({
191
- roster: resolvedConfig.agents.roster,
192
- leadAgentId: resolvedConfig.agents.leadAgentId,
193
- displayNames: resolvedConfig.agents.displayNames,
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
- })
231
+ const socialChatAgentId = runtimeConfig.socialChat?.agentId?.trim() || 'socialChat'
232
+ const socialChatAgentDisplayName = runtimeConfig.socialChat?.agentDisplayName?.trim() || 'Lota'
233
+ if (runtimeConfig.socialChat && !runtimeConfig.agents.roster.includes(socialChatAgentId)) {
234
+ throw new Error(`socialChat.agentId must be present in agents.roster: ${socialChatAgentId}`)
235
+ }
236
+ const agentDisplayNames = runtimeConfig.socialChat
237
+ ? { ...runtimeConfig.agents.displayNames, [socialChatAgentId]: socialChatAgentDisplayName }
238
+ : runtimeConfig.agents.displayNames
211
239
 
212
- const pluginRuntime = resolvedConfig.pluginRuntime ?? {}
213
- const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
214
- const schemaFiles = [...getBuiltInSchemaFiles(), ...(resolvedConfig.extraSchemaFiles ?? [])]
215
- const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
216
- const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
217
- const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
218
- const workers = buildRuntimeWorkerRegistry(resolvedConfig.extraWorkers)
240
+ configureAgents({
241
+ roster: runtimeConfig.agents.roster,
242
+ leadAgentId: runtimeConfig.agents.leadAgentId,
243
+ displayNames: agentDisplayNames,
244
+ shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
245
+ teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
246
+ getCoreWorkstreamProfile: runtimeConfig.agents.getCoreWorkstreamProfile,
247
+ })
248
+ configureAgentFactory({
249
+ createAgent: runtimeConfig.agents.createAgent,
250
+ buildAgentTools: runtimeConfig.agents.buildAgentTools,
251
+ getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
252
+ pluginRuntime: runtimeConfig.pluginRuntime,
253
+ })
254
+ configureWorkstreams({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.workstreams })
255
+ configureNotificationService(runtimeConfig.notificationService ?? null)
256
+ configureRuntimeExtensions({
257
+ adapters: runtimeConfig.runtimeAdapters,
258
+ turnHooks: runtimeConfig.turnHooks,
259
+ toolProviders: (runtimeConfig.toolProviders ?? {}) as never,
260
+ extraWorkers: runtimeConfig.extraWorkers,
261
+ })
219
262
 
220
- const lota = {
221
- organizations: {
222
- create: organizationServiceSingleton.createOrganization.bind(organizationServiceSingleton),
223
- upsert: organizationServiceSingleton.upsertOrganization.bind(organizationServiceSingleton),
224
- get: organizationServiceSingleton.getOrganization.bind(organizationServiceSingleton),
225
- list: organizationServiceSingleton.listOrganizations.bind(organizationServiceSingleton),
226
- update: organizationServiceSingleton.updateOrganization.bind(organizationServiceSingleton),
227
- delete: organizationServiceSingleton.deleteOrganization.bind(organizationServiceSingleton),
228
- },
229
- users: {
230
- upsert: userServiceSingleton.upsertUser.bind(userServiceSingleton),
231
- get: userServiceSingleton.getUser.bind(userServiceSingleton),
232
- list: userServiceSingleton.listUsers.bind(userServiceSingleton),
233
- update: userServiceSingleton.updateUser.bind(userServiceSingleton),
234
- delete: userServiceSingleton.deleteUser.bind(userServiceSingleton),
235
- },
236
- memberships: {
237
- add: organizationMemberServiceSingleton.addMembership.bind(organizationMemberServiceSingleton),
238
- listForOrganization: organizationMemberServiceSingleton.listMembershipsForOrganization.bind(
239
- organizationMemberServiceSingleton,
240
- ),
241
- listForUser: organizationMemberServiceSingleton.listMembershipsForUser.bind(organizationMemberServiceSingleton),
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
- }
263
+ const pluginRuntime = runtimeConfig.pluginRuntime ?? {}
264
+ domainAgentExecutorService.configure(pluginRuntime)
265
+ if (runtimeConfig.graphDesigner) {
266
+ configureGraphDesigner(runtimeConfig.graphDesigner)
267
+ }
274
268
 
275
- return await createWorkstreamTurnStreamSingleton({
276
- workstream,
277
- workstreamRef,
278
- orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
279
- userRef: ensureRecordId(userId, TABLES.USER),
280
- userName,
281
- inputMessage: routed.inputMessage,
282
- })
269
+ for (const [pluginRef, plugin] of Object.entries(pluginRuntime)) {
270
+ const signals = plugin.contributions.signals
271
+ if (signals && signals.length > 0) {
272
+ coordinationRegistryServiceSingleton.register(pluginRef, [...signals])
273
+ }
274
+ }
275
+ coordinationRegistryServiceSingleton.validate()
276
+ // Collect playbook contributions early to fail fast on misconfiguration
277
+ playbookRegistryService.collectPlaybooks()
278
+
279
+ const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
280
+ const schemaFiles = [...getBuiltInSchemaFiles(), ...(runtimeConfig.extraSchemaFiles ?? [])]
281
+ const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
282
+ const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
283
+ const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
284
+ const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
285
+ const socialChat = createSocialChatRuntime({
286
+ redisClient: redisManager.getConnection(),
287
+ socialChat: runtimeConfig.socialChat,
288
+ })
289
+
290
+ const lota = {
291
+ organizations: {
292
+ create: organizationServiceSingleton.createOrganization.bind(organizationServiceSingleton),
293
+ upsert: organizationServiceSingleton.upsertOrganization.bind(organizationServiceSingleton),
294
+ get: organizationServiceSingleton.getOrganization.bind(organizationServiceSingleton),
295
+ list: organizationServiceSingleton.listOrganizations.bind(organizationServiceSingleton),
296
+ update: organizationServiceSingleton.updateOrganization.bind(organizationServiceSingleton),
297
+ delete: organizationServiceSingleton.deleteOrganization.bind(organizationServiceSingleton),
298
+ },
299
+ users: {
300
+ upsert: userServiceSingleton.upsertUser.bind(userServiceSingleton),
301
+ get: userServiceSingleton.getUser.bind(userServiceSingleton),
302
+ list: userServiceSingleton.listUsers.bind(userServiceSingleton),
303
+ update: userServiceSingleton.updateUser.bind(userServiceSingleton),
304
+ delete: userServiceSingleton.deleteUser.bind(userServiceSingleton),
283
305
  },
284
- continueApproval: async ({ workstreamId, organizationId, userId, userName, messages }) => {
285
- const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
286
- const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
287
- const routed = routeWorkstreamChatMessages(messages)
288
- if (routed.kind !== 'approval-continuation') {
289
- throw new Error(
290
- routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
306
+ memberships: {
307
+ add: organizationMemberServiceSingleton.addMembership.bind(organizationMemberServiceSingleton),
308
+ listForOrganization: organizationMemberServiceSingleton.listMembershipsForOrganization.bind(
309
+ organizationMemberServiceSingleton,
310
+ ),
311
+ listForUser: organizationMemberServiceSingleton.listMembershipsForUser.bind(organizationMemberServiceSingleton),
312
+ remove: organizationMemberServiceSingleton.removeMembership.bind(organizationMemberServiceSingleton),
313
+ isMember: organizationMemberServiceSingleton.isMember.bind(organizationMemberServiceSingleton),
314
+ },
315
+ workstreams: {
316
+ create: workstreamServiceSingleton.createWorkstream.bind(workstreamServiceSingleton),
317
+ list: workstreamServiceSingleton.listWorkstreams.bind(workstreamServiceSingleton),
318
+ get: workstreamServiceSingleton.getWorkstream.bind(workstreamServiceSingleton),
319
+ update: workstreamServiceSingleton.updateTitle.bind(workstreamServiceSingleton),
320
+ archive: async (workstreamId, status = 'archived') =>
321
+ await workstreamServiceSingleton.updateStatus(workstreamId, status),
322
+ unarchive: async (workstreamId, status = 'regular') =>
323
+ await workstreamServiceSingleton.updateStatus(workstreamId, status),
324
+ delete: workstreamServiceSingleton.deleteWorkstream.bind(workstreamServiceSingleton),
325
+ stop: workstreamServiceSingleton.stopActiveRun.bind(workstreamServiceSingleton),
326
+ listMessages: workstreamMessageServiceSingleton.listMessageHistoryPage.bind(workstreamMessageServiceSingleton),
327
+ getMessage: async ({ workstreamId, messageId }) => {
328
+ const messages = await workstreamMessageServiceSingleton.listMessages(
329
+ ensureRecordId(workstreamId, TABLES.WORKSTREAM),
291
330
  )
292
- }
331
+ const message = messages.find((candidate) => candidate.id === messageId)
332
+ if (!message) {
333
+ throw new Error(`Workstream message not found: ${messageId}`)
334
+ }
335
+ return message
336
+ },
337
+ sendMessage: async ({ workstreamId, organizationId, userId, userName, messages }) => {
338
+ const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
339
+ const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
340
+ const routed = routeWorkstreamChatMessages(messages)
341
+ if (routed.kind !== 'turn') {
342
+ throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
343
+ }
344
+
345
+ return createWorkstreamTurnStreamSingleton({
346
+ workstream,
347
+ workstreamRef,
348
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
349
+ userRef: ensureRecordId(userId, TABLES.USER),
350
+ userName,
351
+ inputMessage: routed.inputMessage,
352
+ })
353
+ },
354
+ continueApproval: async ({ workstreamId, organizationId, userId, userName, messages }) => {
355
+ const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
356
+ const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
357
+ const routed = routeWorkstreamChatMessages(messages)
358
+ if (routed.kind !== 'approval-continuation') {
359
+ throw new Error(
360
+ routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
361
+ )
362
+ }
293
363
 
294
- return await createWorkstreamApprovalContinuationStreamSingleton({
295
- workstream,
296
- workstreamRef,
297
- orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
298
- userRef: ensureRecordId(userId, TABLES.USER),
299
- userName,
300
- approvalMessages: routed.approvalMessages,
301
- })
364
+ return createWorkstreamApprovalContinuationStreamSingleton({
365
+ workstream,
366
+ workstreamRef,
367
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
368
+ userRef: ensureRecordId(userId, TABLES.USER),
369
+ userName,
370
+ approvalMessages: routed.approvalMessages,
371
+ })
372
+ },
373
+ uploadAttachment: attachmentServiceSingleton.uploadWorkstreamAttachment.bind(attachmentServiceSingleton),
302
374
  },
303
- uploadAttachment: attachmentServiceSingleton.uploadWorkstreamAttachment.bind(attachmentServiceSingleton),
304
- },
305
- } satisfies LotaRuntime['lota']
375
+ } satisfies LotaRuntime['lota']
306
376
 
307
- return {
308
- services: {
309
- database: db,
310
- redis: redisManager,
311
- closeRedisConnection: async () => await redisManager.closeConnection(),
312
- attachmentService: attachmentServiceSingleton,
313
- documentChunkService: documentChunkServiceSingleton,
314
- generatedDocumentStorageService: generatedDocumentStorageServiceSingleton,
315
- memoryService: memoryServiceSingleton,
316
- verifyMutatingApproval: verifyMutatingApprovalSingleton,
317
- organizationService: organizationServiceSingleton,
318
- organizationMemberService: organizationMemberServiceSingleton,
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
- },
377
+ let disconnected = false
378
+
379
+ return {
380
+ services: {
381
+ database: db,
382
+ redis: redisManager,
383
+ closeRedisConnection: async () => await redisManager.closeConnection(),
384
+ attachmentService: attachmentServiceSingleton,
385
+ documentChunkService: documentChunkServiceSingleton,
386
+ generatedDocumentStorageService: generatedDocumentStorageServiceSingleton,
387
+ memoryService: memoryServiceSingleton,
388
+ verifyMutatingApproval: verifyMutatingApprovalSingleton,
389
+ organizationService: organizationServiceSingleton,
390
+ organizationMemberService: organizationMemberServiceSingleton,
391
+ userService: userServiceSingleton,
392
+ recentActivityService: recentActivityServiceSingleton,
393
+ recentActivityTitleService: recentActivityTitleServiceSingleton,
394
+ socialChatHistoryService: socialChatHistoryServiceSingleton,
395
+ executionPlanService: executionPlanServiceSingleton,
396
+ workstreamMessageService: workstreamMessageServiceSingleton,
397
+ workstreamService: workstreamServiceSingleton,
398
+ workstreamTitleService: workstreamTitleServiceSingleton,
399
+ createWorkstreamApprovalContinuationStream: createWorkstreamApprovalContinuationStreamSingleton,
400
+ createWorkstreamNativeToolApprovalStream: createWorkstreamNativeToolApprovalStreamSingleton,
401
+ createWorkstreamTurnStream: createWorkstreamTurnStreamSingleton,
402
+ isApprovalContinuationRequest: isApprovalContinuationRequestSingleton,
403
+ runWorkstreamTurnInBackground: runWorkstreamTurnInBackgroundSingleton,
404
+ syncPlaybookTemplates: playbookRegistryService.syncPlaybookTemplates.bind(playbookRegistryService),
405
+ },
406
+ lota,
407
+ redis: {
408
+ manager: redisManager,
409
+ getConnection: () => redisManager.getConnection(),
410
+ getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
411
+ closeConnection: async () => await redisManager.closeConnection(),
412
+ },
413
+ workers,
414
+ socialChat,
415
+ schemaFiles,
416
+ contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
417
+ config: runtimeConfig,
418
+ plugins: pluginRuntime,
419
+ systemExecutors,
420
+ async connectPluginDatabases() {
421
+ await connectPluginDatabases()
422
+ },
423
+ async connect() {
424
+ await db.connect()
425
+ const bunFiles = schemaFiles.map((schemaFile) =>
426
+ schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
427
+ )
428
+ await db.applySchema(bunFiles)
429
+ const schemaFingerprint = await computeSchemaFingerprint(schemaFiles)
430
+ await publishDatabaseBootstrap({ databaseService: db, schemaFingerprint })
431
+ },
432
+ async disconnect() {
433
+ if (disconnected) {
434
+ return
435
+ }
436
+ disconnected = true
437
+
438
+ try {
439
+ await socialChat.shutdown()
440
+ await closeSharedSubscriber()
441
+ await db.disconnect()
442
+ await redisManager.closeConnection()
443
+ } finally {
444
+ releaseRuntimeToken(runtimeToken)
445
+ }
446
+ },
447
+ }
448
+ } catch (error) {
449
+ releaseRuntimeToken(runtimeToken)
450
+ throw error
360
451
  }
361
452
  }
362
453
 
@@ -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.union([z.date(), z.string(), z.number()]) })
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 = new Date(toTimestamp(cursorRow.createdAt) ?? Date.now())
66
+ const cursorCreatedAt = cursorRow.createdAt
68
67
  const cursorId = config.toRowId(params.parentId, params.beforeMessageId)
69
68
 
70
- return await databaseService.query<unknown>(
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'
@@ -1,5 +1,3 @@
1
- import { createHash } from 'node:crypto'
2
-
3
1
  import type { BasicSearchRow, SurrealMemoryRow } from './memory-store.rows'
4
2
  import type { MemoryRecord, MemorySearchResult } from './memory-types'
5
3
  import { recordIdToString } from './record-id'
@@ -114,5 +112,5 @@ export function processGraphAwareRows<T extends BasicSearchRow>(
114
112
  }
115
113
 
116
114
  export function hashContent(content: string, scopeId: string, memoryType: string): string {
117
- return createHash('sha256').update(`${scopeId}:${memoryType}:${content}`).digest('hex')
115
+ return new Bun.CryptoHasher('sha256').update(`${scopeId}:${memoryType}:${content}`).digest('hex')
118
116
  }
@@ -13,17 +13,17 @@ export interface SurrealMemoryRow {
13
13
  importance: number
14
14
  accessCount: number
15
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
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 | string
28
+ createdAt?: Date
29
29
  }