@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,6 +1,10 @@
1
+ import { normalizeMessageBatch } from '@lota-sdk/shared'
2
+ import { Context, Effect, Layer } from 'effect'
1
3
  import { z } from 'zod'
2
4
 
3
- import { getRedisConnection } from '../redis'
5
+ import { RedisServiceTag, RuntimeConfigServiceTag } from '../effect/services'
6
+ import type { RedisConnectionManager } from '../redis/connection'
7
+ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
4
8
  import type { LotaRuntimeBackgroundCursor, LotaRuntimeBackgroundCursorKind } from '../runtime/runtime-extensions'
5
9
 
6
10
  const DEFAULT_SOCIAL_CHAT_HISTORY_PREFIX = 'lota:social:history'
@@ -8,6 +12,37 @@ const DEFAULT_SOCIAL_CHAT_HISTORY_PREFIX = 'lota:social:history'
8
12
  const SocialChatMessageRoleSchema = z.enum(['user', 'assistant'])
9
13
  const SocialChatSourceSchema = z.literal('social')
10
14
  const SocialChatPlatformSchema = z.literal('slack')
15
+ const SocialChatHistoryGenericPartSchema = z
16
+ .object({ type: z.string().optional(), text: z.string().optional() })
17
+ .catchall(z.unknown())
18
+ const SocialChatHistoryTextPartSchema = SocialChatHistoryGenericPartSchema.extend({
19
+ type: z.literal('text'),
20
+ text: z.string().trim().min(1),
21
+ })
22
+ const SocialChatHistoryFilePartSchema = SocialChatHistoryGenericPartSchema.extend({
23
+ type: z.literal('file'),
24
+ filename: z.string().trim().min(1),
25
+ mediaType: z.string().trim().min(1),
26
+ sizeBytes: z.number().int().nonnegative().nullable(),
27
+ storageKey: z.string().trim().min(1),
28
+ })
29
+ const SocialChatHistoryPartSchema = z.union([
30
+ SocialChatHistoryTextPartSchema,
31
+ SocialChatHistoryFilePartSchema,
32
+ SocialChatHistoryGenericPartSchema,
33
+ ])
34
+ const SocialChatHistoryMetadataSchema = z
35
+ .object({
36
+ platform: SocialChatPlatformSchema,
37
+ channelId: z.string().trim().min(1),
38
+ threadId: z.string().trim().min(1),
39
+ messageId: z.string().trim().min(1),
40
+ authorId: z.string().trim().min(1).optional(),
41
+ authorName: z.string().trim().min(1).optional(),
42
+ agentId: z.string().trim().min(1).optional(),
43
+ agentName: z.string().trim().min(1).optional(),
44
+ })
45
+ .catchall(z.unknown())
11
46
 
