@lota-sdk/core 0.4.7 → 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/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  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,119 +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'
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'
66
64
  import {
67
- configureSocialChatHistory,
68
- socialChatHistoryService as socialChatHistoryServiceSingleton,
69
- } from './services/social-chat-history.service'
70
- import { getBuiltInSystemExecutors } from './services/system-executor.service'
71
- import type { threadMessageService } from './services/thread-message.service'
72
- import { threadMessageService as threadMessageServiceSingleton } from './services/thread-message.service'
73
- import type { threadTitleService } from './services/thread-title.service'
74
- 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'
75
95
  import type {
76
96
  createThreadApprovalContinuationStream,
77
97
  createThreadNativeToolApprovalStream,
78
98
  createThreadTurnStream,
79
99
  runThreadTurnInBackground,
80
100
  triggerPlanNodeTurn,
81
- } 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'
82
112
  import {
83
- createThreadApprovalContinuationStream as createThreadApprovalContinuationStreamSingleton,
84
- createThreadNativeToolApprovalStream as createThreadNativeToolApprovalStreamSingleton,
85
- createThreadTurnStream as createThreadTurnStreamSingleton,
86
- isApprovalContinuationRequest as isApprovalContinuationRequestSingleton,
87
- runThreadTurnInBackground as runThreadTurnInBackgroundSingleton,
88
- triggerPlanNodeTurn as triggerPlanNodeTurnSingleton,
89
- } from './services/thread-turn'
90
- import type { threadService } from './services/thread.service'
91
- import { threadService as threadServiceSingleton } from './services/thread.service'
92
- import type { userService } from './services/user.service'
93
- import { userService as userServiceSingleton } from './services/user.service'
94
- import type { generatedDocumentStorageService } from './storage/generated-document-storage.service'
95
- 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'>
96
125
 
97
126
  type ArchiveSdkThread = (
98
- threadId: Parameters<typeof threadServiceSingleton.updateStatus>[0],
127
+ threadId: Parameters<Svc<typeof ThreadServiceTag>['updateStatus']>[0],
99
128
  status?: 'archived',
100
- ) => ReturnType<typeof threadServiceSingleton.updateStatus>
129
+ ) => ReturnType<Svc<typeof ThreadServiceTag>['updateStatus']>
101
130
 
102
131
  type UnarchiveSdkThread = (
103
- threadId: Parameters<typeof threadServiceSingleton.updateStatus>[0],
132
+ threadId: Parameters<Svc<typeof ThreadServiceTag>['updateStatus']>[0],
104
133
  status?: 'active',
105
- ) => ReturnType<typeof threadServiceSingleton.updateStatus>
134
+ ) => ReturnType<Svc<typeof ThreadServiceTag>['updateStatus']>
106
135
 
107
136
  let activeRuntimeToken: symbol | null = null
108
137
 
109
- function claimRuntimeToken(): symbol {
110
- if (activeRuntimeToken) {
111
- throw new Error('createLotaRuntime() is process-scoped. Disconnect the active runtime before creating another one.')
112
- }
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
+ }
113
145
 
114
- const token = Symbol('lota-runtime')
115
- activeRuntimeToken = token
116
- return token
146
+ const token = Symbol('lota-runtime')
147
+ activeRuntimeToken = token
148
+ return token
149
+ })
117
150
  }
118
151
 
119
152
  function releaseRuntimeToken(token: symbol) {
@@ -122,35 +155,69 @@ function releaseRuntimeToken(token: symbol) {
122
155
  }
123
156
  }
