@lota-sdk/core 0.4.8 → 0.4.10

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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -0,0 +1,344 @@
1
+ import { THREAD, sdkThreadStatusSchema } from '@lota-sdk/shared'
2
+ import { Context, Effect, Layer } from 'effect'
3
+ import { surql } from 'surrealdb'
4
+
5
+ import { getCoreThreadProfile, isAgentName } from '../../config/agent-defaults'
6
+ import { ensureRecordId, isRecordIdInput, recordIdToString } from '../../db/record-id'
7
+ import type { RecordIdRef } from '../../db/record-id'
8
+ import type { SurrealDBService } from '../../db/service'
9
+ import { TABLES } from '../../db/tables'
10
+ import { BadRequestError, ServiceError } from '../../effect/errors'
11
+ import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
12
+ import { DatabaseServiceTag, RedisServiceTag } from '../../effect/services'
13
+ import type { RedisConnectionManager } from '../../redis/connection'
14
+ import { CompactionCoordinationTag } from '../../runtime/chat-run-orchestration'
15
+ import { toIsoDateTimeString } from '../../utils/date-time'
16
+ import { BackgroundWorkService } from '../background-work.service'
17
+ import { ChatRunRegistryTag } from '../chat-run-registry.service'
18
+ import { ContextCompactionServiceTag } from '../context-compaction.service'
19
+ import type { makeContextCompactionService } from '../context-compaction.service'
20
+ import { createThreadActiveRunHelpers } from './thread-active-run'
21
+ import { createThreadBootstrapHelpers } from './thread-bootstrap'
22
+ import { createThreadListingHelpers } from './thread-listing'
23
+ import { createThreadMemoryBlockHelpers, formatMemoryBlockForPrompt } from './thread-memory-block'
24
+ import { ThreadMessageServiceTag } from './thread-message.service'
25
+ import type { makeThreadMessageService } from './thread-message.service'
26
+ import { createThreadRecordStore } from './thread-record-store'
27
+ import { NormalizedThreadSchema, PublicThreadSchema } from './thread.types'
28
+ import type { NormalizedThread, PublicThread, ThreadRecord } from './thread.types'
29
+
30
+ export { ActiveThreadRunConflictError } from '../../effect/errors'
31
+
32
+ function assertMutableThreadEffect(thread: ThreadRecord): Effect.Effect<void, BadRequestError> {
33
+ return thread.type === 'default'
34
+ ? Effect.fail(new BadRequestError({ message: 'Default threads cannot be modified.' }))
35
+ : Effect.void
36
+ }
37
+
38
+ function getDefaultTitle(thread: Pick<ThreadRecord, 'type' | 'threadType'>): string {
39
+ if (thread.type === 'thread' && typeof thread.threadType === 'string') {
40
+ return getCoreThreadProfile(thread.threadType).config.title
41
+ }
42
+
43
+ return THREAD.DEFAULT_TITLE
44
+ }
45
+
46
+ function normalizeRecordIdStringEffect(id: unknown, table: string): Effect.Effect<string, BadRequestError> {
47
+ return isRecordIdInput(id)
48
+ ? Effect.succeed(recordIdToString(ensureRecordId(id, table), table))
49
+ : Effect.fail(new BadRequestError({ message: `Invalid record id for table ${table}.` }))
50
+ }
51
+
52
+ function toPublicThread(thread: NormalizedThread): PublicThread {
53
+ const { organizationId: _organizationId, userId: _userId, memoryBlock: _memoryBlock, ...publicThread } = thread
54
+ return PublicThreadSchema.parse(publicThread)
55
+ }
56
+
57
+ type ThreadServiceError = ServiceError | BadRequestError
58
+
59
+ const effectTryPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
60
+
61
+ type ChatRunRegistry = Context.Service.Shape<typeof ChatRunRegistryTag>
62
+
63
+ type CompactionCoordination = Context.Service.Shape<typeof CompactionCoordinationTag>
64
+
65
+ interface ThreadServiceDeps {
66
+ db: SurrealDBService
67
+ redis: RedisConnectionManager
68
+ chatRunRegistry: ChatRunRegistry
69
+ threadMessageService: ReturnType<typeof makeThreadMessageService>
70
+ contextCompactionService: ReturnType<typeof makeContextCompactionService>
71
+ compactionCoordination: CompactionCoordination
72
+ background: Context.Service.Shape<typeof BackgroundWorkService>
73
+ }
74
+
75
+ export function makeThreadService(deps: ThreadServiceDeps) {
76
+ const threadStore = createThreadRecordStore({ db: deps.db })
77
+ const activeRun = createThreadActiveRunHelpers({
78
+ db: deps.db,
79
+ threadStore,
80
+ redis: deps.redis,
81
+ chatRunRegistry: deps.chatRunRegistry,
82
+ })
83
+
84
+ function computeIsRunning(
85
+ thread: Pick<ThreadRecord, 'id' | 'activeRunId'>,
86
+ options: { checkLease: boolean },
87
+ ): Effect.Effect<boolean, ThreadServiceError> {
88
+ const activeRunId =
89
+ typeof thread.activeRunId === 'string' && thread.activeRunId.trim().length > 0 ? thread.activeRunId : null
90
+
91
+ if (activeRunId === null) {
92
+ return Effect.succeed(false)
93
+ }
94
+
95
+ if (deps.chatRunRegistry.has(activeRunId)) {
96
+ return Effect.succeed(true)
97
+ }
98
+
99
+ if (!options.checkLease) {
100
+ return Effect.succeed(true)
101
+ }
102
+
103
+ return effectTryPromise(
104
+ () => activeRun.hasActiveRunLease(ensureRecordId(thread.id, TABLES.THREAD)),
105
+ 'Failed to check active thread run lease.',
106
+ )
107
+ }
108
+
109
+ function toNormalizedThread(
110
+ thread: ThreadRecord,
111
+ options: { checkLease?: boolean } = {},
112
+ ): Effect.Effect<NormalizedThread, ThreadServiceError> {
113
+ return Effect.gen(function* () {
114
+ const isRunning = yield* computeIsRunning(thread, { checkLease: options.checkLease ?? true })
115
+ const [id, userId, organizationId] = yield* Effect.all([
116
+ normalizeRecordIdStringEffect(thread.id, TABLES.THREAD),
117
+ normalizeRecordIdStringEffect(thread.userId, TABLES.USER),
118
+ normalizeRecordIdStringEffect(thread.organizationId, TABLES.ORGANIZATION),
119
+ ])
120
+ const candidate = {
121
+ id,
122
+ userId,
123
+ organizationId,
124
+ type: thread.type,
125
+ ...(thread.type === 'thread' && typeof thread.threadType === 'string' ? { threadType: thread.threadType } : {}),
126
+ nameGenerated: thread.nameGenerated,
127
+ isRunning,
128
+ isCompacting: thread.isCompacting === true,
129
+ ...(isAgentName(thread.agentId) ? { agentId: thread.agentId } : {}),
130
+ title: thread.title ?? getDefaultTitle(thread),
131
+ status: thread.status,
132
+ memoryBlock: formatMemoryBlockForPrompt(thread),
133
+ members: thread.members,
134
+ createdAt: toIsoDateTimeString(thread.createdAt),
135
+ updatedAt: toIsoDateTimeString(thread.updatedAt),
136
+ }
137
+ return yield* Effect.try({
138
+ try: () => NormalizedThreadSchema.parse(candidate),
139
+ catch: (cause) => new ServiceError({ message: 'Failed to parse normalized thread.', cause }),
140
+ })
141
+ })
142
+ }
143
+
144
+ function toNormalizedThreads(
145
+ threads: ThreadRecord[],
146
+ options: { checkLease?: boolean } = {},
147
+ ): Effect.Effect<NormalizedThread[], ThreadServiceError> {
148
+ return Effect.forEach(threads, (thread) => toNormalizedThread(thread, options))
149
+ }
150
+
151
+ const bootstrap = createThreadBootstrapHelpers({
152
+ threadStore,
153
+ threadMessageService: deps.threadMessageService,
154
+ redis: deps.redis,
155
+ normalizeThread: toNormalizedThread,
156
+ })
157
+
158
+ const listing = createThreadListingHelpers({ db: deps.db, normalizeThreads: toNormalizedThreads })
159
+
160
+ const memory = createThreadMemoryBlockHelpers({
161
+ threadStore,
162
+ contextCompactionService: { compactMemoryBlock: deps.contextCompactionService.compactMemoryBlock },
163
+ background: deps.background,
164
+ })
165
+
166
+ function getById(threadId: RecordIdRef) {
167
+ return effectTryPromise(() => threadStore.getById(threadId), 'Failed to load thread.')
168
+ }
169
+
170
+ function getThread(threadId: RecordIdRef) {
171
+ return Effect.gen(function* () {
172
+ const thread = yield* getById(threadId)
173
+ return yield* toNormalizedThread(thread)
174
+ })
175
+ }
176
+
177
+ function updateTitle(threadId: RecordIdRef, title: string) {
178
+ return Effect.gen(function* () {
179
+ const existing = yield* getById(threadId)
180
+ yield* assertMutableThreadEffect(existing)
181
+ const thread = yield* effectTryPromise(
182
+ () => threadStore.update(threadId, { title, nameGenerated: true }),
183
+ 'Failed to update thread title.',
184
+ )
185
+ return yield* toNormalizedThread(thread)
186
+ })
187
+ }
188
+
189
+ function updateStatus(threadId: RecordIdRef, status: string) {
190
+ return Effect.gen(function* () {
191
+ const parsedStatus = sdkThreadStatusSchema.safeParse(status)
192
+ if (!parsedStatus.success) {
193
+ return yield* new BadRequestError({ message: `Invalid thread status: ${status}` })
194
+ }
195
+ const existing = yield* getById(threadId)
196
+ yield* assertMutableThreadEffect(existing)
197
+ const thread = yield* effectTryPromise(
198
+ () => threadStore.update(threadId, { status: parsedStatus.data }),
199
+ 'Failed to update thread status.',
200
+ )
201
+ return yield* toNormalizedThread(thread)
202
+ })
203
+ }
204
+
205
+ function setCompacting(threadId: RecordIdRef, value: boolean) {
206
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
207
+ const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
208
+ return Effect.asVoid(
209
+ effectTryPromise(
210
+ () => deps.db.query<unknown>(surql`UPDATE ONLY ${threadRef} SET isCompacting = ${value}`),
211
+ 'Failed to update thread compaction flag.',
212
+ ).pipe(Effect.tap(() => deps.compactionCoordination.signal(threadIdString, value))),
213
+ )
214
+ }
215
+
216
+ function clearThread(threadId: RecordIdRef) {
217
+ return Effect.gen(function* () {
218
+ const existing = yield* getById(threadId)
219
+ yield* assertMutableThreadEffect(existing)
220
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
221
+ yield* effectTryPromise(
222
+ () => deps.db.deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef }),
223
+ 'Failed to delete thread messages.',
224
+ )
225
+ yield* effectTryPromise(
226
+ () =>
227
+ deps.db.query<unknown>(surql`
228
+ UPDATE ONLY ${threadRef}
229
+ SET turnCount = 0,
230
+ compactionSummary = NONE,
231
+ lastCompactedMessageId = NONE,
232
+ activeRunId = NONE,
233
+ activeStreamId = NONE,
234
+ isCompacting = false
235
+ `),
236
+ 'Failed to reset thread state.',
237
+ )
238
+ })
239
+ }
240
+
241
+ function deleteThread(threadId: RecordIdRef) {
242
+ return Effect.gen(function* () {
243
+ const existing = yield* getById(threadId)
244
+ yield* assertMutableThreadEffect(existing)
245
+ yield* effectTryPromise(() => threadStore.deleteById(threadId), 'Failed to delete thread.')
246
+ })
247
+ }
248
+
249
+ function incrementTurnCount(threadId: RecordIdRef) {
250
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
251
+ return Effect.gen(function* () {
252
+ const result = yield* effectTryPromise(
253
+ () =>
254
+ deps.db.query<{ turnCount: number }>(
255
+ surql`
256
+ UPDATE ONLY ${threadRef}
257
+ SET turnCount += 1
258
+ RETURN turnCount
259
+ `,
260
+ ),
261
+ 'Failed to increment thread turn count.',
262
+ )
263
+ return result[0].turnCount
264
+ })
265
+ }
266
+
267
+ return {
268
+ findById: (...args: Parameters<typeof threadStore.findById>) =>
269
+ effectTryPromise(() => threadStore.findById(...args), 'Failed to find thread.'),
270
+ getById,
271
+ findAll: (...args: Parameters<typeof threadStore.findAll>) =>
272
+ effectTryPromise(() => threadStore.findAll(...args), 'Failed to find all threads.'),
273
+ create: (...args: Parameters<typeof threadStore.create>) =>
274
+ effectTryPromise(() => threadStore.create(...args), 'Failed to create thread.'),
275
+ update: (...args: Parameters<typeof threadStore.update>) =>
276
+ effectTryPromise(() => threadStore.update(...args), 'Failed to update thread.'),
277
+ delete: (...args: Parameters<typeof threadStore.deleteById>) =>
278
+ effectTryPromise(() => threadStore.deleteById(...args), 'Failed to delete thread.'),
279
+ getOrCreateDefault: (...args: Parameters<typeof bootstrap.getOrCreateDefault>) =>
280
+ effectTryPromise(() => bootstrap.getOrCreateDefault(...args), 'Failed to get or create default thread.'),
281
+ getOrCreateThread: (...args: Parameters<typeof bootstrap.getOrCreateThread>) =>
282
+ effectTryPromise(() => bootstrap.getOrCreateThread(...args), 'Failed to get or create thread.'),
283
+ createThread: (...args: Parameters<typeof bootstrap.createThread>) =>
284
+ effectTryPromise(() => bootstrap.createThread(...args), 'Failed to create thread.'),
285
+ ensureBootstrapThreads: (...args: Parameters<typeof bootstrap.ensureBootstrapThreads>) =>
286
+ effectTryPromise(() => bootstrap.ensureBootstrapThreads(...args), 'Failed to ensure bootstrap threads.'),
287
+ listThreads: (...args: Parameters<typeof listing.listThreads>) =>
288
+ effectTryPromise(() => listing.listThreads(...args), 'Failed to list threads.'),
289
+ listOrganizationThreads: (...args: Parameters<typeof listing.listOrganizationThreads>) =>
290
+ effectTryPromise(() => listing.listOrganizationThreads(...args), 'Failed to list organization threads.'),
291
+ getThread,
292
+ updateTitle,
293
+ updateStatus,
294
+ setActiveTurn: activeRun.setActiveTurn,
295
+ getActiveTurn: (...args: Parameters<typeof activeRun.getActiveTurn>) =>
296
+ effectTryPromise(() => activeRun.getActiveTurn(...args), 'Failed to get active turn.'),
297
+ getActiveRunId: activeRun.getActiveRunId,
298
+ hasActiveRunLease: activeRun.hasActiveRunLease,
299
+ withActiveRunLease: activeRun.withActiveRunLease,
300
+ getActiveStreamId: activeRun.getActiveStreamId,
301
+ clearActiveTurn: activeRun.clearActiveTurn,
302
+ clearStaleActiveRunIfMissingFromRegistry: activeRun.clearStaleActiveRunIfMissingFromRegistry,
303
+ stopActiveRun: (...args: Parameters<typeof activeRun.stopActiveRun>) =>
304
+ effectTryPromise(() => activeRun.stopActiveRun(...args), 'Failed to stop active run.'),
305
+ setCompacting,
306
+ appendMemoryBlock: (...args: Parameters<typeof memory.appendMemoryBlock>) =>
307
+ effectTryPromise(() => memory.appendMemoryBlock(...args), 'Failed to append memory block.'),
308
+ compactMemoryBlock: (...args: Parameters<typeof memory.compactMemoryBlock>) =>
309
+ effectTryPromise(() => memory.compactMemoryBlock(...args), 'Failed to compact memory block.'),
310
+ clearThread,
311
+ deleteThread,
312
+ listRecentThreads: (...args: Parameters<typeof listing.listRecentThreads>) =>
313
+ effectTryPromise(() => listing.listRecentThreads(...args), 'Failed to list recent threads.'),
314
+ formatMemoryBlockForPrompt,
315
+ toPublicThread,
316
+ incrementTurnCount,
317
+ }
318
+ }
319
+
320
+ export class ThreadServiceTag extends Context.Service<ThreadServiceTag, ReturnType<typeof makeThreadService>>()(
321
+ '@lota-sdk/core/ThreadService',
322
+ ) {}
323
+
324
+ export const ThreadServiceLive = Layer.effect(
325
+ ThreadServiceTag,
326
+ Effect.gen(function* () {
327
+ const db = yield* DatabaseServiceTag
328
+ const redis = yield* RedisServiceTag
329
+ const chatRunRegistry = yield* ChatRunRegistryTag
330
+ const threadMessageService = yield* ThreadMessageServiceTag
331
+ const contextCompactionService = yield* ContextCompactionServiceTag
332
+ const compactionCoordination = yield* CompactionCoordinationTag
333
+ const background = yield* BackgroundWorkService
334
+ return makeThreadService({
335
+ db,
336
+ redis,
337
+ chatRunRegistry,
338
+ threadMessageService,
339
+ contextCompactionService,
340
+ compactionCoordination,
341
+ background,
342
+ })
343
+ }),
344
+ )
@@ -1,56 +1,106 @@
1
1
  import type { SdkUser, SdkUserRecord } from '@lota-sdk/shared'