12
47
  const SocialChatHistoryMessageSchema = z.object({
13
48
  source: SocialChatSourceSchema,
@@ -18,15 +53,15 @@ const SocialChatHistoryMessageSchema = z.object({
18
53
  threadId: z.string().trim().min(1),
19
54
  messageId: z.string().trim().min(1),
20
55
  role: SocialChatMessageRoleSchema,
21
- parts: z.array(z.record(z.string(), z.unknown())),
22
- metadata: z.record(z.string(), z.unknown()).optional(),
56
+ parts: z.array(SocialChatHistoryPartSchema),
57
+ metadata: SocialChatHistoryMetadataSchema.optional(),
23
58
  cursor: z.object({ createdAt: z.coerce.date(), id: z.string().trim().min(1) }),
24
59
  })
25
60
 
61
+ export type SocialChatHistoryPart = z.infer<typeof SocialChatHistoryPartSchema>
62
+ export type SocialChatHistoryMetadata = z.infer<typeof SocialChatHistoryMetadataSchema>
26
63
  export type SocialChatHistoryMessage = z.infer<typeof SocialChatHistoryMessageSchema>
27
64
 
28
- let socialChatHistoryPrefix = DEFAULT_SOCIAL_CHAT_HISTORY_PREFIX
29
-
30
65
  function trimConfiguredPrefix(value: string | undefined): string {
31
66
  const normalized = value?.trim()
32
67
  return normalized && normalized.length > 0 ? normalized : DEFAULT_SOCIAL_CHAT_HISTORY_PREFIX
@@ -38,160 +73,176 @@ function compareCursorOrder(left: LotaRuntimeBackgroundCursor, right: LotaRuntim
38
73
  return left.id.localeCompare(right.id)
39
74
  }
40
75
 
41
- class SocialChatHistoryService {
42
- configure(params?: { keyPrefix?: string }): void {
43
- socialChatHistoryPrefix = trimConfiguredPrefix(params?.keyPrefix)
44
- }
76
+ export function makeSocialChatHistoryService(
77
+ redis: RedisConnectionManager,
78
+ config: ResolvedLotaRuntimeConfig | undefined,
79
+ ) {
80
+ const resolvePrefix = (): string => trimConfiguredPrefix(config?.socialChat?.historyRedisKeyPrefix)
45
81
 
46
- private messageStorageKey(message: {
82
+ const messageStorageKey = (message: {
47
83
  platform: 'slack'
48
84
  workspaceId: string
49
85
  threadId: string
50
86
  messageId: string
51
- }): string {
52
- return `${socialChatHistoryPrefix}:message:${message.platform}:${message.workspaceId}:${message.threadId}:${message.messageId}`
53
- }
87
+ }): string =>
88
+ `${resolvePrefix()}:message:${message.platform}:${message.workspaceId}:${message.threadId}:${message.messageId}`
54
89
 
55
- private threadIndexKey(workspaceId: string, threadId: string): string {
56
- return `${socialChatHistoryPrefix}:thread:${workspaceId}:${threadId}`
57
- }
90
+ const threadIndexKey = (workspaceId: string, threadId: string): string =>
91
+ `${resolvePrefix()}:thread:${workspaceId}:${threadId}`
58
92
 
59
- private workspaceIndexKey(workspaceId: string): string {
60
- return `${socialChatHistoryPrefix}:workspace:${workspaceId}`
61
- }
93
+ const workspaceIndexKey = (workspaceId: string): string => `${resolvePrefix()}:workspace:${workspaceId}`
62
94
 
63
- private cursorKey(kind: LotaRuntimeBackgroundCursorKind, workspaceId: string): string {
64
- return `${socialChatHistoryPrefix}:cursor:${kind}:${workspaceId}`
65
- }
95
+ const cursorKey = (kind: LotaRuntimeBackgroundCursorKind, workspaceId: string): string =>
96
+ `${resolvePrefix()}:cursor:${kind}:${workspaceId}`
66
97
 
67
- private serializeMessage(message: SocialChatHistoryMessage): string {
68
- return JSON.stringify({
69
- ...message,
70
- cursor: { ...message.cursor, createdAt: message.cursor.createdAt.toISOString() },
71
- })
72
- }
98
+ const serializeMessage = (message: SocialChatHistoryMessage): string =>
99
+ JSON.stringify({ ...message, cursor: { ...message.cursor, createdAt: message.cursor.createdAt.toISOString() } })
73
100
 
74
- private parseStoredMessage(value: string | null): SocialChatHistoryMessage | null {
75
- if (!value) return null
76
- try {
77
- const parsed = SocialChatHistoryMessageSchema.safeParse(JSON.parse(value))
78
- return parsed.success ? parsed.data : null
79
- } catch {
80
- return null
81
- }
82
- }
101
+ const parseStoredMessageEffect = (value: string | null): Effect.Effect<SocialChatHistoryMessage | null> => {
102
+ if (!value) return Effect.succeed(null)
83
103
 
84
- private serializeCursor(cursor: LotaRuntimeBackgroundCursor): string {
85
- return JSON.stringify({ ...cursor, createdAt: cursor.createdAt.toISOString() })
104
+ return Effect.try({ try: (): unknown => JSON.parse(value), catch: (cause) => cause }).pipe(
105
+ Effect.map((parsed) => {
106
+ const validated = SocialChatHistoryMessageSchema.safeParse(parsed)
107
+ return validated.success ? validated.data : null
108
+ }),
109
+ Effect.catch(() => Effect.succeed(null)),
110
+ )
86
111
  }
87
112
 
88
- private parseCursor(value: string | null): LotaRuntimeBackgroundCursor | null {
89
- if (!value) return null
90
- try {
91
- const parsed = z.object({ createdAt: z.coerce.date(), id: z.string().trim().min(1) }).safeParse(JSON.parse(value))
92
- return parsed.success ? parsed.data : null
93
- } catch {
94
- return null
95
- }
96
- }
113
+ const serializeCursor = (cursor: LotaRuntimeBackgroundCursor): string =>
114
+ JSON.stringify({ ...cursor, createdAt: cursor.createdAt.toISOString() })
97
115
 
98
- async upsertMessages(messages: SocialChatHistoryMessage[]): Promise<SocialChatHistoryMessage[]> {
99
- if (messages.length === 0) return []
116
+ const parseCursorEffect = (value: string | null): Effect.Effect<LotaRuntimeBackgroundCursor | null> => {
117
+ if (!value) return Effect.succeed(null)
100
118
 
101
- const redis = getRedisConnection()
102
- const normalizedMessages = messages.map((message) => SocialChatHistoryMessageSchema.parse(message))
103
- const multi = redis.multi()
104
-
105
- for (const message of normalizedMessages) {
106
- const storageKey = this.messageStorageKey(message)
107
- const score = message.cursor.createdAt.getTime()
108
- multi.set(storageKey, this.serializeMessage(message))
109
- multi.zadd(this.threadIndexKey(message.workspaceId, message.threadId), score, storageKey)
110
- multi.zadd(this.workspaceIndexKey(message.workspaceId), score, storageKey)
111
- }
112
-
113
- await multi.exec()
114
- return normalizedMessages
119
+ return Effect.try({ try: (): unknown => JSON.parse(value), catch: (cause) => cause }).pipe(
120
+ Effect.map((parsed) => {
121
+ const validated = z.object({ createdAt: z.coerce.date(), id: z.string().trim().min(1) }).safeParse(parsed)
122
+ return validated.success ? validated.data : null
123
+ }),
124
+ Effect.catch(() => Effect.succeed(null)),
125
+ )
115
126
  }
116
127
 
117
- async listThreadMessages(params: { workspaceId: string; threadId: string }): Promise<SocialChatHistoryMessage[]> {
118
- const redis = getRedisConnection()
119
- const storageKeys = await redis.zrange(this.threadIndexKey(params.workspaceId, params.threadId), 0, -1)
120
- if (storageKeys.length === 0) return []
128
+ const upsertMessagesEffect = (messages: SocialChatHistoryMessage[]) =>
129
+ Effect.gen(function* () {
130
+ if (messages.length === 0) return [] as SocialChatHistoryMessage[]
121
131
 
122
- const storedValues = await redis.mget(storageKeys)
123
- return storedValues
124
- .map((value) => this.parseStoredMessage(value))
125
- .filter((message): message is SocialChatHistoryMessage => message !== null)
126
- .sort((left, right) => compareCursorOrder(left.cursor, right.cursor))
127
- }
132
+ const conn = redis.getConnection()
133
+ const normalizedMessages = normalizeMessageBatch(messages, {
134
+ parseMessage: (message) => SocialChatHistoryMessageSchema.parse(message),
135
+ compareMessages: (left, right) => compareCursorOrder(left.cursor, right.cursor),
136
+ getMessageId: (message) => message.messageId,
137
+ })
138
+ const multi = conn.multi()
139
+
140
+ for (const message of normalizedMessages) {
141
+ const storageKey = messageStorageKey(message)
142
+ const score = message.cursor.createdAt.getTime()
143
+ multi.set(storageKey, serializeMessage(message))
144
+ multi.zadd(threadIndexKey(message.workspaceId, message.threadId), score, storageKey)
145
+ multi.zadd(workspaceIndexKey(message.workspaceId), score, storageKey)
146
+ }
147
+
148
+ yield* Effect.tryPromise(() => multi.exec())
149
+ return normalizedMessages
150
+ })
151
+
152
+ const listThreadMessagesEffect = (params: { workspaceId: string; threadId: string }) =>
153
+ Effect.gen(function* () {
154
+ const conn = redis.getConnection()
155
+ const storageKeys = yield* Effect.tryPromise(() =>
156
+ conn.zrange(threadIndexKey(params.workspaceId, params.threadId), 0, -1),
157
+ )
158
+ if (storageKeys.length === 0) return [] as SocialChatHistoryMessage[]
159
+
160
+ const storedValues = yield* Effect.tryPromise(() => conn.mget(storageKeys))
161
+ const parsedMessages = yield* Effect.forEach(storedValues, parseStoredMessageEffect)
162
+ return parsedMessages
163
+ .filter((message): message is SocialChatHistoryMessage => message !== null)
164
+ .sort((left, right) => compareCursorOrder(left.cursor, right.cursor))
165
+ })
128
166
 
129
- async listWorkspaceMessages(params: {
167
+ const listWorkspaceMessagesEffect = (params: {
130
168
  workspaceId: string
131
169
  cursor: LotaRuntimeBackgroundCursor | null
132
170
  onboardingCutoff: Date | null
133
- }): Promise<SocialChatHistoryMessage[]> {
134
- const redis = getRedisConnection()
135
- const indexKey = this.workspaceIndexKey(params.workspaceId)
136
- const scoreStart =
137
- params.cursor?.createdAt.getTime() ??
138
- (params.onboardingCutoff ? params.onboardingCutoff.getTime() : Number.NEGATIVE_INFINITY)
139
- const storageKeys =
140
- params.cursor || params.onboardingCutoff
141
- ? await redis.zrangebyscore(indexKey, scoreStart, '+inf')
142
- : await redis.zrange(indexKey, 0, -1)
143
-
144
- if (storageKeys.length === 0) return []
145
-
146
- const storedValues = await redis.mget(storageKeys)
147
- return storedValues
148
- .map((value) => this.parseStoredMessage(value))
149
- .filter((message): message is SocialChatHistoryMessage => message !== null)
150
- .filter((message) => {
151
- if (params.cursor) {
152
- return compareCursorOrder(message.cursor, params.cursor) > 0
153
- }
154
- if (params.onboardingCutoff) {
155
- return message.cursor.createdAt.getTime() > params.onboardingCutoff.getTime()
156
- }
157
- return true
158
- })
159
- .sort((left, right) => compareCursorOrder(left.cursor, right.cursor))
160
- }
171
+ }) =>
172
+ Effect.gen(function* () {
173
+ const conn = redis.getConnection()
174
+ const indexKey = workspaceIndexKey(params.workspaceId)
175
+ const scoreStart =
176
+ params.cursor?.createdAt.getTime() ??
177
+ (params.onboardingCutoff ? params.onboardingCutoff.getTime() : Number.NEGATIVE_INFINITY)
178
+ const storageKeys = yield* Effect.tryPromise(() =>
179
+ params.cursor || params.onboardingCutoff
180
+ ? conn.zrangebyscore(indexKey, scoreStart, '+inf')
181
+ : conn.zrange(indexKey, 0, -1),
182
+ )
183
+ if (storageKeys.length === 0) return [] as SocialChatHistoryMessage[]
184
+
185
+ const storedValues = yield* Effect.tryPromise(() => conn.mget(storageKeys))
186
+ const parsedMessages = yield* Effect.forEach(storedValues, parseStoredMessageEffect)
187
+ return parsedMessages
188
+ .filter((message): message is SocialChatHistoryMessage => message !== null)
189
+ .filter((message) => {
190
+ if (params.cursor) {
191
+ return compareCursorOrder(message.cursor, params.cursor) > 0
192
+ }
193
+ if (params.onboardingCutoff) {
194
+ return message.cursor.createdAt.getTime() > params.onboardingCutoff.getTime()
195
+ }
196
+ return true
197
+ })
198
+ .sort((left, right) => compareCursorOrder(left.cursor, right.cursor))
199
+ })
161
200
 
162
- async hasWorkspaceMessages(params: {
201
+ const hasWorkspaceMessagesEffect = (params: {
163
202
  workspaceId: string
164
203
  cursor: LotaRuntimeBackgroundCursor | null
165
204
  onboardingCutoff: Date | null
166
- }): Promise<boolean> {
167
- const messages = await this.listWorkspaceMessages({
205
+ }) =>
206
+ listWorkspaceMessagesEffect({
168
207
  workspaceId: params.workspaceId,
169
208
  cursor: params.cursor,
170
209
  onboardingCutoff: params.onboardingCutoff,
171
- })
172
- return messages.length > 0
173
- }
210
+ }).pipe(Effect.map((messages) => messages.length > 0))
174
211
 
175
- async getBackgroundCursor(
176
- kind: LotaRuntimeBackgroundCursorKind,
177
- workspaceId: string,
178
- ): Promise<LotaRuntimeBackgroundCursor | null> {
179
- const redis = getRedisConnection()
180
- return this.parseCursor(await redis.get(this.cursorKey(kind, workspaceId)))
181
- }
212
+ const getBackgroundCursorEffect = (kind: LotaRuntimeBackgroundCursorKind, workspaceId: string) =>
213
+ Effect.tryPromise(() => redis.getConnection().get(cursorKey(kind, workspaceId))).pipe(
214
+ Effect.flatMap((value) => parseCursorEffect(value)),
215
+ )
182
216
 
183
- async setBackgroundCursor(
217
+ const setBackgroundCursorEffect = (
184
218
  kind: LotaRuntimeBackgroundCursorKind,
185
219
  workspaceId: string,
186
220
  cursor: LotaRuntimeBackgroundCursor,
187
- ): Promise<void> {
188
- const redis = getRedisConnection()
189
- await redis.set(this.cursorKey(kind, workspaceId), this.serializeCursor(cursor))
221
+ ) =>
222
+ Effect.tryPromise(() => redis.getConnection().set(cursorKey(kind, workspaceId), serializeCursor(cursor))).pipe(
223
+ Effect.asVoid,
224
+ )
225
+
226
+ return {
227
+ upsertMessages: upsertMessagesEffect,
228
+ listThreadMessages: listThreadMessagesEffect,
229
+ listWorkspaceMessages: listWorkspaceMessagesEffect,
230
+ hasWorkspaceMessages: hasWorkspaceMessagesEffect,
231
+ getBackgroundCursor: getBackgroundCursorEffect,
232
+ setBackgroundCursor: setBackgroundCursorEffect,
190
233
  }
191
234
  }
192
235
 
193
- export const socialChatHistoryService = new SocialChatHistoryService()
194
-
195
- export function configureSocialChatHistory(params?: { keyPrefix?: string }): void {
196
- socialChatHistoryService.configure(params)
197
- }
236
+ export class SocialChatHistoryServiceTag extends Context.Service<
237
+ SocialChatHistoryServiceTag,
238
+ ReturnType<typeof makeSocialChatHistoryService>
239
+ >()('SocialChatHistoryService') {}
240
+
241
+ export const SocialChatHistoryServiceLive = Layer.effect(
242
+ SocialChatHistoryServiceTag,
243
+ Effect.gen(function* () {
244
+ const redis = yield* RedisServiceTag
245
+ const config = yield* RuntimeConfigServiceTag
246
+ return makeSocialChatHistoryService(redis, config)
247
+ }),
248
+ )
@@ -1,27 +1,33 @@
1
1
  import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, SystemPlanNodeOwner } from '@lota-sdk/shared'
2
+ import { Context, Effect, Layer } from 'effect'
2
3
 
4
+ import { BadRequestError } from '../effect/errors'
5
+ import { RuntimeConfigServiceTag } from '../effect/services'
3
6
  import type { SystemNodeExecutor, PluginNodeExecutionParams } from '../runtime/plugin-types'
4
- import { getRuntimeConfig } from '../runtime/runtime-config'
5
- import type { PlanValidationIssueInput } from './plan-validator.service'
7
+ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
8
+ import type { PlanValidationIssueInput } from './plan/plan-validator.service'
6
9
 
7
10
  const BUILT_IN_SYSTEM_EXECUTORS = Object.freeze({
8
11
  'plan-runtime': {
9
12
  supportedOperations: ['echo-input'],
10
- async executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult> {
11
- return { notes: 'System echo-input completed.', structuredOutput: structuredClone(params.inputs), artifacts: [] }
13
+ executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult> {
14
+ return Promise.resolve({
15
+ notes: 'System echo-input completed.',
16
+ structuredOutput: structuredClone(params.inputs),
17
+ artifacts: [],
18
+ })
12
19
  },
13
20
  } satisfies SystemNodeExecutor,
14
21
  })
15
22
 
16
- export function getBuiltInSystemExecutors(): Record<string, SystemNodeExecutor> {
17
- return Object.fromEntries(Object.entries(BUILT_IN_SYSTEM_EXECUTORS))
23
+ function isSystemOwner(
24
+ owner: PlanNodeSpec['owner'],
25
+ ): owner is Extract<PlanNodeSpec['owner'], { executorType: 'system' }> {
26
+ return owner.executorType === 'system'
18
27
  }
19
28
 
20
- function getSystemExecutors() {
21
- return (getRuntimeConfig().systemExecutors ?? getBuiltInSystemExecutors()) as Record<
22
- string,
23
- SystemNodeExecutor | undefined
24
- >
29
+ export function getBuiltInSystemExecutors(): Record<string, SystemNodeExecutor> {
30
+ return Object.fromEntries(Object.entries(BUILT_IN_SYSTEM_EXECUTORS))
25
31
  }
26
32
 
27
33
  function buildSystemExecutionParams(params: {
@@ -45,61 +51,85 @@ function buildSystemExecutionParams(params: {
45
51
  }
46
52
  }
47
53
 
48
- class SystemExecutorService {
49
- validateOwner(owner: SystemPlanNodeOwner, nodeId: string): PlanValidationIssueInput[] {
50
- const executor = getSystemExecutors()[owner.ref]
51
- if (!executor) {
52
- return [
53
- {
54
- severity: 'blocking',
55
- code: 'system_executor_missing',
56
- message: `Node "${nodeId}" references unknown system executor "${owner.ref}".`,
57
- nodeId,
58
- detail: { systemRef: owner.ref },
59
- },
60
- ]
61
- }
54
+ export function makeSystemExecutorService(config: ResolvedLotaRuntimeConfig) {
55
+ function getSystemExecutors() {
56
+ return (config.systemExecutors ?? getBuiltInSystemExecutors()) as Record<string, SystemNodeExecutor | undefined>
57
+ }
62
58
 
63
- if (!executor.supportedOperations.includes(owner.operation)) {
64
- return [
65
- {
66
- severity: 'blocking',
67
- code: 'system_operation_missing',
68
- message: `System executor "${owner.ref}" does not support operation "${owner.operation}".`,
69
- nodeId,
70
- detail: { systemRef: owner.ref, operation: owner.operation },
71
- },
72
- ]
73
- }
59
+ return {
60
+ validateOwner(owner: SystemPlanNodeOwner, nodeId: string): PlanValidationIssueInput[] {
61
+ const executor = getSystemExecutors()[owner.ref]
62
+ if (!executor) {
63
+ return [
64
+ {
65
+ severity: 'blocking',
66
+ code: 'system_executor_missing',
67
+ message: `Node "${nodeId}" references unknown system executor "${owner.ref}".`,
68
+ nodeId,
69
+ detail: { systemRef: owner.ref },
70
+ },
71
+ ]
72
+ }
74
73
 
75
- return []
76
- }
74
+ if (!executor.supportedOperations.includes(owner.operation)) {
75
+ return [
76
+ {
77
+ severity: 'blocking',
78
+ code: 'system_operation_missing',
79
+ message: `System executor "${owner.ref}" does not support operation "${owner.operation}".`,
80
+ nodeId,
81
+ detail: { systemRef: owner.ref, operation: owner.operation },
82
+ },
83
+ ]
84
+ }
85
+
86
+ return []
87
+ },
77
88
 
78
- async executeNode(params: {
79
- nodeSpec: PlanNodeSpec
80
- resolvedInput: Record<string, unknown>
81
- context: OwnershipDispatchContext
82
- }): Promise<PlanNodeResult> {
83
- if (params.nodeSpec.owner.executorType !== 'system') {
84
- throw new Error(`SystemExecutor cannot execute owner type "${params.nodeSpec.owner.executorType}".`)
85
- }
89
+ executeNode(params: {
90
+ nodeSpec: PlanNodeSpec
91
+ resolvedInput: Record<string, unknown>
92
+ context: OwnershipDispatchContext
93
+ }) {
94
+ return Effect.gen(function* () {
95
+ const owner = params.nodeSpec.owner
96
+ if (!isSystemOwner(owner)) {
97
+ return yield* new BadRequestError({
98
+ message: `SystemExecutor cannot execute owner type "${owner.executorType}".`,
99
+ })
100
+ }
86
101
 
87
- const executor = getSystemExecutors()[params.nodeSpec.owner.ref]
88
- if (!executor || !executor.supportedOperations.includes(params.nodeSpec.owner.operation)) {
89
- throw new Error(
90
- `System executor ${params.nodeSpec.owner.ref}.${params.nodeSpec.owner.operation} is not registered.`,
91
- )
92
- }
102
+ const executor = getSystemExecutors()[owner.ref]
103
+ if (!executor || !executor.supportedOperations.includes(owner.operation)) {
104
+ return yield* new BadRequestError({
105
+ message: `System executor ${owner.ref}.${owner.operation} is not registered.`,
106
+ })
107
+ }
93
108
 
94
- return executor.executeNode(
95
- buildSystemExecutionParams({
96
- owner: params.nodeSpec.owner,
97
- nodeSpec: params.nodeSpec,
98
- resolvedInput: params.resolvedInput,
99
- context: params.context,
100
- }),
101
- )
109
+ return yield* Effect.tryPromise(() =>
110
+ executor.executeNode(
111
+ buildSystemExecutionParams({
112
+ owner,
113
+ nodeSpec: params.nodeSpec,
114
+ resolvedInput: params.resolvedInput,
115
+ context: params.context,
116
+ }),
117
+ ),
118
+ )
119
+ })
120
+ },
102
121
  }
103
122
  }
104
123
 
105
- export const systemExecutorService = new SystemExecutorService()
124
+ export class SystemExecutorServiceTag extends Context.Service<
125
+ SystemExecutorServiceTag,
126
+ ReturnType<typeof makeSystemExecutorService>
127
+ >()('SystemExecutorService') {}
128
+
129
+ export const SystemExecutorServiceLive = Layer.effect(
130
+ SystemExecutorServiceTag,
131
+ Effect.gen(function* () {
132
+ const runtimeConfig = yield* RuntimeConfigServiceTag
133
+ return makeSystemExecutorService(runtimeConfig)
134
+ }),
135
+ )