124
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
+
125
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
126
188
  services: {
127
189
  database: SurrealDBService
128
190
  redis: RedisConnectionManager
129
191
  closeRedisConnection: () => Promise<void>
130
- agentActivityService: typeof agentActivityService
131
- artifactService: typeof artifactService
132
- attachmentService: typeof attachmentService
133
- autonomousJobService: typeof autonomousJobService
134
- documentChunkService: typeof documentChunkService
135
- generatedDocumentStorageService: typeof generatedDocumentStorageService
136
- memoryService: typeof memoryService
137
- verifyMutatingApproval: typeof verifyMutatingApproval
138
- organizationService: typeof organizationService
139
- organizationMemberService: typeof organizationMemberService
140
- userService: typeof userService
141
- recentActivityService: typeof recentActivityService
142
- recentActivityTitleService: typeof recentActivityTitleService
143
- socialChatHistoryService: typeof socialChatHistoryServiceSingleton
144
- executionPlanService: typeof executionPlanService
145
- planTemplateService: typeof planTemplateService
146
- planCoordinationService: typeof planCoordinationService
147
- planSchedulerService: typeof planSchedulerService
148
- planAgentHeartbeatService: typeof planAgentHeartbeatService
149
- planAgentQueryService: typeof planAgentQueryService
150
- planCycleService: typeof planCycleService
151
- threadMessageService: typeof threadMessageService
152
- threadService: typeof threadService
153
- 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>
154
221
  createThreadApprovalContinuationStream: typeof createThreadApprovalContinuationStream
155
222
  createThreadNativeToolApprovalStream: typeof createThreadNativeToolApprovalStream
156
223
  createThreadTurnStream: typeof createThreadTurnStream
@@ -160,37 +227,37 @@ export interface LotaRuntime {
160
227
  }
161
228
  lota: {
162
229
  organizations: {
163
- create: typeof organizationServiceSingleton.createOrganization
164
- upsert: typeof organizationServiceSingleton.upsertOrganization
165
- get: typeof organizationServiceSingleton.getOrganization
166
- list: typeof organizationServiceSingleton.listOrganizations
167
- update: typeof organizationServiceSingleton.updateOrganization
168
- 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']>
169
236
  }
170
237
  users: {
171
- upsert: typeof userServiceSingleton.upsertUser
172
- get: typeof userServiceSingleton.getUser
173
- list: typeof userServiceSingleton.listUsers
174
- update: typeof userServiceSingleton.updateUser
175
- 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']>
176
243
  }
177
244
  memberships: {
178
- add: typeof organizationMemberServiceSingleton.addMembership
179
- listForOrganization: typeof organizationMemberServiceSingleton.listMembershipsForOrganization
180
- listForUser: typeof organizationMemberServiceSingleton.listMembershipsForUser
181
- remove: typeof organizationMemberServiceSingleton.removeMembership
182
- 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']>
183
250
  }
184
251
  threads: {
185
- create: typeof threadServiceSingleton.createThread
186
- list: typeof threadServiceSingleton.listThreads
187
- get: typeof threadServiceSingleton.getThread
188
- update: typeof threadServiceSingleton.updateTitle
189
- archive: ArchiveSdkThread
190
- unarchive: UnarchiveSdkThread
191
- delete: typeof threadServiceSingleton.deleteThread
192
- stop: typeof threadServiceSingleton.stopActiveRun
193
- 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']>
194
261
  getMessage: (params: { threadId: string; messageId: string }) => Promise<ChatMessage>
195
262
  sendMessage: (params: {
196
263
  threadId: string
@@ -206,11 +273,12 @@ export interface LotaRuntime {
206
273
  userName: string
207
274
  messages: Parameters<typeof routeThreadChatMessages>[0]
208
275
  }) => Promise<Awaited<ReturnType<typeof createThreadApprovalContinuationStream>>>
209
- uploadAttachment: typeof attachmentServiceSingleton.uploadThreadAttachment
276
+ uploadAttachment: AwaitableValue<Svc<typeof AttachmentServiceTag>['uploadThreadAttachment']>
210
277
  }
211
278
  }
212
279
  redis: {
213
280
  manager: RedisConnectionManager
281
+ subscriber: Subscriber
214
282
  getConnection: () => ReturnType<RedisConnectionManager['getConnection']>
215
283
  getConnectionForBullMQ: () => ReturnType<RedisConnectionManager['getConnectionForBullMQ']>
216
284
  closeConnection: () => Promise<void>
@@ -227,252 +295,483 @@ export interface LotaRuntime {
227
295
  disconnect(): Promise<void>
228
296
  }
229
297
 
230
- export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
231
- const runtimeToken = claimRuntimeToken()
232
-
233
- try {
234
- const resolvedConfig = parseLotaRuntimeConfig(config)
235
- const systemExecutors = { ...getBuiltInSystemExecutors(), ...resolvedConfig.systemExecutors }
236
- const runtimeConfig = { ...resolvedConfig, systemExecutors } satisfies ResolvedLotaRuntimeConfig
237
- configureRuntimeConfig(runtimeConfig)
238
-
239
- await configureLotaLogger(runtimeConfig.logging.level)
240
-
241
- const db = new SurrealDBServiceClass({
242
- url: runtimeConfig.database.url,
243
- namespace: runtimeConfig.database.namespace,
244
- database: LOTA_SDK_DATABASE_NAME,
245
- username: runtimeConfig.database.username,
246
- password: runtimeConfig.database.password,
247
- })
248
- setDatabaseService(db)
249
-
250
- const redisManager = createRedisConnectionManager({ url: runtimeConfig.redis.url })
251
- setRedisConnectionManager(redisManager)
252
- configureEmbeddingCache(redisManager.getConnection(), runtimeConfig.memory.embeddingCacheTtlSeconds)
253
- configureSocialChatHistory({ keyPrefix: runtimeConfig.socialChat?.historyRedisKeyPrefix })
254
-
255
- const socialChatAgentId = runtimeConfig.socialChat?.agentId?.trim() || 'socialChat'
256
- const socialChatAgentDisplayName = runtimeConfig.socialChat?.agentDisplayName?.trim() || 'Lota'
257
- if (runtimeConfig.socialChat && !runtimeConfig.agents.roster.includes(socialChatAgentId)) {
258
- throw new Error(`socialChat.agentId must be present in agents.roster: ${socialChatAgentId}`)
259
- }
260
- const agentDisplayNames = runtimeConfig.socialChat
261
- ? { ...runtimeConfig.agents.displayNames, [socialChatAgentId]: socialChatAgentDisplayName }
262
- : runtimeConfig.agents.displayNames
263
-
264
- configureAgents({
265
- roster: runtimeConfig.agents.roster,
266
- leadAgentId: runtimeConfig.agents.leadAgentId,
267
- displayNames: agentDisplayNames,
268
- shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
269
- descriptions: runtimeConfig.agents.descriptions,
270
- routerModelId: runtimeConfig.agents.routerModelId,
271
- teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
272
- getCoreThreadProfile: runtimeConfig.agents.getCoreThreadProfile,
273
- })
274
- configureAgentFactory({
275
- createAgent: runtimeConfig.agents.createAgent,
276
- buildAgentTools: runtimeConfig.agents.buildAgentTools,
277
- getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
278
- pluginRuntime: runtimeConfig.pluginRuntime,
279
- })
280
- configureThreads({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads })
281
- configureNotificationService(runtimeConfig.notificationService ?? null)
282
- configureRuntimeExtensions({
283
- adapters: runtimeConfig.runtimeAdapters,
284
- turnHooks: runtimeConfig.turnHooks,
285
- toolProviders: (runtimeConfig.toolProviders ?? {}) as never,
286
- extraWorkers: runtimeConfig.extraWorkers,
287
- })
288
-
289
- const pluginRuntime = runtimeConfig.pluginRuntime ?? {}
290
- if (runtimeConfig.graphDesigner) {
291
- configureGraphDesigner(runtimeConfig.graphDesigner)
292
- }
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
293
329
 
294
- const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
295
- const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
296
- const schemaFiles = [
297
- ...getBuiltInSchemaFiles(),
298
- ...(runtimeConfig.extraSchemaFiles ?? []),
299
- ...hostContributionSchemaFiles,
300
- ]
301
- const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
302
- const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
303
- const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
304
- const socialChat = createSocialChatRuntime({
305
- redisClient: redisManager.getConnection(),
306
- socialChat: runtimeConfig.socialChat,
307
- })
308
-
309
- const lota = {
310
- organizations: {
311
- create: organizationServiceSingleton.createOrganization.bind(organizationServiceSingleton),
312
- upsert: organizationServiceSingleton.upsertOrganization.bind(organizationServiceSingleton),
313
- get: organizationServiceSingleton.getOrganization.bind(organizationServiceSingleton),
314
- list: organizationServiceSingleton.listOrganizations.bind(organizationServiceSingleton),
315
- update: organizationServiceSingleton.updateOrganization.bind(organizationServiceSingleton),
316
- delete: organizationServiceSingleton.deleteOrganization.bind(organizationServiceSingleton),
317
- },
318
- users: {
319
- upsert: userServiceSingleton.upsertUser.bind(userServiceSingleton),
320
- get: userServiceSingleton.getUser.bind(userServiceSingleton),
321
- list: userServiceSingleton.listUsers.bind(userServiceSingleton),
322
- update: userServiceSingleton.updateUser.bind(userServiceSingleton),
323
- delete: userServiceSingleton.deleteUser.bind(userServiceSingleton),
324
- },
325
- memberships: {
326
- add: organizationMemberServiceSingleton.addMembership.bind(organizationMemberServiceSingleton),
327
- listForOrganization: organizationMemberServiceSingleton.listMembershipsForOrganization.bind(
328
- 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,
329
354
  ),
330
- listForUser: organizationMemberServiceSingleton.listMembershipsForUser.bind(organizationMemberServiceSingleton),
331
- remove: organizationMemberServiceSingleton.removeMembership.bind(organizationMemberServiceSingleton),
332
- isMember: organizationMemberServiceSingleton.isMember.bind(organizationMemberServiceSingleton),
333
- },
334
- threads: {
335
- create: threadServiceSingleton.createThread.bind(threadServiceSingleton),
336
- list: threadServiceSingleton.listThreads.bind(threadServiceSingleton),
337
- get: threadServiceSingleton.getThread.bind(threadServiceSingleton),
338
- update: threadServiceSingleton.updateTitle.bind(threadServiceSingleton),
339
- archive: async (threadId, status = 'archived') => await threadServiceSingleton.updateStatus(threadId, status),
340
- unarchive: async (threadId, status = 'active') => await threadServiceSingleton.updateStatus(threadId, status),
341
- delete: threadServiceSingleton.deleteThread.bind(threadServiceSingleton),
342
- stop: threadServiceSingleton.stopActiveRun.bind(threadServiceSingleton),
343
- listMessages: threadMessageServiceSingleton.listMessageHistoryPage.bind(threadMessageServiceSingleton),
344
- getMessage: async ({ threadId, messageId }) => {
345
- const messages = await threadMessageServiceSingleton.listMessages(ensureRecordId(threadId, TABLES.THREAD))
346
- const message = messages.find((candidate) => candidate.id === messageId)
347
- if (!message) {
348
- throw new Error(`Thread message not found: ${messageId}`)
349
- }
350
- return message
351
- },
352
- sendMessage: async ({ threadId, organizationId, userId, userName, messages }) => {
353
- const threadRef = ensureRecordId(threadId, TABLES.THREAD)
354
- const thread = await threadServiceSingleton.getThread(threadRef)
355
- const routed = routeThreadChatMessages(messages)
356
- if (routed.kind !== 'turn') {
357
- throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
358
- }
355
+ infrastructureLayer,
356
+ )
357
+ const ctx0 = Layer.mergeAll(
358
+ infrastructureLayer,
359
+ provide(Layer.mergeAll(AiGatewayLive, EmbeddingCacheLive, FirecrawlLive, HelperModelLive), infrastructureLayer),
360
+ tier0,
361
+ )
359
362
 
360
- return createThreadTurnStreamSingleton({
361
- thread,
362
- threadRef,
363
- orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
364
- userRef: ensureRecordId(userId, TABLES.USER),
365
- userName,
366
- inputMessage: routed.inputMessage,
367
- })
368
- },
369
- continueApproval: async ({ threadId, organizationId, userId, userName, messages }) => {
370
- const threadRef = ensureRecordId(threadId, TABLES.THREAD)
371
- const thread = await threadServiceSingleton.getThread(threadRef)
372
- const routed = routeThreadChatMessages(messages)
373
- if (routed.kind !== 'approval-continuation') {
374
- throw new Error(
375
- routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
376
- )
377
- }
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)
378
389
 
379
- return createThreadApprovalContinuationStreamSingleton({
380
- thread,
381
- threadRef,
382
- orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
383
- userRef: ensureRecordId(userId, TABLES.USER),
384
- userName,
385
- approvalMessages: routed.approvalMessages,
386
- })
387
- },
388
- uploadAttachment: attachmentServiceSingleton.uploadThreadAttachment.bind(attachmentServiceSingleton),
389
- },
390
- } 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)
391
477
 
392
- 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) })
393
485
 
