@lota-sdk/core 0.4.8 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,121 +1,152 @@
1
1
  import type { ChatMessage } from '@lota-sdk/shared'
2
+ import type { Context } from 'effect'
3
+ import { Effect, Layer, ManagedRuntime } from 'effect'
4
+ import { DevTools } from 'effect/unstable/devtools'
5
+ import { FetchHttpClient } from 'effect/unstable/http'
6
+ import { Otlp } from 'effect/unstable/observability'
7
+ import type { Subscriber } from 'resumable-stream/ioredis'
2
8
 
3
- import { configureEmbeddingCache } from './ai/embedding-cache'
4
- import { configureAgentFactory, configureAgents } from './config/agent-defaults'
5
- import { configureLotaLogger } from './config/logger'
6
- import { configureThreads } from './config/thread-defaults'
9
+ import { AiGatewayLive } from './ai-gateway/ai-gateway'
10
+ import { EmbeddingCacheLive } from './ai/embedding-cache'
7
11
  import { ensureRecordId } from './db/record-id'
8
12
  import { computeSchemaFingerprint } from './db/schema-fingerprint'
9
- import { LOTA_SDK_DATABASE_NAME } from './db/sdk-database'
10
13
  import type { SurrealDBService } from './db/service'
11
- import { SurrealDBService as SurrealDBServiceClass, setDatabaseService } from './db/service'
12
- import { publishDatabaseBootstrap } from './db/startup'
14
+ import { publishDatabaseBootstrapEffect } from './db/startup'
13
15
  import { TABLES } from './db/tables'
16
+ import {
17
+ buildInfrastructureLayer,
18
+ clearLotaSdkRuntime,
19
+ DatabaseServiceTag as EffectDatabaseService,
20
+ RedisServiceTag as EffectRedisService,
21
+ runPromise,
22
+ setLotaSdkRuntime,
23
+ } from './effect'
24
+ import type { AwaitableService, AwaitableValue } from './effect/awaitable-effect'
25
+ import { toAwaitableService } from './effect/awaitable-effect'
26
+ import { BadRequestError, ConfigurationError, NotFoundError, ServiceError } from './effect/errors'
27
+ import { effectTryPromise } from './effect/helpers'
14
28
  import type { RedisConnectionManager } from './redis/connection'
15
- import { createRedisConnectionManager } from './redis/connection'
16
- import { setRedisConnectionManager } from './redis/index'
17
- import { closeSharedSubscriber } from './redis/stream-context'
29
+ import { SharedThreadStreamSubscriberLive, SharedThreadStreamSubscriberTag } from './redis/stream-context'
18
30
  import type { isApprovalContinuationRequest } from './runtime/approval-continuation'
19
31
  import { routeThreadChatMessages } from './runtime/chat-request-routing'
20
- import { configureGraphDesigner } from './runtime/graph-designer'
21
- import type { LotaPlugin, SystemNodeExecutor } from './runtime/plugin-types'
22
- import { configureRuntimeConfig, LOTA_RUNTIME_ENV_KEYS, parseLotaRuntimeConfig } from './runtime/runtime-config'
32
+ import { CompactionCoordinationLive } from './runtime/chat-run-orchestration'
33
+ import { HelperModelLive } from './runtime/helper-model'
34
+ import type { LotaPlugin, PluginLifecycleServices, SystemNodeExecutor } from './runtime/plugin-types'
35
+ import { LOTA_RUNTIME_ENV_KEYS, loadLotaRuntimeConfigFromEnv, parseLotaRuntimeConfig } from './runtime/runtime-config'
23
36
  import type { LotaRuntimeConfig, ResolvedLotaRuntimeConfig } from './runtime/runtime-config'
24
- import { configureRuntimeExtensions } from './runtime/runtime-extensions'
25
37
  import type { LotaRuntimeWorkers } from './runtime/runtime-worker-registry'
26
38
  import { buildRuntimeWorkerRegistry } from './runtime/runtime-worker-registry'