2
2
  import { sdkUserRecordSchema, sdkUserSchema } from '@lota-sdk/shared'
3
+ import { Context, Schema, Effect, Layer } from 'effect'
3
4
 
4
- import { BaseService } from '../db/base.service'
5
5
  import { ensureRecordId, recordIdToString } from '../db/record-id'
6
6
  import type { RecordIdInput } from '../db/record-id'
7
- import { databaseService } from '../db/service'
7
+ import type { SurrealDBService } from '../db/service'
8
8
  import { TABLES } from '../db/tables'
9
+ import { NotFoundError } from '../effect/errors'
10
+ import { DatabaseServiceTag } from '../effect/services'
9
11
  import { toIsoDateTimeString } from '../utils/date-time'
10
12
 
11
- class UserService extends BaseService<typeof sdkUserRecordSchema> {
12
- constructor() {
13
- super(TABLES.USER, sdkUserRecordSchema)
14
- }
13
+ function toPublic(record: SdkUserRecord): SdkUser {
14
+ return sdkUserSchema.parse({
15
+ id: recordIdToString(ensureRecordId(record.id as RecordIdInput, TABLES.USER), TABLES.USER),
16
+ name: record.name,
17
+ email: record.email,
18
+ createdAt: toIsoDateTimeString(record.createdAt),
19
+ updatedAt: toIsoDateTimeString(record.updatedAt),
20
+ })
21
+ }
15
22
 
16
- toPublic(record: SdkUserRecord): SdkUser {
17
- return sdkUserSchema.parse({
18
- id: recordIdToString(ensureRecordId(record.id as RecordIdInput, TABLES.USER), TABLES.USER),
19
- name: record.name,
20
- email: record.email,
21
- createdAt: toIsoDateTimeString(record.createdAt),
22
- updatedAt: toIsoDateTimeString(record.updatedAt),
23
- })
24
- }
23
+ function userNotFoundError(userId: RecordIdInput): NotFoundError {
24
+ return new NotFoundError({
25
+ resource: TABLES.USER,
26
+ id: recordIdToString(userId, TABLES.USER),
27
+ message: `Record not found in ${TABLES.USER}: ${recordIdToString(userId, TABLES.USER)}`,
28
+ })
29
+ }
30
+
31
+ class UserServiceError extends Schema.TaggedErrorClass<UserServiceError>()('UserServiceError', {
32
+ operation: Schema.String,
33
+ cause: Schema.Defect,
34
+ }) {}
35
+
36
+ function toUserServiceError(operation: string, cause: unknown): UserServiceError {
37
+ return new UserServiceError({ operation, cause })
38
+ }
25
39
 
26
- async upsertUser(params: { id: RecordIdInput; name: string; email: string }): Promise<SdkUser> {
40
+ export function makeUserService(db: SurrealDBService) {
41
+ function upsertUserEffect(params: { id: RecordIdInput; name: string; email: string }) {
27
42
  const userRef = ensureRecordId(params.id, TABLES.USER)
28
- const record = await databaseService.upsert(
29
- TABLES.USER,
30
- userRef,
31
- { name: params.name, email: params.email },
32
- sdkUserRecordSchema,
43
+ return db.upsert(TABLES.USER, userRef, { name: params.name, email: params.email }, sdkUserRecordSchema).pipe(
44
+ Effect.mapError((cause) => toUserServiceError('upsertUser', cause)),
45
+ Effect.map(toPublic),
33
46
  )
34
- return this.toPublic(record)
35
47
  }
36
48
 
37
- async getUser(userId: RecordIdInput): Promise<SdkUser> {
38
- return this.toPublic(await this.getById(ensureRecordId(userId, TABLES.USER)))
49
+ function getUserEffect(userId: RecordIdInput) {
50
+ return db.findOne(TABLES.USER, { id: ensureRecordId(userId, TABLES.USER) }, sdkUserRecordSchema).pipe(
51
+ Effect.mapError((cause) => toUserServiceError('getUser', cause)),
52
+ Effect.flatMap((record) => (record ? Effect.succeed(record) : Effect.fail(userNotFoundError(userId)))),
53
+ Effect.map(toPublic),
54
+ )
39
55
  }
40
56
 
41
- async listUsers(): Promise<SdkUser[]> {
42
- return (await this.findAll({}, { orderBy: 'createdAt', orderDir: 'ASC' })).map((record) => this.toPublic(record))
57
+ function listUsersEffect() {
58
+ return db.findMany(TABLES.USER, {}, sdkUserRecordSchema, { orderBy: 'createdAt', orderDir: 'ASC' }).pipe(
59
+ Effect.mapError((cause) => toUserServiceError('listUsers', cause)),
60
+ Effect.map((records) => records.map(toPublic)),
61
+ )
43
62
  }
44
63
 
45
- async updateUser(userId: RecordIdInput, params: { name?: string; email?: string }): Promise<SdkUser> {
46
- return this.toPublic(await this.update(ensureRecordId(userId, TABLES.USER), params))
64
+ function updateUserEffect(userId: RecordIdInput, params: { name?: string; email?: string }) {
65
+ return db.update(TABLES.USER, ensureRecordId(userId, TABLES.USER), params, sdkUserRecordSchema).pipe(
66
+ Effect.mapError((cause) => toUserServiceError('updateUser', cause)),
67
+ Effect.flatMap((updated) => (updated ? Effect.succeed(updated) : Effect.fail(userNotFoundError(userId)))),
68
+ Effect.map(toPublic),
69
+ )
47
70
  }
48
71
 
49
- async deleteUser(userId: RecordIdInput): Promise<void> {
72
+ function deleteUserEffect(userId: RecordIdInput) {
50
73
  const userRef = ensureRecordId(userId, TABLES.USER)
51
- await databaseService.deleteWhere(TABLES.ORGANIZATION_MEMBER, { in: userRef })
52
- await this.delete(userRef)
74
+ return Effect.gen(function* () {
75
+ yield* db
76
+ .deleteWhere(TABLES.ORGANIZATION_MEMBER, { in: userRef })
77
+ .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteMemberships', cause)))
78
+ const deleted = yield* db
79
+ .deleteById(TABLES.USER, userRef)
80
+ .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteUser', cause)))
81
+ if (!deleted) {
82
+ return yield* userNotFoundError(userId)
83
+ }
84
+ })
85
+ }
86
+
87
+ return {
88
+ upsertUser: upsertUserEffect,
89
+ getUser: getUserEffect,
90
+ listUsers: listUsersEffect,
91
+ updateUser: updateUserEffect,
92
+ deleteUser: deleteUserEffect,
53
93
  }
54
94
  }
55
95
 
56
- export const userService = new UserService()
96
+ export class UserServiceTag extends Context.Service<UserServiceTag, ReturnType<typeof makeUserService>>()(
97
+ '@lota-sdk/core/UserService',
98
+ ) {}
99
+
100
+ export const UserServiceLive = Layer.effect(
101
+ UserServiceTag,
102
+ Effect.gen(function* () {
103
+ const db = yield* DatabaseServiceTag
104
+ return makeUserService(db)
105
+ }),
106
+ )
@@ -1,6 +1,8 @@
1
1
  import type { PlanDataSchema, PlanNodeSpec, PlanSchemaRegistry, WriteIntent } from '@lota-sdk/shared'
2
+ import { Context, Layer } from 'effect'
2
3
 
3
- import { validateSchemaValue } from './plan-validator.service'
4
+ import { nowIsoDateTimeString } from '../utils/date-time'
5
+ import { validateSchemaValue } from './plan/plan-validator.service'
4
6
 
5
7
  export interface WriteValidationIssue {
6
8
  code: string
@@ -15,19 +17,51 @@ export interface WriteValidationResult {
15
17
  validatedAt: string
16
18
  }
17
19
 
18
- class WriteIntentValidatorService {
19
- validate(params: {
20
- intent: WriteIntent
21
- nodeSpec: PlanNodeSpec
22
- schemaRegistry: PlanSchemaRegistry
23
- existingDeliverables: Map<string, unknown>
24
- }): WriteValidationResult {
25
- const issues: WriteValidationIssue[] = []
26
- const { intent, nodeSpec, schemaRegistry, existingDeliverables } = params
20
+ function buildWriteValidationResult(issues: WriteValidationIssue[]): WriteValidationResult {
21
+ const hasFailed = issues.length > 0
22
+ return {
23
+ status: hasFailed ? 'fail' : 'pass',
24
+ issues,
25
+ ...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
26
+ validatedAt: nowIsoDateTimeString(),
27
+ }
28
+ }
29
+
30
+ export function makeWriteIntentValidatorService() {
31
+ return {
32
+ validate(params: {
33
+ intent: WriteIntent
34
+ nodeSpec: PlanNodeSpec
35
+ schemaRegistry: PlanSchemaRegistry
36
+ existingDeliverables: Map<string, unknown>
37
+ }): WriteValidationResult {
38
+ const issues: WriteValidationIssue[] = []
39
+ const { intent, nodeSpec, schemaRegistry, existingDeliverables } = params
40
+
41
+ if (intent.targetPath.startsWith('structuredOutput')) {
42
+ if (nodeSpec.outputSchemaRef) {
43
+ const schema = schemaRegistry[nodeSpec.outputSchemaRef] as PlanDataSchema | undefined
44
+ if (schema) {
45
+ const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
46
+ for (const message of schemaIssues) {
47
+ issues.push({ code: 'schema_validation_failed', message })
48
+ }
49
+ }
50
+ }
51
+ return buildWriteValidationResult(issues)
52
+ }
27
53
 
28
- if (intent.targetPath.startsWith('structuredOutput')) {
29
- if (nodeSpec.outputSchemaRef) {
30
- const schema = schemaRegistry[nodeSpec.outputSchemaRef] as PlanDataSchema | undefined
54
+ const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
55
+ if (!deliverable) {
56
+ issues.push({
57
+ code: 'unknown_deliverable',
58
+ message: `"${intent.targetPath}" does not match any declared deliverable.`,
59
+ })
60
+ return buildWriteValidationResult(issues)
61
+ }
62
+
63
+ if (deliverable.schemaRef) {
64
+ const schema = schemaRegistry[deliverable.schemaRef] as PlanDataSchema | undefined
31
65
  if (schema) {
32
66
  const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
33
67
  for (const message of schemaIssues) {
@@ -35,47 +69,25 @@ class WriteIntentValidatorService {
35
69
  }
36
70
  }
37
71
  }
38
- return this.buildResult(issues)
39
- }
40
-
41
- const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
42
- if (!deliverable) {
43
- issues.push({
44
- code: 'unknown_deliverable',
45
- message: `"${intent.targetPath}" does not match any declared deliverable.`,
46
- })
47
- return this.buildResult(issues)
48
- }
49
72
 
50
- if (deliverable.schemaRef) {
51
- const schema = schemaRegistry[deliverable.schemaRef] as PlanDataSchema | undefined
52
- if (schema) {
53
- const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
54
- for (const message of schemaIssues) {
55
- issues.push({ code: 'schema_validation_failed', message })
56
- }
73
+ if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
74
+ issues.push({
75
+ code: 'update_target_not_found',
76
+ message: `Cannot update "${intent.targetPath}" no prior write exists.`,
77
+ })
57
78
  }
58
- }
59
-
60
- if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
61
- issues.push({
62
- code: 'update_target_not_found',
63
- message: `Cannot update "${intent.targetPath}" — no prior write exists.`,
64
- })
65
- }
66
79
 
67
- return this.buildResult(issues)
68
- }
69
-
70
- private buildResult(issues: WriteValidationIssue[]): WriteValidationResult {
71
- const hasFailed = issues.length > 0
72
- return {
73
- status: hasFailed ? 'fail' : 'pass',
74
- issues,
75
- ...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
76
- validatedAt: new Date().toISOString(),
77
- }
80
+ return buildWriteValidationResult(issues)
81
+ },
78
82
  }
79
83
  }
80
84
 
81
- export const writeIntentValidatorService = new WriteIntentValidatorService()
85
+ export class WriteIntentValidatorServiceTag extends Context.Service<
86
+ WriteIntentValidatorServiceTag,
87
+ ReturnType<typeof makeWriteIntentValidatorService>
88
+ >()('@lota-sdk/core/WriteIntentValidatorService') {}
89
+
90
+ export const WriteIntentValidatorServiceLive = Layer.succeed(
91
+ WriteIntentValidatorServiceTag,
92
+ makeWriteIntentValidatorService(),
93
+ )