394
- return {
395
- 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'] = {
396
508
  database: db,
397
509
  redis: redisManager,
398
- closeRedisConnection: async () => await redisManager.closeConnection(),
399
- agentActivityService: agentActivityServiceSingleton,
400
- artifactService: artifactServiceSingleton,
401
- attachmentService: attachmentServiceSingleton,
402
- autonomousJobService: autonomousJobServiceSingleton,
403
- documentChunkService: documentChunkServiceSingleton,
404
- generatedDocumentStorageService: generatedDocumentStorageServiceSingleton,
405
- memoryService: memoryServiceSingleton,
406
- verifyMutatingApproval: verifyMutatingApprovalSingleton,
407
- organizationService: organizationServiceSingleton,
408
- organizationMemberService: organizationMemberServiceSingleton,
409
- userService: userServiceSingleton,
410
- recentActivityService: recentActivityServiceSingleton,
411
- recentActivityTitleService: recentActivityTitleServiceSingleton,
412
- socialChatHistoryService: socialChatHistoryServiceSingleton,
413
- executionPlanService: executionPlanServiceSingleton,
414
- planTemplateService: planTemplateServiceSingleton,
415
- planCoordinationService: planCoordinationServiceSingleton,
416
- planSchedulerService: planSchedulerServiceSingleton,
417
- planAgentHeartbeatService: planAgentHeartbeatServiceSingleton,
418
- planAgentQueryService: planAgentQueryServiceSingleton,
419
- planCycleService: planCycleServiceSingleton,
420
- threadMessageService: threadMessageServiceSingleton,
421
- threadService: threadServiceSingleton,
422
- threadTitleService: threadTitleServiceSingleton,
423
- createThreadApprovalContinuationStream: createThreadApprovalContinuationStreamSingleton,
424
- createThreadNativeToolApprovalStream: createThreadNativeToolApprovalStreamSingleton,
425
- createThreadTurnStream: createThreadTurnStreamSingleton,
426
- isApprovalContinuationRequest: isApprovalContinuationRequestSingleton,
427
- runThreadTurnInBackground: runThreadTurnInBackgroundSingleton,
428
- triggerPlanNodeTurn: triggerPlanNodeTurnSingleton,
429
- },
430
- lota,
431
- redis: {
432
- manager: redisManager,
433
- getConnection: () => redisManager.getConnection(),
434
- getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
435
- closeConnection: async () => await redisManager.closeConnection(),
436
- },
437
- workers,
438
- socialChat,
439
- schemaFiles,
440
- contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
441
- config: runtimeConfig,
442
- plugins: pluginRuntime,
443
- systemExecutors,
444
- async connectPluginDatabases() {
445
- await connectPluginDatabases()
446
- },
447
- async connect() {
448
- await db.connect()
449
- const bunFiles = schemaFiles.map((schemaFile) =>
450
- schemaFile instanceof URL ? Bun.file(schemaFile.pathname) : Bun.file(schemaFile),
451
- )
452
- await db.applySchema(bunFiles)
453
- const schemaFingerprint = await computeSchemaFingerprint(schemaFiles)
454
- await publishDatabaseBootstrap({ databaseService: db, schemaFingerprint })
455
- },
456
- async disconnect() {
457
- if (disconnected) {
458
- return
459
- }
460
- disconnected = true
461
-
462
- try {
463
- await socialChat.shutdown()
464
- await closeSharedSubscriber()
465
- await db.disconnect()
466
- await redisManager.closeConnection()
467
- } finally {
468
- releaseRuntimeToken(runtimeToken)
469
- }
470
- },
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)
471
771
  }
472
- } catch (error) {
473
- releaseRuntimeToken(runtimeToken)
772
+ if (runtimeToken) releaseRuntimeToken(runtimeToken)
474
773
  throw error
475
- }
774
+ })
476
775
  }
477
776
 
478
777
  function getBuiltInSchemaFiles(): URL[] {
@@ -492,16 +791,59 @@ function getBuiltInSchemaFiles(): URL[] {
492
791
  ]
493
792
  }
494
793
 
495
- function createPluginDatabaseConnector(pluginRuntime: Record<string, LotaPlugin>): () => Promise<void> {
496
- return async () => {
497
- for (const plugin of Object.values(pluginRuntime)) {
498
- const services = plugin.services
499
- const connectDatabase = services.connectDatabase
500
- if (typeof connectDatabase !== 'function') {
501
- continue
502
- }
794
+ function getPluginLifecycleServices(plugin: LotaPlugin): PluginLifecycleServices {
795
+ return plugin.services as PluginLifecycleServices
796
+ }
503
797
 
504
- await Reflect.apply(connectDatabase, services, [])
505
- }
506
- }
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
+ )
507
849
  }