27
- import type { LotaRuntimeSocialChat } from './runtime/social-chat'
28
- import { createSocialChatRuntime } from './runtime/social-chat'
29
- import type { agentActivityService } from './services/agent-activity.service'
30
- import { agentActivityService as agentActivityServiceSingleton } from './services/agent-activity.service'
31
- import type { artifactService } from './services/artifact.service'
32
- import { artifactService as artifactServiceSingleton } from './services/artifact.service'
33
- import type { attachmentService } from './services/attachment.service'
34
- import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
35
- import type { autonomousJobService } from './services/autonomous-job.service'
36
- import { autonomousJobService as autonomousJobServiceSingleton } from './services/autonomous-job.service'
37
- import type { documentChunkService } from './services/document-chunk.service'
38
- import { documentChunkService as documentChunkServiceSingleton } from './services/document-chunk.service'
39
- import type { executionPlanService } from './services/execution-plan.service'
40
- import { executionPlanService as executionPlanServiceSingleton } from './services/execution-plan.service'
41
- import type { memoryService } from './services/memory.service'
42
- import { memoryService as memoryServiceSingleton } from './services/memory.service'
43
- import type { verifyMutatingApproval } from './services/mutating-approval.service'
44
- import { verifyMutatingApproval as verifyMutatingApprovalSingleton } from './services/mutating-approval.service'
45
- import { configureNotificationService } from './services/notification.service'
46
- import type { organizationMemberService } from './services/organization-member.service'
47
- import { organizationMemberService as organizationMemberServiceSingleton } from './services/organization-member.service'
48
- import type { organizationService } from './services/organization.service'
49
- import { organizationService as organizationServiceSingleton } from './services/organization.service'
50
- import type { planAgentHeartbeatService } from './services/plan-agent-heartbeat.service'
51
- import { planAgentHeartbeatService as planAgentHeartbeatServiceSingleton } from './services/plan-agent-heartbeat.service'
52
- import type { planAgentQueryService } from './services/plan-agent-query.service'
53
- import { planAgentQueryService as planAgentQueryServiceSingleton } from './services/plan-agent-query.service'
54
- import type { planCoordinationService } from './services/plan-coordination.service'
55
- import { planCoordinationService as planCoordinationServiceSingleton } from './services/plan-coordination.service'
56
- import type { planCycleService } from './services/plan-cycle.service'
57
- import { planCycleService as planCycleServiceSingleton } from './services/plan-cycle.service'
58
- import type { planSchedulerService } from './services/plan-scheduler.service'
59
- import { planSchedulerService as planSchedulerServiceSingleton } from './services/plan-scheduler.service'
60
- import type { planTemplateService } from './services/plan-template.service'
61
- import { planTemplateService as planTemplateServiceSingleton } from './services/plan-template.service'
62
- import type { recentActivityTitleService } from './services/recent-activity-title.service'
63
- import { recentActivityTitleService as recentActivityTitleServiceSingleton } from './services/recent-activity-title.service'
64
- import type { recentActivityService } from './services/recent-activity.service'
65
- import { recentActivityService as recentActivityServiceSingleton } from './services/recent-activity.service'
66
- import type { rerankService } from './services/rerank.service'
67
- import { rerankService as rerankServiceSingleton } from './services/rerank.service'
39
+ import type { LotaRuntimeSocialChat } from './runtime/social-chat/social-chat'
40
+ import { createSocialChatRuntime } from './runtime/social-chat/social-chat'
41
+ // ── Service Layer & Tag imports ────────────────────────────────────────
42
+ import { AgentActivityServiceLive, AgentActivityServiceTag } from './services/agent-activity.service'
43
+ import { AgentExecutorServiceLive } from './services/agent-executor.service'
44
+ import { ArtifactServiceLive, ArtifactServiceTag } from './services/artifact.service'
45
+ import { AttachmentServiceLive, AttachmentServiceTag } from './services/attachment.service'
46
+ import { AutonomousJobServiceLive, AutonomousJobServiceTag } from './services/autonomous-job.service'
47
+ import { ChatRunRegistryLive } from './services/chat-run-registry.service'
48
+ import { ContextCompactionServiceLive, ContextCompactionServiceTag } from './services/context-compaction.service'
49
+ import { DocumentChunkServiceLive, DocumentChunkServiceTag } from './services/document-chunk.service'
50
+ import { ExecutionPlanServiceLive, ExecutionPlanServiceTag } from './services/execution-plan/execution-plan.service'
51
+ import { FeedbackLoopServiceLive } from './services/feedback-loop.service'
52
+ import { GlobalOrchestratorServiceLive } from './services/global-orchestrator.service'
53
+ import { InstitutionalMemoryServiceLive } from './services/institutional-memory.service'
54
+ import { LearnedSkillServiceLive, LearnedSkillServiceTag } from './services/learned-skill.service'
55
+ import { MemoryServiceLive, MemoryServiceTag } from './services/memory/memory.service'
56
+ import { RerankServiceLive, RerankServiceTag } from './services/memory/rerank.service'
57
+ import { MonitoringWindowServiceLive } from './services/monitoring-window.service'
58
+ import { MutatingApprovalServiceLive, MutatingApprovalServiceTag } from './services/mutating-approval.service'
59
+ import { NodeWorkspaceServiceLive } from './services/node-workspace.service'
60
+ import { NotificationServiceLive } from './services/notification.service'
61
+ import { OrganizationMemberServiceLive, OrganizationMemberServiceTag } from './services/organization-member.service'
62
+ import { OrganizationServiceLive, OrganizationServiceTag } from './services/organization.service'
63
+ import { OwnershipDispatcherServiceLive } from './services/ownership-dispatcher.service'
68
64
  import {
69
- configureSocialChatHistory,
70
- socialChatHistoryService as socialChatHistoryServiceSingleton,
71
- } from './services/social-chat-history.service'
72
- import { getBuiltInSystemExecutors } from './services/system-executor.service'
73
- import type { threadMessageService } from './services/thread-message.service'
74
- import { threadMessageService as threadMessageServiceSingleton } from './services/thread-message.service'
75
- import type { threadTitleService } from './services/thread-title.service'
76
- import { threadTitleService as threadTitleServiceSingleton } from './services/thread-title.service'
65
+ PlanAgentHeartbeatServiceLive,
66
+ PlanAgentHeartbeatServiceTag,
67
+ } from './services/plan/plan-agent-heartbeat.service'
68
+ import { PlanAgentQueryServiceLive, PlanAgentQueryServiceTag } from './services/plan/plan-agent-query.service'
69
+ import { PlanApprovalServiceLive } from './services/plan/plan-approval.service'
70
+ import { PlanArtifactServiceLive } from './services/plan/plan-artifact.service'
71
+ import { PlanBuilderServiceLive } from './services/plan/plan-builder.service'
72
+ import { PlanCheckpointServiceLive } from './services/plan/plan-checkpoint.service'
73
+ import { PlanCompilerServiceLive } from './services/plan/plan-compiler.service'
74
+ import { PlanCoordinationServiceLive, PlanCoordinationServiceTag } from './services/plan/plan-coordination.service'
75
+ import { PlanCycleServiceLive, PlanCycleServiceTag } from './services/plan/plan-cycle.service'
76
+ import { PlanDeadlineServiceLive, PlanDeadlineServiceTag } from './services/plan/plan-deadline.service'
77
+ import { PlanEventDeliveryServiceLive } from './services/plan/plan-event-delivery.service'
78
+ import { PlanExecutorServiceLive, PlanExecutorServiceTag } from './services/plan/plan-executor.service'
79
+ import { PlanRunServiceLive, PlanRunServiceTag } from './services/plan/plan-run.service'
80
+ import { PlanSchedulerServiceLive, PlanSchedulerServiceTag } from './services/plan/plan-scheduler.service'
81
+ import { PlanTemplateServiceLive, PlanTemplateServiceTag } from './services/plan/plan-template.service'
82
+ import { PlanValidatorServiceLive } from './services/plan/plan-validator.service'
83
+ import { PlanWorkspaceServiceLive } from './services/plan/plan-workspace.service'
84
+ import { PluginExecutorServiceLive } from './services/plugin-executor.service'
85
+ import { QualityMetricsServiceLive } from './services/quality-metrics.service'
86
+ import { QueueJobServiceLive } from './services/queue-job.service'
87
+ import { RecentActivityTitleServiceLive, RecentActivityTitleServiceTag } from './services/recent-activity-title.service'
88
+ import { RecentActivityServiceLive, RecentActivityServiceTag } from './services/recent-activity.service'
89
+ import { SkillResolverServiceLive } from './services/skill-resolver.service'
90
+ import type { makeSocialChatHistoryService } from './services/social-chat-history.service'
91
+ import { SocialChatHistoryServiceLive, SocialChatHistoryServiceTag } from './services/social-chat-history.service'
92
+ import { getBuiltInSystemExecutors, SystemExecutorServiceLive } from './services/system-executor.service'
93
+ import { ThreadMessageServiceLive, ThreadMessageServiceTag } from './services/thread/thread-message.service'
94
+ import { ThreadTitleServiceLive, ThreadTitleServiceTag } from './services/thread/thread-title.service'
77
95
  import type {
78
96
  createThreadApprovalContinuationStream,
79
97
  createThreadNativeToolApprovalStream,
80
98
  createThreadTurnStream,
81
99
  runThreadTurnInBackground,
82
100
  triggerPlanNodeTurn,
83
- } from './services/thread-turn'
101
+ } from './services/thread/thread-turn'
102
+ import {
103
+ isApprovalContinuationRequest as isApprovalContinuationRequestFn,
104
+ ThreadTurnServiceLive,
105
+ ThreadTurnServiceTag,
106
+ } from './services/thread/thread-turn'
107
+ import { ThreadTurnPreparationServiceLive } from './services/thread/thread-turn-preparation.service'
108
+ import { ThreadServiceLive, ThreadServiceTag } from './services/thread/thread.service'
109
+ import { UserServiceLive, UserServiceTag } from './services/user.service'
110
+ import { WriteIntentValidatorServiceLive } from './services/write-intent-validator.service'
111
+ import { AttachmentStorageServiceLive } from './storage/attachment-storage.service'
84
112
  import {
85
- createThreadApprovalContinuationStream as createThreadApprovalContinuationStreamSingleton,
86
- createThreadNativeToolApprovalStream as createThreadNativeToolApprovalStreamSingleton,
87
- createThreadTurnStream as createThreadTurnStreamSingleton,
88
- isApprovalContinuationRequest as isApprovalContinuationRequestSingleton,
89
- runThreadTurnInBackground as runThreadTurnInBackgroundSingleton,
90
- triggerPlanNodeTurn as triggerPlanNodeTurnSingleton,
91
- } from './services/thread-turn'
92
- import type { threadService } from './services/thread.service'
93
- import { threadService as threadServiceSingleton } from './services/thread.service'
94
- import type { userService } from './services/user.service'
95
- import { userService as userServiceSingleton } from './services/user.service'
96
- import type { generatedDocumentStorageService } from './storage/generated-document-storage.service'
97
- import { generatedDocumentStorageService as generatedDocumentStorageServiceSingleton } from './storage/generated-document-storage.service'
113
+ GeneratedDocumentStorageServiceLive,
114
+ GeneratedDocumentStorageServiceTag,
115
+ } from './storage/generated-document-storage.service'
116
+ import { FirecrawlLive } from './tools/firecrawl-client'
117
+
118
+ // ── Type helpers ────────────────────────────────────────────────────────
119
+ type Svc<T extends { readonly Service: unknown }> = T['Service']
120
+ type HostSvc<T extends { readonly Service: object }> = AwaitableService<T['Service']>
121
+ type AwaitableThreadService = HostSvc<typeof ThreadServiceTag> &
122
+ Pick<Svc<typeof ThreadServiceTag>, 'withActiveRunLease'>
123
+ type AwaitableDocumentChunkService = HostSvc<typeof DocumentChunkServiceTag> &
124
+ Pick<Svc<typeof DocumentChunkServiceTag>, 'syncVersionedChunks'>
98
125
 
99
126
  type ArchiveSdkThread = (
100
- threadId: Parameters<typeof threadServiceSingleton.updateStatus>[0],
127
+ threadId: Parameters<Svc<typeof ThreadServiceTag>['updateStatus']>[0],
101
128
  status?: 'archived',
102
- ) => ReturnType<typeof threadServiceSingleton.updateStatus>
129
+ ) => ReturnType<Svc<typeof ThreadServiceTag>['updateStatus']>
103
130
 
104
131
  type UnarchiveSdkThread = (
105
- threadId: Parameters<typeof threadServiceSingleton.updateStatus>[0],
132
+ threadId: Parameters<Svc<typeof ThreadServiceTag>['updateStatus']>[0],
106
133
  status?: 'active',
107
- ) => ReturnType<typeof threadServiceSingleton.updateStatus>
134
+ ) => ReturnType<Svc<typeof ThreadServiceTag>['updateStatus']>
108
135
 
109
136
  let activeRuntimeToken: symbol | null = null
110
137
 
111
- function claimRuntimeToken(): symbol {
112
- if (activeRuntimeToken) {
113
- throw new Error('createLotaRuntime() is process-scoped. Disconnect the active runtime before creating another one.')
114
- }
138
+ function claimRuntimeToken(): Effect.Effect<symbol, ConfigurationError> {
139
+ return Effect.gen(function* () {
140
+ if (activeRuntimeToken) {
141
+ return yield* new ConfigurationError({
142
+ message: 'createLotaRuntime() is process-scoped. Disconnect the active runtime before creating another one.',
143
+ })
144
+ }
115
145
 
116
- const token = Symbol('lota-runtime')
117
- activeRuntimeToken = token
118
- return token
146
+ const token = Symbol('lota-runtime')
147
+ activeRuntimeToken = token
148
+ return token
149
+ })
119
150
  }
120
151
 
121
152
  function releaseRuntimeToken(token: symbol) {
@@ -124,36 +155,69 @@ function releaseRuntimeToken(token: symbol) {
124
155
  }
125
156
  }
126
157
 
158
+ function buildObservabilityLayer(runtimeConfig: ResolvedLotaRuntimeConfig): Layer.Layer<never> {
159
+ let layer = Layer.empty
160
+ const observability = runtimeConfig.observability
161
+
162
+ if (observability.otlpBaseUrl) {
163
+ layer = Layer.merge(
164
+ layer,
165
+ Otlp.layerJson({
166
+ baseUrl: observability.otlpBaseUrl,
167
+ resource: {
168
+ serviceName: observability.serviceName,
169
+ ...(observability.serviceVersion ? { serviceVersion: observability.serviceVersion } : {}),
170
+ },
171
+ ...(observability.otlpHeaders ? { headers: observability.otlpHeaders } : {}),
172
+ }).pipe(Layer.provide(FetchHttpClient.layer)),
173
+ )
174
+ }
175
+
176
+ if (observability.devToolsUrl && Bun.env.NODE_ENV !== 'production') {
177
+ layer = Layer.merge(layer, DevTools.layer(observability.devToolsUrl))
178
+ }
179
+
180
+ return layer
181
+ }
182
+
127
183
  export interface LotaRuntime {
184
+ /** Run an Effect as a Promise through the SDK runtime (carries OTLP context). */
185
+ runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: { readonly signal?: AbortSignal }) => Promise<A>
186
+ /** Run an Effect synchronously through the SDK runtime. */
187
+ runSync: <A, E>(effect: Effect.Effect<A, E>) => A
128
188
  services: {
129
189
  database: SurrealDBService
130
190
  redis: RedisConnectionManager
131
191
  closeRedisConnection: () => Promise<void>
132
- agentActivityService: typeof agentActivityService
133
- artifactService: typeof artifactService
134
- attachmentService: typeof attachmentService
135
- autonomousJobService: typeof autonomousJobService
136
- documentChunkService: typeof documentChunkService
137
- generatedDocumentStorageService: typeof generatedDocumentStorageService
138
- memoryService: typeof memoryService
139
- rerankService: typeof rerankService
140
- verifyMutatingApproval: typeof verifyMutatingApproval
141
- organizationService: typeof organizationService
142
- organizationMemberService: typeof organizationMemberService
143
- userService: typeof userService
144
- recentActivityService: typeof recentActivityService
145
- recentActivityTitleService: typeof recentActivityTitleService
146
- socialChatHistoryService: typeof socialChatHistoryServiceSingleton
147
- executionPlanService: typeof executionPlanService
148
- planTemplateService: typeof planTemplateService
149
- planCoordinationService: typeof planCoordinationService
150
- planSchedulerService: typeof planSchedulerService
151
- planAgentHeartbeatService: typeof planAgentHeartbeatService
152
- planAgentQueryService: typeof planAgentQueryService
153
- planCycleService: typeof planCycleService
154
- threadMessageService: typeof threadMessageService
155
- threadService: typeof threadService
156
- threadTitleService: typeof threadTitleService
192
+ agentActivityService: HostSvc<typeof AgentActivityServiceTag>
193
+ artifactService: HostSvc<typeof ArtifactServiceTag>
194
+ attachmentService: HostSvc<typeof AttachmentServiceTag>
195
+ autonomousJobService: HostSvc<typeof AutonomousJobServiceTag>
196
+ contextCompactionService: HostSvc<typeof ContextCompactionServiceTag>
197
+ documentChunkService: AwaitableDocumentChunkService
198
+ generatedDocumentStorageService: HostSvc<typeof GeneratedDocumentStorageServiceTag>
199
+ memoryService: HostSvc<typeof MemoryServiceTag>
200
+ rerankService: HostSvc<typeof RerankServiceTag>
201
+ verifyMutatingApproval: HostSvc<typeof MutatingApprovalServiceTag>
202
+ organizationService: HostSvc<typeof OrganizationServiceTag>
203
+ organizationMemberService: HostSvc<typeof OrganizationMemberServiceTag>
204
+ userService: HostSvc<typeof UserServiceTag>
205
+ recentActivityService: HostSvc<typeof RecentActivityServiceTag>
206
+ recentActivityTitleService: HostSvc<typeof RecentActivityTitleServiceTag>
207
+ socialChatHistoryService: ReturnType<typeof makeSocialChatHistoryService>
208
+ executionPlanService: HostSvc<typeof ExecutionPlanServiceTag>
209
+ planDeadlineService: HostSvc<typeof PlanDeadlineServiceTag>
210
+ planExecutorService: HostSvc<typeof PlanExecutorServiceTag>
211
+ planRunService: HostSvc<typeof PlanRunServiceTag>
212
+ planTemplateService: HostSvc<typeof PlanTemplateServiceTag>
213
+ planCoordinationService: HostSvc<typeof PlanCoordinationServiceTag>
214
+ planSchedulerService: HostSvc<typeof PlanSchedulerServiceTag>
215
+ planAgentHeartbeatService: HostSvc<typeof PlanAgentHeartbeatServiceTag>
216
+ planAgentQueryService: HostSvc<typeof PlanAgentQueryServiceTag>
217
+ planCycleService: HostSvc<typeof PlanCycleServiceTag>
218
+ threadMessageService: HostSvc<typeof ThreadMessageServiceTag>
219
+ threadService: AwaitableThreadService
220
+ threadTitleService: HostSvc<typeof ThreadTitleServiceTag>
157
221
  createThreadApprovalContinuationStream: typeof createThreadApprovalContinuationStream
158
222
  createThreadNativeToolApprovalStream: typeof createThreadNativeToolApprovalStream
159
223
  createThreadTurnStream: typeof createThreadTurnStream
@@ -163,37 +227,37 @@ export interface LotaRuntime {
163
227
  }
164
228
  lota: {
165
229
  organizations: {
166
- create: typeof organizationServiceSingleton.createOrganization
167
- upsert: typeof organizationServiceSingleton.upsertOrganization
168
- get: typeof organizationServiceSingleton.getOrganization
169
- list: typeof organizationServiceSingleton.listOrganizations
170
- update: typeof organizationServiceSingleton.updateOrganization
171
- delete: typeof organizationServiceSingleton.deleteOrganization
230
+ create: AwaitableValue<Svc<typeof OrganizationServiceTag>['createOrganization']>
231
+ upsert: AwaitableValue<Svc<typeof OrganizationServiceTag>['upsertOrganization']>
232
+ get: AwaitableValue<Svc<typeof OrganizationServiceTag>['getOrganization']>
233
+ list: AwaitableValue<Svc<typeof OrganizationServiceTag>['listOrganizations']>
234
+ update: AwaitableValue<Svc<typeof OrganizationServiceTag>['updateOrganization']>
235
+ delete: AwaitableValue<Svc<typeof OrganizationServiceTag>['deleteOrganization']>
172
236
  }
173
237
  users: {
174
- upsert: typeof userServiceSingleton.upsertUser
175
- get: typeof userServiceSingleton.getUser
176
- list: typeof userServiceSingleton.listUsers
177
- update: typeof userServiceSingleton.updateUser
178
- delete: typeof userServiceSingleton.deleteUser
238
+ upsert: AwaitableValue<Svc<typeof UserServiceTag>['upsertUser']>
239
+ get: AwaitableValue<Svc<typeof UserServiceTag>['getUser']>
240
+ list: AwaitableValue<Svc<typeof UserServiceTag>['listUsers']>
241
+ update: AwaitableValue<Svc<typeof UserServiceTag>['updateUser']>
242
+ delete: AwaitableValue<Svc<typeof UserServiceTag>['deleteUser']>
179
243
  }
180
244
  memberships: {
181
- add: typeof organizationMemberServiceSingleton.addMembership
182
- listForOrganization: typeof organizationMemberServiceSingleton.listMembershipsForOrganization
183
- listForUser: typeof organizationMemberServiceSingleton.listMembershipsForUser
184
- remove: typeof organizationMemberServiceSingleton.removeMembership
185
- isMember: typeof organizationMemberServiceSingleton.isMember
245
+ add: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['addMembership']>
246
+ listForOrganization: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['listMembershipsForOrganization']>
247
+ listForUser: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['listMembershipsForUser']>
248
+ remove: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['removeMembership']>
249
+ isMember: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['isMember']>
186
250
  }
187
251
  threads: {
188
- create: typeof threadServiceSingleton.createThread
189
- list: typeof threadServiceSingleton.listThreads
190
- get: typeof threadServiceSingleton.getThread
191
- update: typeof threadServiceSingleton.updateTitle
192
- archive: ArchiveSdkThread
193
- unarchive: UnarchiveSdkThread
194
- delete: typeof threadServiceSingleton.deleteThread
195
- stop: typeof threadServiceSingleton.stopActiveRun
196
- listMessages: typeof threadMessageServiceSingleton.listMessageHistoryPage
252
+ create: AwaitableValue<Svc<typeof ThreadServiceTag>['createThread']>
253
+ list: AwaitableValue<Svc<typeof ThreadServiceTag>['listThreads']>
254
+ get: AwaitableValue<Svc<typeof ThreadServiceTag>['getThread']>
255
+ update: AwaitableValue<Svc<typeof ThreadServiceTag>['updateTitle']>
256
+ archive: AwaitableValue<ArchiveSdkThread>
257
+ unarchive: AwaitableValue<UnarchiveSdkThread>
258
+ delete: AwaitableValue<Svc<typeof ThreadServiceTag>['deleteThread']>
259
+ stop: AwaitableValue<Svc<typeof ThreadServiceTag>['stopActiveRun']>
260
+ listMessages: AwaitableValue<Svc<typeof ThreadMessageServiceTag>['listMessageHistoryPage']>
197
261
  getMessage: (params: { threadId: string; messageId: string }) => Promise<ChatMessage>
198
262
  sendMessage: (params: {
199
263
  threadId: string
@@ -209,11 +273,12 @@ export interface LotaRuntime {
209
273
  userName: string
210
274
  messages: Parameters<typeof routeThreadChatMessages>[0]
211
275
  }) => Promise<Awaited<ReturnType<typeof createThreadApprovalContinuationStream>>>
212
- uploadAttachment: typeof attachmentServiceSingleton.uploadThreadAttachment
276
+ uploadAttachment: AwaitableValue<Svc<typeof AttachmentServiceTag>['uploadThreadAttachment']>
213
277
  }
214
278
  }
215
279
  redis: {
216
280
  manager: RedisConnectionManager
281
+ subscriber: Subscriber
217
282
  getConnection: () => ReturnType<RedisConnectionManager['getConnection']>
218
283
  getConnectionForBullMQ: () => ReturnType<RedisConnectionManager['getConnectionForBullMQ']>
219
284
  closeConnection: () => Promise<void>
@@ -230,253 +295,483 @@ export interface LotaRuntime {
230
295
  disconnect(): Promise<void>
231
296
  }
232
297
 
233
- export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
234
- const runtimeToken = claimRuntimeToken()
235
-
236
- try {
237
- const resolvedConfig = parseLotaRuntimeConfig(config)
238
- const systemExecutors = { ...getBuiltInSystemExecutors(), ...resolvedConfig.systemExecutors }
239
- const runtimeConfig = { ...resolvedConfig, systemExecutors } satisfies ResolvedLotaRuntimeConfig
240
- configureRuntimeConfig(runtimeConfig)
241
-
242
- await configureLotaLogger(runtimeConfig.logging.level)
243
-
244
- const db = new SurrealDBServiceClass({
245
- url: runtimeConfig.database.url,
246
- namespace: runtimeConfig.database.namespace,
247
- database: LOTA_SDK_DATABASE_NAME,
248
- username: runtimeConfig.database.username,
249
- password: runtimeConfig.database.password,
250
- })
251
- setDatabaseService(db)
252
-
253
- const redisManager = createRedisConnectionManager({ url: runtimeConfig.redis.url })
254
- setRedisConnectionManager(redisManager)
255
- configureEmbeddingCache(redisManager.getConnection(), runtimeConfig.memory.embeddingCacheTtlSeconds)
256
- configureSocialChatHistory({ keyPrefix: runtimeConfig.socialChat?.historyRedisKeyPrefix })
257
-
258
- const socialChatAgentId = runtimeConfig.socialChat?.agentId?.trim() || 'socialChat'
259
- const socialChatAgentDisplayName = runtimeConfig.socialChat?.agentDisplayName?.trim() || 'Lota'
260
- if (runtimeConfig.socialChat && !runtimeConfig.agents.roster.includes(socialChatAgentId)) {
261
- throw new Error(`socialChat.agentId must be present in agents.roster: ${socialChatAgentId}`)
262
- }
263
- const agentDisplayNames = runtimeConfig.socialChat
264
- ? { ...runtimeConfig.agents.displayNames, [socialChatAgentId]: socialChatAgentDisplayName }
265
- : runtimeConfig.agents.displayNames
266
-
267
- configureAgents({
268
- roster: runtimeConfig.agents.roster,
269
- leadAgentId: runtimeConfig.agents.leadAgentId,
270
- displayNames: agentDisplayNames,
271
- shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
272
- descriptions: runtimeConfig.agents.descriptions,
273
- routerModelId: runtimeConfig.agents.routerModelId,
274
- teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
275
- getCoreThreadProfile: runtimeConfig.agents.getCoreThreadProfile,
276
- })
277
- configureAgentFactory({
278
- createAgent: runtimeConfig.agents.createAgent,
279
- buildAgentTools: runtimeConfig.agents.buildAgentTools,
280
- getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
281
- pluginRuntime: runtimeConfig.pluginRuntime,
282
- })
283
- configureThreads({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads })
284
- configureNotificationService(runtimeConfig.notificationService ?? null)
285
- configureRuntimeExtensions({
286
- adapters: runtimeConfig.runtimeAdapters,
287
- turnHooks: runtimeConfig.turnHooks,
288
- toolProviders: (runtimeConfig.toolProviders ?? {}) as never,
289
- extraWorkers: runtimeConfig.extraWorkers,
290
- })
291
-
292
- const pluginRuntime = runtimeConfig.pluginRuntime ?? {}
293
- if (runtimeConfig.graphDesigner) {
294
- configureGraphDesigner(runtimeConfig.graphDesigner)
295
- }
298
+ export function createLotaRuntimeFromEnv(
299
+ config: Parameters<typeof loadLotaRuntimeConfigFromEnv>[0],
300
+ options?: Parameters<typeof loadLotaRuntimeConfigFromEnv>[1],
301
+ ): Promise<LotaRuntime> {
302
+ return Effect.runPromise(loadLotaRuntimeConfigFromEnv(config, options)).then((resolvedConfig) =>
303
+ createLotaRuntime(resolvedConfig),
304
+ )
305
+ }
306
+
307
+ export function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
308
+ let runtimeToken: symbol | null = null
309
+ let effectRuntime: { dispose(): Promise<void> } | null = null
310
+
311
+ return Effect.runPromise(
312
+ Effect.gen(function* () {
313
+ runtimeToken = yield* claimRuntimeToken()
314
+ const resolvedConfig = parseLotaRuntimeConfig(config)
315
+ const systemExecutors = { ...getBuiltInSystemExecutors(), ...resolvedConfig.systemExecutors }
316
+ const runtimeConfig = { ...resolvedConfig, systemExecutors } satisfies ResolvedLotaRuntimeConfig
317
+
318
+ const socialChatAgentId = runtimeConfig.socialChat?.agentId?.trim() || 'socialChat'
319
+ const socialChatAgentDisplayName = runtimeConfig.socialChat?.agentDisplayName?.trim() || 'Lota'
320
+ if (runtimeConfig.socialChat && !runtimeConfig.agents.roster.includes(socialChatAgentId)) {
321
+ return yield* new ConfigurationError({
322
+ message: `socialChat.agentId must be present in agents.roster: ${socialChatAgentId}`,
323
+ key: 'socialChat.agentId',
324
+ })
325
+ }
326
+ const resolvedAgentDisplayNames = runtimeConfig.socialChat
327
+ ? { ...runtimeConfig.agents.displayNames, [socialChatAgentId]: socialChatAgentDisplayName }
328
+ : runtimeConfig.agents.displayNames
296
329
 
297
- const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
298
- const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
299
- const schemaFiles = [
300
- ...getBuiltInSchemaFiles(),
301
- ...(runtimeConfig.extraSchemaFiles ?? []),
302
- ...hostContributionSchemaFiles,
303
- ]
304
- const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
305
- const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
306
- const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
307
- const socialChat = createSocialChatRuntime({
308
- redisClient: redisManager.getConnection(),
309
- socialChat: runtimeConfig.socialChat,
310
- })
311
-
312
- const lota = {
313
- organizations: {
314
- create: organizationServiceSingleton.createOrganization.bind(organizationServiceSingleton),
315
- upsert: organizationServiceSingleton.upsertOrganization.bind(organizationServiceSingleton),
316
- get: organizationServiceSingleton.getOrganization.bind(organizationServiceSingleton),
317
- list: organizationServiceSingleton.listOrganizations.bind(organizationServiceSingleton),
318
- update: organizationServiceSingleton.updateOrganization.bind(organizationServiceSingleton),
319
- delete: organizationServiceSingleton.deleteOrganization.bind(organizationServiceSingleton),
320
- },
321
- users: {
322
- upsert: userServiceSingleton.upsertUser.bind(userServiceSingleton),
323
- get: userServiceSingleton.getUser.bind(userServiceSingleton),
324
- list: userServiceSingleton.listUsers.bind(userServiceSingleton),
325
- update: userServiceSingleton.updateUser.bind(userServiceSingleton),
326
- delete: userServiceSingleton.deleteUser.bind(userServiceSingleton),
327
- },
328
- memberships: {
329
- add: organizationMemberServiceSingleton.addMembership.bind(organizationMemberServiceSingleton),
330
- listForOrganization: organizationMemberServiceSingleton.listMembershipsForOrganization.bind(
331
- organizationMemberServiceSingleton,
330
+ // ── Infrastructure layers ───────────────────────────────────────────
331
+ const infrastructureLayer = buildInfrastructureLayer(runtimeConfig, {
332
+ resolvedAgentDisplayNames,
333
+ observabilityLayer: buildObservabilityLayer(runtimeConfig),
334
+ })
335
+
336
+ // ── Domain service layers ───────────────────────────────────────────
337
+ function provide<A, E, R extends RCtx, RCtx, E2>(
338
+ layer: Layer.Layer<A, E, R>,
339
+ ctx: Layer.Layer<RCtx, E2, never>,
340
+ ): Layer.Layer<A, E | E2, never> {
341
+ return Layer.provideMerge(ctx)(layer)
342
+ }
343
+
344
+ const tier0 = provide(
345
+ Layer.mergeAll(
346
+ ChatRunRegistryLive,
347
+ CompactionCoordinationLive,
348
+ DocumentChunkServiceLive,
349
+ NodeWorkspaceServiceLive,
350
+ NotificationServiceLive,
351
+ PlanArtifactServiceLive,
352
+ PlanBuilderServiceLive,
353
+ WriteIntentValidatorServiceLive,
332
354
  ),
333
- listForUser: organizationMemberServiceSingleton.listMembershipsForUser.bind(organizationMemberServiceSingleton),
334
- remove: organizationMemberServiceSingleton.removeMembership.bind(organizationMemberServiceSingleton),
335
- isMember: organizationMemberServiceSingleton.isMember.bind(organizationMemberServiceSingleton),
336
- },
337
- threads: {
338
- create: threadServiceSingleton.createThread.bind(threadServiceSingleton),
339
- list: threadServiceSingleton.listThreads.bind(threadServiceSingleton),
340
- get: threadServiceSingleton.getThread.bind(threadServiceSingleton),
341
- update: threadServiceSingleton.updateTitle.bind(threadServiceSingleton),
342
- archive: async (threadId, status = 'archived') => await threadServiceSingleton.updateStatus(threadId, status),
343
- unarchive: async (threadId, status = 'active') => await threadServiceSingleton.updateStatus(threadId, status),
344
- delete: threadServiceSingleton.deleteThread.bind(threadServiceSingleton),
345
- stop: threadServiceSingleton.stopActiveRun.bind(threadServiceSingleton),
346
- listMessages: threadMessageServiceSingleton.listMessageHistoryPage.bind(threadMessageServiceSingleton),
347
- getMessage: async ({ threadId, messageId }) => {
348
- const messages = await threadMessageServiceSingleton.listMessages(ensureRecordId(threadId, TABLES.THREAD))
349
- const message = messages.find((candidate) => candidate.id === messageId)
350
- if (!message) {
351
- throw new Error(`Thread message not found: ${messageId}`)
352
- }
353
- return message
354
- },
355
- sendMessage: async ({ threadId, organizationId, userId, userName, messages }) => {
356
- const threadRef = ensureRecordId(threadId, TABLES.THREAD)
357
- const thread = await threadServiceSingleton.getThread(threadRef)
358
- const routed = routeThreadChatMessages(messages)
359
- if (routed.kind !== 'turn') {
360
- throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
361
- }
355
+ infrastructureLayer,
356
+ )
357
+ const ctx0 = Layer.mergeAll(
358
+ infrastructureLayer,
359
+ provide(Layer.mergeAll(AiGatewayLive, EmbeddingCacheLive, FirecrawlLive, HelperModelLive), infrastructureLayer),
360
+ tier0,
361
+ )
362
362
 
363
- return createThreadTurnStreamSingleton({
364
- thread,
365
- threadRef,
366
- orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
367
- userRef: ensureRecordId(userId, TABLES.USER),
368
- userName,
369
- inputMessage: routed.inputMessage,
370
- })
371
- },
372
- continueApproval: async ({ threadId, organizationId, userId, userName, messages }) => {
373
- const threadRef = ensureRecordId(threadId, TABLES.THREAD)
374
- const thread = await threadServiceSingleton.getThread(threadRef)
375
- const routed = routeThreadChatMessages(messages)
376
- if (routed.kind !== 'approval-continuation') {
377
- throw new Error(
378
- routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
379
- )
380
- }
363
+ const tier1 = provide(
364
+ Layer.mergeAll(
365
+ SharedThreadStreamSubscriberLive,
366
+ AgentExecutorServiceLive,
367
+ AttachmentStorageServiceLive,
368
+ GeneratedDocumentStorageServiceLive,
369
+ LearnedSkillServiceLive,
370
+ OrganizationServiceLive,
371
+ OrganizationMemberServiceLive,
372
+ PlanApprovalServiceLive,
373
+ PlanRunServiceLive,
374
+ PlanSchedulerServiceLive,
375
+ PlanWorkspaceServiceLive,
376
+ PluginExecutorServiceLive,
377
+ QualityMetricsServiceLive,
378
+ QueueJobServiceLive,
379
+ RecentActivityServiceLive,
380
+ RerankServiceLive,
381
+ SocialChatHistoryServiceLive,
382
+ SystemExecutorServiceLive,
383
+ ThreadMessageServiceLive,
384
+ UserServiceLive,
385
+ ),
386
+ ctx0,
387
+ )
388
+ const ctx1 = Layer.mergeAll(ctx0, tier1)
381
389
 
382
- return createThreadApprovalContinuationStreamSingleton({
383
- thread,
384
- threadRef,
385
- orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
386
- userRef: ensureRecordId(userId, TABLES.USER),
387
- userName,
388
- approvalMessages: routed.approvalMessages,
389
- })
390
- },
391
- uploadAttachment: attachmentServiceSingleton.uploadThreadAttachment.bind(attachmentServiceSingleton),
392
- },
393
- } satisfies LotaRuntime['lota']
390
+ const tier2 = provide(
391
+ Layer.mergeAll(
392
+ ArtifactServiceLive,
393
+ AttachmentServiceLive,
394
+ ContextCompactionServiceLive,
395
+ FeedbackLoopServiceLive,
396
+ InstitutionalMemoryServiceLive,
397
+ MemoryServiceLive,
398
+ MonitoringWindowServiceLive,
399
+ MutatingApprovalServiceLive,
400
+ PlanAgentQueryServiceLive,
401
+ PlanCheckpointServiceLive,
402
+ PlanCoordinationServiceLive,
403
+ PlanEventDeliveryServiceLive,
404
+ SkillResolverServiceLive,
405
+ RecentActivityTitleServiceLive,
406
+ ),
407
+ ctx1,
408
+ )
409
+ const ctx2 = Layer.mergeAll(ctx1, tier2)
410
+
411
+ const tier3 = provide(Layer.mergeAll(PlanValidatorServiceLive, ThreadServiceLive), ctx2)
412
+ const ctx3 = Layer.mergeAll(ctx2, tier3)
413
+
414
+ const tier4 = provide(Layer.mergeAll(PlanCompilerServiceLive, ThreadTitleServiceLive), ctx3)
415
+ const ctx4 = Layer.mergeAll(ctx3, tier4)
416
+
417
+ const tier5 = provide(PlanExecutorServiceLive, ctx4)
418
+ const ctx5 = Layer.mergeAll(ctx4, tier5)
419
+
420
+ const tier6 = provide(
421
+ Layer.mergeAll(OwnershipDispatcherServiceLive, PlanAgentHeartbeatServiceLive, PlanDeadlineServiceLive),
422
+ ctx5,
423
+ )
424
+ const ctx6 = Layer.mergeAll(ctx5, tier6)
425
+
426
+ const tier7 = provide(Layer.mergeAll(ExecutionPlanServiceLive, GlobalOrchestratorServiceLive), ctx6)
427
+ const ctx7 = Layer.mergeAll(ctx6, tier7)
428
+
429
+ const tier8 = provide(
430
+ Layer.mergeAll(
431
+ PlanTemplateServiceLive,
432
+ AutonomousJobServiceLive,
433
+ AgentActivityServiceLive,
434
+ ThreadTurnPreparationServiceLive,
435
+ ),
436
+ ctx7,
437
+ )
438
+ const ctx8 = Layer.mergeAll(ctx7, tier8)
439
+
440
+ const tier9 = provide(Layer.mergeAll(PlanCycleServiceLive, ThreadTurnServiceLive), ctx8)
441
+ const fullLayer = Layer.mergeAll(ctx8, tier9)
442
+
443
+ const managedRuntime = ManagedRuntime.make(fullLayer)
444
+ effectRuntime = managedRuntime
445
+ const resolvedServices = yield* Effect.tryPromise({
446
+ try: () =>
447
+ managedRuntime.runPromise(
448
+ Effect.gen(function* () {
449
+ return {
450
+ db: yield* EffectDatabaseService,
451
+ redisManager: yield* EffectRedisService,
452
+ sharedSubscriber: yield* SharedThreadStreamSubscriberTag,
453
+ threadTurnService: yield* ThreadTurnServiceTag,
454
+ socialChatHistoryService: yield* SocialChatHistoryServiceTag,
455
+ learnedSkillService: yield* LearnedSkillServiceTag,
456
+ memoryService: yield* MemoryServiceTag,
457
+ }
458
+ }),
459
+ ),
460
+ catch: (error) =>
461
+ new ServiceError({
462
+ message: `Failed to initialize Effect runtime services: ${error instanceof Error ? error.message : String(error)}`,
463
+ cause: error,
464
+ }),
465
+ })
466
+ const {
467
+ db,
468
+ redisManager,
469
+ sharedSubscriber,
470
+ threadTurnService,
471
+ socialChatHistoryService,
472
+ learnedSkillService,
473
+ memoryService,
474
+ } = resolvedServices
475
+
476
+ setLotaSdkRuntime(managedRuntime)
394
477
 
395
- let disconnected = false
478
+ type RuntimeServices = Layer.Success<typeof fullLayer>
479
+ const resolve = <I extends RuntimeServices, T>(tag: Context.Key<I, T>): T =>
480
+ managedRuntime.runSync(Effect.service(tag))
481
+ const resolveAwaitableService = <I extends RuntimeServices, T extends object>(
482
+ tag: Context.Key<I, T>,
483
+ ): AwaitableService<T> =>
484
+ toAwaitableService(resolve(tag), { runPromise: (effect) => managedRuntime.runPromise(effect) })
396
485
 
397
- return {
398
- services: {
486
+ const pluginRuntime = runtimeConfig.pluginRuntime ?? {}
487
+ const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
488
+ const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
489
+ const schemaFiles = [
490
+ ...getBuiltInSchemaFiles(),
491
+ ...(runtimeConfig.extraSchemaFiles ?? []),
492
+ ...hostContributionSchemaFiles,
493
+ ]
494
+ const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
495
+ const connectedPluginDatabases = new Set<string>()
496
+ const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime, connectedPluginDatabases)
497
+ const disconnectPluginDatabases = createPluginDatabaseDisconnector(pluginRuntime, connectedPluginDatabases)
498
+ const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
499
+ const socialChat = createSocialChatRuntime({
500
+ redisClient: redisManager.getConnection() as unknown as Parameters<
501
+ typeof createSocialChatRuntime
502
+ >[0]['redisClient'],
503
+ socialChat: runtimeConfig.socialChat,
504
+ services: { learnedSkillService, memoryService, socialChatHistoryService },
505
+ })
506
+
507
+ const services: LotaRuntime['services'] = {
399
508
  database: db,
400
509
  redis: redisManager,
401
- closeRedisConnection: async () => await redisManager.closeConnection(),
402
- agentActivityService: agentActivityServiceSingleton,
403
- artifactService: artifactServiceSingleton,
404
- attachmentService: attachmentServiceSingleton,
405
- autonomousJobService: autonomousJobServiceSingleton,
406
- documentChunkService: documentChunkServiceSingleton,
407
- generatedDocumentStorageService: generatedDocumentStorageServiceSingleton,
408
- memoryService: memoryServiceSingleton,
409
- rerankService: rerankServiceSingleton,
410
- verifyMutatingApproval: verifyMutatingApprovalSingleton,
411
- organizationService: organizationServiceSingleton,
412
- organizationMemberService: organizationMemberServiceSingleton,
413
- userService: userServiceSingleton,
414
- recentActivityService: recentActivityServiceSingleton,
415
- recentActivityTitleService: recentActivityTitleServiceSingleton,
416
- socialChatHistoryService: socialChatHistoryServiceSingleton,
417
- executionPlanService: executionPlanServiceSingleton,
418
- planTemplateService: planTemplateServiceSingleton,
419
- planCoordinationService: planCoordinationServiceSingleton,
420
- planSchedulerService: planSchedulerServiceSingleton,
421
- planAgentHeartbeatService: planAgentHeartbeatServiceSingleton,
422
- planAgentQueryService: planAgentQueryServiceSingleton,
423
- planCycleService: planCycleServiceSingleton,
424
- threadMessageService: threadMessageServiceSingleton,
425
- threadService: threadServiceSingleton,
426
- threadTitleService: threadTitleServiceSingleton,
427
- createThreadApprovalContinuationStream: createThreadApprovalContinuationStreamSingleton,
428
- createThreadNativeToolApprovalStream: createThreadNativeToolApprovalStreamSingleton,
429
- createThreadTurnStream: createThreadTurnStreamSingleton,
430
- isApprovalContinuationRequest: isApprovalContinuationRequestSingleton,
431
- runThreadTurnInBackground: runThreadTurnInBackgroundSingleton,
432
- triggerPlanNodeTurn: triggerPlanNodeTurnSingleton,
433
- },
434
- lota,
435
- redis: {
436
- manager: redisManager,
437
- getConnection: () => redisManager.getConnection(),
438
- getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
439
- closeConnection: async () => await redisManager.closeConnection(),
440
- },
441
- workers,
442
- socialChat,
443
- schemaFiles,
444
- contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
445
- config: runtimeConfig,
446
- plugins: pluginRuntime,
447
- systemExecutors,
448
- async connectPluginDatabases() {
449
- await connectPluginDatabases()
450
- },
451
- async connect() {
452
- await db.connect()
453
- const bunFiles = schemaFiles.map((schemaFile) =>
454
- schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
455
- )
456
- await db.applySchema(bunFiles)
457
- const schemaFingerprint = await computeSchemaFingerprint(schemaFiles)
458
- await publishDatabaseBootstrap({ databaseService: db, schemaFingerprint })
459
- },
460
- async disconnect() {
461
- if (disconnected) {
462
- return
463
- }
464
- disconnected = true
465
-
466
- try {
467
- await socialChat.shutdown()
468
- await closeSharedSubscriber()
469
- await db.disconnect()
470
- await redisManager.closeConnection()
471
- } finally {
472
- releaseRuntimeToken(runtimeToken)
473
- }
474
- },
510
+ closeRedisConnection: () => runPromise(effectTryPromise(() => redisManager.closeConnection())),
511
+ get agentActivityService() {
512
+ return resolveAwaitableService(AgentActivityServiceTag)
513
+ },
514
+ get artifactService() {
515
+ return resolveAwaitableService(ArtifactServiceTag)
516
+ },
517
+ get attachmentService() {
518
+ return resolveAwaitableService(AttachmentServiceTag)
519
+ },
520
+ get autonomousJobService() {
521
+ return resolveAwaitableService(AutonomousJobServiceTag)
522
+ },
523
+ get contextCompactionService() {
524
+ return resolveAwaitableService(ContextCompactionServiceTag)
525
+ },
526
+ get documentChunkService() {
527
+ return resolveAwaitableService(DocumentChunkServiceTag) as AwaitableDocumentChunkService
528
+ },
529
+ get generatedDocumentStorageService() {
530
+ return resolveAwaitableService(GeneratedDocumentStorageServiceTag)
531
+ },
532
+ get memoryService() {
533
+ return resolveAwaitableService(MemoryServiceTag)
534
+ },
535
+ get rerankService() {
536
+ return resolveAwaitableService(RerankServiceTag)
537
+ },
538
+ get verifyMutatingApproval() {
539
+ return resolveAwaitableService(MutatingApprovalServiceTag)
540
+ },
541
+ get organizationService() {
542
+ return resolveAwaitableService(OrganizationServiceTag)
543
+ },
544
+ get organizationMemberService() {
545
+ return resolveAwaitableService(OrganizationMemberServiceTag)
546
+ },
547
+ get userService() {
548
+ return resolveAwaitableService(UserServiceTag)
549
+ },
550
+ get recentActivityService() {
551
+ return resolveAwaitableService(RecentActivityServiceTag)
552
+ },
553
+ get recentActivityTitleService() {
554
+ return resolveAwaitableService(RecentActivityTitleServiceTag)
555
+ },
556
+ socialChatHistoryService,
557
+ get executionPlanService() {
558
+ return resolveAwaitableService(ExecutionPlanServiceTag)
559
+ },
560
+ get planDeadlineService() {
561
+ return resolveAwaitableService(PlanDeadlineServiceTag)
562
+ },
563
+ get planExecutorService() {
564
+ return resolveAwaitableService(PlanExecutorServiceTag)
565
+ },
566
+ get planRunService() {
567
+ return resolveAwaitableService(PlanRunServiceTag)
568
+ },
569
+ get planTemplateService() {
570
+ return resolveAwaitableService(PlanTemplateServiceTag)
571
+ },
572
+ get planCoordinationService() {
573
+ return resolveAwaitableService(PlanCoordinationServiceTag)
574
+ },
575
+ get planSchedulerService() {
576
+ return resolveAwaitableService(PlanSchedulerServiceTag)
577
+ },
578
+ get planAgentHeartbeatService() {
579
+ return resolveAwaitableService(PlanAgentHeartbeatServiceTag)
580
+ },
581
+ get planAgentQueryService() {
582
+ return resolveAwaitableService(PlanAgentQueryServiceTag)
583
+ },
584
+ get planCycleService() {
585
+ return resolveAwaitableService(PlanCycleServiceTag)
586
+ },
587
+ get threadMessageService() {
588
+ return resolveAwaitableService(ThreadMessageServiceTag)
589
+ },
590
+ get threadService() {
591
+ return resolveAwaitableService(ThreadServiceTag) as AwaitableThreadService
592
+ },
593
+ get threadTitleService() {
594
+ return resolveAwaitableService(ThreadTitleServiceTag)
595
+ },
596
+ createThreadApprovalContinuationStream: (...args) =>
597
+ runPromise(threadTurnService.createThreadApprovalContinuationStream(...args)),
598
+ createThreadNativeToolApprovalStream: (...args) =>
599
+ runPromise(threadTurnService.createThreadNativeToolApprovalStream(...args)),
600
+ createThreadTurnStream: (...args) => runPromise(threadTurnService.createThreadTurnStream(...args)),
601
+ isApprovalContinuationRequest: isApprovalContinuationRequestFn,
602
+ runThreadTurnInBackground: (...args) => runPromise(threadTurnService.runThreadTurnInBackground(...args)),
603
+ triggerPlanNodeTurn: (...args) => runPromise(threadTurnService.triggerPlanNodeTurn(...args)),
604
+ }
605
+
606
+ const lota: LotaRuntime['lota'] = {
607
+ organizations: {
608
+ create: (...args) => resolveAwaitableService(OrganizationServiceTag).createOrganization(...args),
609
+ upsert: (...args) => resolveAwaitableService(OrganizationServiceTag).upsertOrganization(...args),
610
+ get: (...args) => resolveAwaitableService(OrganizationServiceTag).getOrganization(...args),
611
+ list: (...args) => resolveAwaitableService(OrganizationServiceTag).listOrganizations(...args),
612
+ update: (...args) => resolveAwaitableService(OrganizationServiceTag).updateOrganization(...args),
613
+ delete: (...args) => resolveAwaitableService(OrganizationServiceTag).deleteOrganization(...args),
614
+ },
615
+ users: {
616
+ upsert: (...args) => resolveAwaitableService(UserServiceTag).upsertUser(...args),
617
+ get: (...args) => resolveAwaitableService(UserServiceTag).getUser(...args),
618
+ list: (...args) => resolveAwaitableService(UserServiceTag).listUsers(...args),
619
+ update: (...args) => resolveAwaitableService(UserServiceTag).updateUser(...args),
620
+ delete: (...args) => resolveAwaitableService(UserServiceTag).deleteUser(...args),
621
+ },
622
+ memberships: {
623
+ add: (...args) => resolveAwaitableService(OrganizationMemberServiceTag).addMembership(...args),
624
+ listForOrganization: (...args) =>
625
+ resolveAwaitableService(OrganizationMemberServiceTag).listMembershipsForOrganization(...args),
626
+ listForUser: (...args) =>
627
+ resolveAwaitableService(OrganizationMemberServiceTag).listMembershipsForUser(...args),
628
+ remove: (...args) => resolveAwaitableService(OrganizationMemberServiceTag).removeMembership(...args),
629
+ isMember: (...args) => resolveAwaitableService(OrganizationMemberServiceTag).isMember(...args),
630
+ },
631
+ threads: {
632
+ create: (...args) => resolveAwaitableService(ThreadServiceTag).createThread(...args),
633
+ list: (...args) => resolveAwaitableService(ThreadServiceTag).listThreads(...args),
634
+ get: (...args) => resolveAwaitableService(ThreadServiceTag).getThread(...args),
635
+ update: (...args) => resolveAwaitableService(ThreadServiceTag).updateTitle(...args),
636
+ archive: (threadId, status = 'archived') =>
637
+ resolveAwaitableService(ThreadServiceTag).updateStatus(threadId, status),
638
+ unarchive: (threadId, status = 'active') =>
639
+ resolveAwaitableService(ThreadServiceTag).updateStatus(threadId, status),
640
+ delete: (...args) => resolveAwaitableService(ThreadServiceTag).deleteThread(...args),
641
+ stop: (...args) => resolveAwaitableService(ThreadServiceTag).stopActiveRun(...args),
642
+ listMessages: (...args) => resolveAwaitableService(ThreadMessageServiceTag).listMessageHistoryPage(...args),
643
+ getMessage: ({ threadId, messageId }) =>
644
+ runPromise(
645
+ Effect.gen(function* () {
646
+ const messages = yield* resolve(ThreadMessageServiceTag).listMessages(
647
+ ensureRecordId(threadId, TABLES.THREAD),
648
+ )
649
+ const message = messages.find((candidate: ChatMessage) => candidate.id === messageId)
650
+ if (!message) {
651
+ return yield* new NotFoundError({
652
+ resource: 'Thread message',
653
+ id: messageId,
654
+ message: `Thread message not found: ${messageId}`,
655
+ })
656
+ }
657
+ return message
658
+ }),
659
+ ),
660
+ sendMessage: ({ threadId, organizationId, userId, userName, messages }) =>
661
+ runPromise(
662
+ Effect.gen(function* () {
663
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
664
+ const thread = yield* resolve(ThreadServiceTag).getThread(threadRef)
665
+ const routed = routeThreadChatMessages(messages)
666
+ if (routed.kind !== 'turn') {
667
+ return yield* new BadRequestError({
668
+ message: routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.',
669
+ })
670
+ }
671
+
672
+ return yield* threadTurnService.createThreadTurnStream({
673
+ thread,
674
+ threadRef,
675
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
676
+ userRef: ensureRecordId(userId, TABLES.USER),
677
+ userName,
678
+ inputMessage: routed.inputMessage,
679
+ })
680
+ }),
681
+ ),
682
+ continueApproval: ({ threadId, organizationId, userId, userName, messages }) =>
683
+ runPromise(
684
+ Effect.gen(function* () {
685
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
686
+ const thread = yield* resolve(ThreadServiceTag).getThread(threadRef)
687
+ const routed = routeThreadChatMessages(messages)
688
+ if (routed.kind !== 'approval-continuation') {
689
+ return yield* new BadRequestError({
690
+ message:
691
+ routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
692
+ })
693
+ }
694
+
695
+ return yield* threadTurnService.createThreadApprovalContinuationStream({
696
+ thread,
697
+ threadRef,
698
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
699
+ userRef: ensureRecordId(userId, TABLES.USER),
700
+ userName,
701
+ approvalMessages: routed.approvalMessages,
702
+ })
703
+ }),
704
+ ),
705
+ uploadAttachment: (...args) => resolveAwaitableService(AttachmentServiceTag).uploadThreadAttachment(...args),
706
+ },
707
+ }
708
+
709
+ let disconnectPromise: Promise<void> | null = null
710
+
711
+ const lotaRuntime: LotaRuntime = {
712
+ runPromise: (effect, options) => managedRuntime.runPromise(effect, options),
713
+ runSync: (effect) => managedRuntime.runSync(effect),
714
+ services,
715
+ lota,
716
+ redis: {
717
+ manager: redisManager,
718
+ subscriber: sharedSubscriber.subscriber,
719
+ getConnection: () => redisManager.getConnection(),
720
+ getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
721
+ closeConnection: () => runPromise(effectTryPromise(() => redisManager.closeConnection())),
722
+ },
723
+ workers,
724
+ socialChat,
725
+ schemaFiles,
726
+ contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
727
+ config: runtimeConfig,
728
+ plugins: pluginRuntime,
729
+ systemExecutors,
730
+ connectPluginDatabases: () => runPromise(effectTryPromise(() => connectPluginDatabases())),
731
+ connect: () =>
732
+ runPromise(
733
+ Effect.gen(function* () {
734
+ yield* db.connect()
735
+ const bunFiles = schemaFiles.map((schemaFile) =>
736
+ schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
737
+ )
738
+ yield* db.applySchema(bunFiles)
739
+ const schemaFingerprint = yield* effectTryPromise(() => computeSchemaFingerprint(schemaFiles))
740
+ yield* publishDatabaseBootstrapEffect({ databaseService: db, schemaFingerprint })
741
+ }),
742
+ ),
743
+ disconnect: () => {
744
+ if (disconnectPromise) {
745
+ return disconnectPromise
746
+ }
747
+
748
+ disconnectPromise = Effect.runPromise(
749
+ Effect.gen(function* () {
750
+ yield* Effect.ignore(effectTryPromise(() => socialChat.shutdown()))
751
+ yield* Effect.ignore(effectTryPromise(() => disconnectPluginDatabases()))
752
+ yield* effectTryPromise(() => managedRuntime.dispose())
753
+ }).pipe(
754
+ Effect.ensuring(
755
+ Effect.sync(() => {
756
+ clearLotaSdkRuntime()
757
+ if (runtimeToken) releaseRuntimeToken(runtimeToken)
758
+ }),
759
+ ),
760
+ ),
761
+ )
762
+ return disconnectPromise
763
+ },
764
+ }
765
+ return lotaRuntime
766
+ }),
767
+ ).catch((error) => {
768
+ if (effectRuntime) {
769
+ clearLotaSdkRuntime()
770
+ void effectRuntime.dispose().catch(() => undefined)
475
771
  }
476
- } catch (error) {
477
- releaseRuntimeToken(runtimeToken)
772
+ if (runtimeToken) releaseRuntimeToken(runtimeToken)
478
773
  throw error
479
- }
774
+ })
480
775
  }
481
776
 
482
777
  function getBuiltInSchemaFiles(): URL[] {
@@ -496,16 +791,59 @@ function getBuiltInSchemaFiles(): URL[] {
496
791
  ]
497
792
  }
498
793
 
499
- function createPluginDatabaseConnector(pluginRuntime: Record<string, LotaPlugin>): () => Promise<void> {
500
- return async () => {
501
- for (const plugin of Object.values(pluginRuntime)) {
502
- const services = plugin.services
503
- const connectDatabase = services.connectDatabase
504
- if (typeof connectDatabase !== 'function') {
505
- continue
506
- }
794
+ function getPluginLifecycleServices(plugin: LotaPlugin): PluginLifecycleServices {
795
+ return plugin.services as PluginLifecycleServices
796
+ }
507
797
 
508
- await Reflect.apply(connectDatabase, services, [])
509
- }
510
- }
798
+ function createPluginDatabaseConnector(
799
+ pluginRuntime: Record<string, LotaPlugin>,
800
+ connectedPluginDatabases: Set<string>,
801
+ ): () => Promise<void> {
802
+ return () =>
803
+ Effect.runPromise(
804
+ Effect.gen(function* () {
805
+ for (const [pluginName, plugin] of Object.entries(pluginRuntime)) {
806
+ if (connectedPluginDatabases.has(pluginName)) {
807
+ continue
808
+ }
809
+
810
+ const services = getPluginLifecycleServices(plugin)
811
+ const connectDatabase = Reflect.get(services, 'connectDatabase')
812
+ if (typeof connectDatabase !== 'function') {
813
+ continue
814
+ }
815
+
816
+ const connectDatabaseFn = connectDatabase as (this: typeof services) => Promise<void>
817
+ yield* effectTryPromise(() => connectDatabaseFn.call(services))
818
+ connectedPluginDatabases.add(pluginName)
819
+ }
820
+ }),
821
+ )
822
+ }
823
+
824
+ function createPluginDatabaseDisconnector(
825
+ pluginRuntime: Record<string, LotaPlugin>,
826
+ connectedPluginDatabases: Set<string>,
827
+ ): () => Promise<void> {
828
+ return () =>
829
+ Effect.runPromise(
830
+ Effect.gen(function* () {
831
+ for (const [pluginName, plugin] of Object.entries(pluginRuntime)) {
832
+ if (!connectedPluginDatabases.has(pluginName)) {
833
+ continue
834
+ }
835
+
836
+ const services = getPluginLifecycleServices(plugin)
837
+ const disconnectDatabase = Reflect.get(services, 'disconnectDatabase')
838
+ if (typeof disconnectDatabase !== 'function') {
839
+ connectedPluginDatabases.delete(pluginName)
840
+ continue
841
+ }
842
+
843
+ const disconnectDatabaseFn = disconnectDatabase as (this: typeof services) => Promise<void>
844
+ yield* effectTryPromise(() => disconnectDatabaseFn.call(services))
845
+ connectedPluginDatabases.delete(pluginName)
846
+ }
847
+ }),
848
+ )
511
849
  }