@lota-sdk/core 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,11 +1,15 @@
1
1
  import type { ToolSet } from 'ai'
2
+ import { Config, ConfigProvider, Effect, Option, Redacted } from 'effect'
2
3
  import { z } from 'zod'
3
4
 
4
5
  import type { CoreThreadProfile } from '../config/agent-defaults'
5
6
  import type { AgentFactory, AgentRuntimeConfigProvider, AgentToolBuilder } from '../config/agent-types'
7
+ import { DEFAULT_AI_GATEWAY_URL } from '../config/constants'
6
8
  import { OPENROUTER_FAST_RERANK_MODEL_ID } from '../config/model-constants'
7
9
  import type { LotaThreadConfig, ThreadBootstrapWelcomeConfig } from '../config/thread-defaults'
8
10
  import type { RecordIdRef } from '../db/record-id'
11
+ import { getCurrentRuntime } from '../effect/runtime-ref'
12
+ import { RuntimeConfigServiceTag } from '../effect/services'
9
13
  import type { NotificationService } from '../services/notification.service'
10
14
  import { isRecord } from '../utils/string'
11
15
  import type { GraphDesigner } from './graph-designer'
@@ -14,6 +18,11 @@ import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime-extens
14
18
  import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
15
19
 
16
20
  const logLevelValues = ['trace', 'debug', 'info', 'warning', 'error', 'fatal'] as const
21
+ const nonEmptyStringSchema = z.string().trim().min(1)
22
+ const stringRecordSchema = z.record(z.string(), z.string())
23
+ function objectRecordSchema<T extends object>() {
24
+ return z.custom<T>(isRecord)
25
+ }
17
26
 
18
27
  function isStringOrUrl(value: unknown): value is string | URL {
19
28
  return typeof value === 'string' || value instanceof URL
@@ -23,26 +32,14 @@ function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
23
32
  return typeof value === 'function'
24
33
  }
25
34
 
26
- function isStringRecord(value: unknown): value is Record<string, string> {
27
- return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string')
35
+ function isToolSet(value: unknown): value is ToolSet {
36
+ return isRecord(value)
28
37
  }
29
38
 
30
39
  function isAgentFactoryRegistry(value: unknown): value is AgentFactory {
31
40
  return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'function')
32
41
  }
33
42
 
34
- function isPluginRuntimeRecord(value: unknown): value is Record<string, LotaPlugin> {
35
- return isRecord(value)
36
- }
37
-
38
- function isSystemExecutorRecord(value: unknown): value is Record<string, SystemNodeExecutor> {
39
- return isRecord(value)
40
- }
41
-
42
- function isToolProviderRecord(value: unknown): value is Record<string, unknown> {
43
- return isRecord(value)
44
- }
45
-
46
43
  function isNotificationService(value: unknown): value is NotificationService {
47
44
  return isRecord(value) && isFunction(value.notify) && isFunction(value.remind) && isFunction(value.escalate)
48
45
  }
@@ -146,7 +143,7 @@ function isSocialChatConfig(value: unknown): value is LotaRuntimeSocialChatConfi
146
143
  }
147
144
 
148
145
  const threadBootstrapWelcomeConfigSchema = z.object({
149
- defaultAgentId: z.string().trim().min(1),
146
+ defaultAgentId: nonEmptyStringSchema,
150
147
  buildMessageText: z.custom<ThreadBootstrapWelcomeConfig['buildMessageText']>(isFunction, {
151
148
  error: 'onboardingWelcome.buildMessageText must be a function',
152
149
  }),
@@ -156,9 +153,9 @@ const threadConfigSchema = z
156
153
  .object({
157
154
  bootstrap: z
158
155
  .object({
159
- onboardingDefaultAgents: z.array(z.string().trim().min(1)).optional(),
160
- completedDefaultAgents: z.array(z.string().trim().min(1)).optional(),
161
- threadTypesAfterOnboarding: z.array(z.string().trim().min(1)).optional(),
156
+ onboardingDefaultAgents: z.array(nonEmptyStringSchema).optional(),
157
+ completedDefaultAgents: z.array(nonEmptyStringSchema).optional(),
158
+ threadTypesAfterOnboarding: z.array(nonEmptyStringSchema).optional(),
162
159
  ensureDefaultGroupOnCompleted: z.boolean().optional(),
163
160
  onboardingWelcome: threadBootstrapWelcomeConfigSchema.optional(),
164
161
  })
@@ -169,19 +166,13 @@ const threadConfigSchema = z
169
166
 
170
167
  const agentsConfigSchema = z
171
168
  .object({
172
- roster: z.array(z.string().trim().min(1)).min(1),
173
- leadAgentId: z.string().trim().min(1),
174
- displayNames: z.custom<Record<string, string>>(isStringRecord, {
175
- error: 'agents.displayNames must be a string record',
176
- }),
177
- shortDisplayNames: z
178
- .custom<Record<string, string>>(isStringRecord, { error: 'agents.shortDisplayNames must be a string record' })
179
- .optional(),
180
- descriptions: z
181
- .custom<Record<string, string>>(isStringRecord, { error: 'agents.descriptions must be a string record' })
182
- .optional(),
183
- routerModelId: z.string().trim().min(1).optional(),
184
- teamConsultParticipants: z.array(z.string().trim().min(1)),
169
+ roster: z.array(nonEmptyStringSchema).min(1),
170
+ leadAgentId: nonEmptyStringSchema,
171
+ displayNames: stringRecordSchema,
172
+ shortDisplayNames: stringRecordSchema.optional(),
173
+ descriptions: stringRecordSchema.optional(),
174
+ routerModelId: nonEmptyStringSchema.optional(),
175
+ teamConsultParticipants: z.array(nonEmptyStringSchema),
185
176
  getCoreThreadProfile: z
186
177
  .custom<(coreType: string) => CoreThreadProfile>(isFunction, {
187
178
  error: 'agents.getCoreThreadProfile must be a function',
@@ -222,24 +213,25 @@ export type MemoryRerankerStrategy = z.infer<typeof MemoryRerankerStrategySchema
222
213
 
223
214
  export const LotaRuntimeConfigSchema = z.object({
224
215
  database: z.object({
225
- url: z.string().trim().min(1),
226
- namespace: z.string().trim().min(1),
227
- username: z.string().trim().min(1),
228
- password: z.string().trim().min(1),
216
+ url: nonEmptyStringSchema,
217
+ namespace: nonEmptyStringSchema,
218
+ username: nonEmptyStringSchema,
219
+ password: nonEmptyStringSchema,
229
220
  }),
230
- redis: z.object({ url: z.string().trim().min(1) }),
221
+ redis: z.object({ url: nonEmptyStringSchema }),
231
222
  aiGateway: z.object({
232
- url: z.string().trim().min(1),
233
- key: z.string().trim().min(1),
234
- embeddingModel: z.string().trim().min(1).default('openai/text-embedding-3-small'),
235
- openRouterApiKey: z.string().trim().min(1).optional(),
223
+ url: nonEmptyStringSchema,
224
+ key: nonEmptyStringSchema,
225
+ embeddingModel: nonEmptyStringSchema.default('openai/text-embedding-3-small'),
226
+ openRouterApiKey: nonEmptyStringSchema.optional(),
227
+ maxConcurrency: z.coerce.number().int().positive().default(8),
236
228
  }),
237
229
  s3: z.object({
238
- endpoint: z.string().trim().min(1),
239
- bucket: z.string().trim().min(1),
240
- region: z.string().trim().min(1).default('garage'),
241
- accessKeyId: z.string().trim().min(1),
242
- secretAccessKey: z.string().trim().min(1),
230
+ endpoint: nonEmptyStringSchema,
231
+ bucket: nonEmptyStringSchema,
232
+ region: nonEmptyStringSchema.default('garage'),
233
+ accessKeyId: nonEmptyStringSchema,
234
+ secretAccessKey: nonEmptyStringSchema,
243
235
  attachmentUrlExpiresIn: z.coerce.number().positive().default(1800),
244
236
  }),
245
237
  firecrawl: z.object({
@@ -266,16 +258,25 @@ export const LotaRuntimeConfigSchema = z.object({
266
258
  }),
267
259
  threads: threadConfigSchema.default({}),
268
260
  agents: agentsConfigSchema,
269
- toolProviders: z.custom<Record<string, unknown>>(isToolProviderRecord).optional(),
261
+ toolProviders: z.custom<ToolSet>(isToolSet, { error: 'toolProviders must be a tool registry object' }).optional(),
270
262
  extraSchemaFiles: z.array(z.custom<string | URL>(isStringOrUrl)).optional(),
271
263
  extraWorkers: z.custom<LotaRuntimeWorkerExtensions>(isWorkerExtensionRecord).optional(),
272
- pluginRuntime: z.custom<Record<string, LotaPlugin>>(isPluginRuntimeRecord).optional(),
273
- systemExecutors: z.custom<Record<string, SystemNodeExecutor>>(isSystemExecutorRecord).optional(),
264
+ pluginRuntime: objectRecordSchema<Record<string, LotaPlugin>>().optional(),
265
+ systemExecutors: objectRecordSchema<Record<string, SystemNodeExecutor>>().optional(),
274
266
  notificationService: z.custom<NotificationService>(isNotificationService).optional(),
275
- runtimeAdapters: z.custom<LotaRuntimeAdapters>(isRecord).optional(),
276
- turnHooks: z.custom<LotaRuntimeTurnHooks>(isRecord).optional(),
267
+ runtimeAdapters: objectRecordSchema<LotaRuntimeAdapters>().optional(),
268
+ turnHooks: objectRecordSchema<LotaRuntimeTurnHooks>().optional(),
277
269
  graphDesigner: z.custom<GraphDesigner>(isGraphDesigner).optional(),
278
270
  socialChat: z.custom<LotaRuntimeSocialChatConfig>(isSocialChatConfig).optional(),
271
+ observability: z
272
+ .object({
273
+ otlpBaseUrl: nonEmptyStringSchema.optional(),
274
+ otlpHeaders: stringRecordSchema.optional(),
275
+ serviceName: nonEmptyStringSchema.default('lota-sdk'),
276
+ serviceVersion: nonEmptyStringSchema.optional(),
277
+ devToolsUrl: nonEmptyStringSchema.optional(),
278
+ })
279
+ .default({ serviceName: 'lota-sdk' }),
279
280
  })
280
281
 
281
282
  export type LotaRuntimeConfig = z.input<typeof LotaRuntimeConfigSchema>
@@ -301,6 +302,7 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
301
302
  'AI_GATEWAY_KEY',
302
303
  'AI_EMBEDDING_MODEL',
303
304
  'OPENROUTER_API_KEY',
305
+ 'AI_GATEWAY_MAX_CONCURRENCY',
304
306
  'S3_ENDPOINT',
305
307
  'S3_BUCKET',
306
308
  'S3_REGION',
@@ -313,28 +315,115 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
313
315
  'MEMORY_SEARCH_K',
314
316
  'MEMORY_RERANKER_STRATEGY',
315
317
  'MEMORY_RERANKER_MODEL_ID',
318
+ 'OTLP_BASE_URL',
319
+ 'OTLP_SERVICE_NAME',
320
+ 'OTLP_SERVICE_VERSION',
321
+ 'DEVTOOLS_URL',
316
322
  ])
317
323
 
318
- let runtimeConfig: ResolvedLotaRuntimeConfig | null = null
319
-
320
324
  export function parseLotaRuntimeConfig(config: LotaRuntimeConfig): ResolvedLotaRuntimeConfig {
321
325
  return LotaRuntimeConfigSchema.parse(config)
322
326
  }
323
327
 
324
- export function configureRuntimeConfig(config: ResolvedLotaRuntimeConfig): void {
325
- runtimeConfig = config
328
+ export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>): WorkerBootstrapEnv {
329
+ return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
326
330
  }
327
331
 
328
- export function getRuntimeConfig(): ResolvedLotaRuntimeConfig {
329
- if (!runtimeConfig) {
330
- throw new Error('Lota runtime config not configured. Call createLotaRuntime() first.')
331
- }
332
+ const runtimeEnvironmentConfig = Config.all({
333
+ surrealdbUrl: Config.string('SURREALDB_URL'),
334
+ surrealdbNamespace: Config.string('SURREALDB_NAMESPACE'),
335
+ surrealdbUser: Config.string('SURREALDB_USER'),
336
+ surrealdbPassword: Config.redacted('SURREALDB_PASSWORD'),
337
+ redisUrl: Config.string('REDIS_URL'),
338
+ aiGatewayUrl: Config.string('AI_GATEWAY_URL').pipe(Config.withDefault(DEFAULT_AI_GATEWAY_URL)),
339
+ aiGatewayKey: Config.redacted('AI_GATEWAY_KEY'),
340
+ aiEmbeddingModel: Config.string('AI_EMBEDDING_MODEL').pipe(Config.withDefault('openai/text-embedding-3-small')),
341
+ openRouterApiKey: Config.redacted('OPENROUTER_API_KEY').pipe(Config.option),
342
+ aiGatewayMaxConcurrency: Config.number('AI_GATEWAY_MAX_CONCURRENCY').pipe(Config.withDefault(8)),
343
+ s3Endpoint: Config.string('S3_ENDPOINT'),
344
+ s3Bucket: Config.string('S3_BUCKET'),
345
+ s3Region: Config.string('S3_REGION').pipe(Config.withDefault('garage')),
346
+ s3AccessKeyId: Config.redacted('S3_ACCESS_KEY_ID'),
347
+ s3SecretAccessKey: Config.redacted('S3_SECRET_ACCESS_KEY'),
348
+ attachmentUrlExpiresIn: Config.number('ATTACHMENT_URL_EXPIRES_IN').pipe(Config.withDefault(1800)),
349
+ firecrawlApiKey: Config.redacted('FIRECRAWL_API_KEY'),
350
+ firecrawlApiBaseUrl: Config.string('FIRECRAWL_API_BASE_URL').pipe(Config.option),
351
+ logLevel: Config.string('LOG_LEVEL').pipe(Config.withDefault('info')),
352
+ memorySearchK: Config.number('MEMORY_SEARCH_K').pipe(Config.withDefault(6)),
353
+ memoryRerankerStrategy: Config.string('MEMORY_RERANKER_STRATEGY').pipe(Config.withDefault('rerank')),
354
+ memoryRerankerModelId: Config.string('MEMORY_RERANKER_MODEL_ID').pipe(
355
+ Config.withDefault(OPENROUTER_FAST_RERANK_MODEL_ID),
356
+ ),
357
+ otlpBaseUrl: Config.string('OTLP_BASE_URL').pipe(Config.option),
358
+ otlpServiceName: Config.string('OTLP_SERVICE_NAME').pipe(Config.withDefault('lota-sdk')),
359
+ otlpServiceVersion: Config.string('OTLP_SERVICE_VERSION').pipe(Config.option),
360
+ devToolsUrl: Config.string('DEVTOOLS_URL').pipe(Config.option),
361
+ })
332
362
 
333
- return runtimeConfig
363
+ export type LotaRuntimeEnvironmentOverrides = Omit<
364
+ LotaRuntimeConfig,
365
+ 'database' | 'redis' | 'aiGateway' | 's3' | 'firecrawl' | 'logging' | 'memory'
366
+ > & { logging?: LotaRuntimeConfig['logging']; memory?: LotaRuntimeConfig['memory'] }
367
+
368
+ export function loadLotaRuntimeConfigFromEnv(
369
+ overrides: LotaRuntimeEnvironmentOverrides,
370
+ options: { configProvider?: ConfigProvider.ConfigProvider } = {},
371
+ ) {
372
+ return runtimeEnvironmentConfig
373
+ .parse(options.configProvider ?? ConfigProvider.fromEnv())
374
+ .pipe(
375
+ Effect.map((env) =>
376
+ parseLotaRuntimeConfig({
377
+ ...overrides,
378
+ database: {
379
+ url: env.surrealdbUrl,
380
+ namespace: env.surrealdbNamespace,
381
+ username: env.surrealdbUser,
382
+ password: Redacted.value(env.surrealdbPassword),
383
+ },
384
+ redis: { url: env.redisUrl },
385
+ aiGateway: {
386
+ url: env.aiGatewayUrl,
387
+ key: Redacted.value(env.aiGatewayKey),
388
+ embeddingModel: env.aiEmbeddingModel,
389
+ maxConcurrency: env.aiGatewayMaxConcurrency,
390
+ ...(Option.isSome(env.openRouterApiKey)
391
+ ? { openRouterApiKey: Redacted.value(env.openRouterApiKey.value) }
392
+ : {}),
393
+ },
394
+ s3: {
395
+ endpoint: env.s3Endpoint,
396
+ bucket: env.s3Bucket,
397
+ region: env.s3Region,
398
+ accessKeyId: Redacted.value(env.s3AccessKeyId),
399
+ secretAccessKey: Redacted.value(env.s3SecretAccessKey),
400
+ attachmentUrlExpiresIn: env.attachmentUrlExpiresIn,
401
+ },
402
+ firecrawl: {
403
+ apiKey: Redacted.value(env.firecrawlApiKey),
404
+ ...(Option.isSome(env.firecrawlApiBaseUrl) ? { apiBaseUrl: env.firecrawlApiBaseUrl.value } : {}),
405
+ },
406
+ logging: overrides.logging ?? { level: env.logLevel as (typeof logLevelValues)[number] },
407
+ memory: {
408
+ searchK: env.memorySearchK,
409
+ rerankerStrategy: env.memoryRerankerStrategy as MemoryRerankerStrategy,
410
+ rerankerModelId: env.memoryRerankerModelId,
411
+ ...overrides.memory,
412
+ },
413
+ observability: {
414
+ serviceName: env.otlpServiceName,
415
+ ...(Option.isSome(env.otlpBaseUrl) ? { otlpBaseUrl: env.otlpBaseUrl.value } : {}),
416
+ ...(Option.isSome(env.otlpServiceVersion) ? { serviceVersion: env.otlpServiceVersion.value } : {}),
417
+ ...(Option.isSome(env.devToolsUrl) ? { devToolsUrl: env.devToolsUrl.value } : {}),
418
+ ...overrides.observability,
419
+ },
420
+ }),
421
+ ),
422
+ )
334
423
  }
335
424
 
336
- export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>): WorkerBootstrapEnv {
337
- return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
425
+ export function getRuntimeConfig(): ResolvedLotaRuntimeConfig {
426
+ return getCurrentRuntime().runSync(Effect.service(RuntimeConfigServiceTag))
338
427
  }
339
428
 
340
429
  export type { LotaThreadConfig }
@@ -6,8 +6,17 @@ import type {
6
6
  PlanSpecRecord,
7
7
  } from '@lota-sdk/shared'
8
8
  import type { ToolSet } from 'ai'
9
+ import { Effect } from 'effect'
10
+ import type { Context } from 'effect'
9
11
 
10
12
  import type { RecordIdRef } from '../db/record-id'
13
+ import { getCurrentRuntime } from '../effect/runtime-ref'
14
+ import {
15
+ RuntimeAdaptersServiceTag,
16
+ RuntimeWorkerExtensionsServiceTag,
17
+ ToolProvidersServiceTag,
18
+ TurnHooksServiceTag,
19
+ } from '../effect/services'
11
20
  import type { ReadableUploadMetadata } from '../storage/attachment-types'
12
21
  import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
13
22
 
@@ -193,54 +202,32 @@ export interface LotaRuntimeAdapters {
193
202
  withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
194
203
  }
195
204
 
196
- interface RuntimeExtensionsState {
197
- adapters: LotaRuntimeAdapters
198
- turnHooks: LotaRuntimeTurnHooks
199
- toolProviders: ToolSet
200
- extraWorkers: LotaRuntimeWorkerExtensions
201
- }
202
-
203
- const EMPTY_TOOLS = {} as ToolSet
204
-
205
- let runtimeExtensionsState: RuntimeExtensionsState = {
206
- adapters: {},
207
- turnHooks: {},
208
- toolProviders: EMPTY_TOOLS,
209
- extraWorkers: {},
210
- }
211
-
212
- export function configureRuntimeExtensions(params: {
213
- adapters?: LotaRuntimeAdapters
214
- turnHooks?: LotaRuntimeTurnHooks
215
- toolProviders?: ToolSet
216
- extraWorkers?: LotaRuntimeWorkerExtensions
217
- }): void {
218
- runtimeExtensionsState = {
219
- adapters: params.adapters ?? {},
220
- turnHooks: params.turnHooks ?? {},
221
- toolProviders: params.toolProviders ?? EMPTY_TOOLS,
222
- extraWorkers: params.extraWorkers ?? {},
223
- }
205
+ function resolveFromRuntime<I, T>(tag: Context.Key<I, T>): T {
206
+ return getCurrentRuntime().runSync(Effect.service(tag))
224
207
  }
225
208
 
226
209
  export function getRuntimeAdapters(): LotaRuntimeAdapters {
227
- return runtimeExtensionsState.adapters
210
+ return resolveFromRuntime(RuntimeAdaptersServiceTag)
228
211
  }
229
212
 
230
213
  export function getTurnHooks(): LotaRuntimeTurnHooks {
231
- return runtimeExtensionsState.turnHooks
214
+ return resolveFromRuntime(TurnHooksServiceTag)
232
215
  }
233
216
 
234
217
  export function getToolProviders(): ToolSet {
235
- return runtimeExtensionsState.toolProviders
218
+ return resolveFromRuntime(ToolProvidersServiceTag)
236
219
  }
237
220
 
238
221
  export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) | undefined {
239
- return runtimeExtensionsState.adapters.connectPluginDatabases
222
+ return getRuntimeAdapters().connectPluginDatabases
223
+ }
224
+
225
+ export function getExtraWorkers(): LotaRuntimeWorkerExtensions {
226
+ return resolveFromRuntime(RuntimeWorkerExtensionsServiceTag)
240
227
  }
241
228
 
242
- export async function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
243
- const adapter = runtimeExtensionsState.adapters.withWorkspaceMemoryLock
229
+ export function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
230
+ const adapter = getRuntimeAdapters().withWorkspaceMemoryLock
244
231
  if (!adapter) {
245
232
  return fn()
246
233
  }
@@ -0,0 +1,157 @@
1
+ import { stepCountIs } from 'ai'
2
+ import type { ToolSet } from 'ai'
3
+ import { Schema, Effect } from 'effect'
4
+
5
+ import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../../config/agent-defaults'
6
+ import { aiLogger } from '../../config/logger'
7
+
8
+ interface ExecutableToolDefinition {
9
+ execute: (...args: unknown[]) => Promise<unknown>
10
+ }
11
+
12
+ class SocialChatAgentRunnerError extends Schema.TaggedErrorClass<SocialChatAgentRunnerError>()(
13
+ 'SocialChatAgentRunnerError',
14
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
15
+ ) {}
16
+
17
+ const SOCIAL_CHAT_RETRIEVAL_TOOL_NAMES = [
18
+ 'memorySearch',
19
+ 'conversationSearch',
20
+ 'queryKnowledge',
21
+ 'researchTopic',
22
+ 'searchWeb',
23
+ 'fetchWebpage',
24
+ 'inspectWebsite',
25
+ ] as const
26
+
27
+ export function withLoggedSocialToolSet(
28
+ tools: ToolSet,
29
+ params: { agentId: string; channelId: string; threadId: string; executedToolNames: string[] },
30
+ ): ToolSet {
31
+ return Object.fromEntries(
32
+ Object.entries(tools).map(([toolName, toolDefinition]) => {
33
+ if (
34
+ typeof toolDefinition !== 'object' ||
35
+ !('execute' in toolDefinition) ||
36
+ typeof toolDefinition.execute !== 'function'
37
+ ) {
38
+ return [toolName, toolDefinition]
39
+ }
40
+
41
+ const executableTool = toolDefinition as typeof toolDefinition & ExecutableToolDefinition
42
+ return [
43
+ toolName,
44
+ {
45
+ ...toolDefinition,
46
+ execute: (...args: unknown[]): Promise<unknown> =>
47
+ Effect.runPromise(
48
+ Effect.gen(function* () {
49
+ aiLogger.info`Slack social-chat tool start: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
50
+ const result: unknown = yield* Effect.tryPromise({
51
+ try: () => executableTool.execute(...args),
52
+ catch: (error: unknown) => new SocialChatAgentRunnerError({ message: String(error), cause: error }),
53
+ })
54
+ params.executedToolNames.push(toolName)
55
+ aiLogger.info`Slack social-chat tool finish: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
56
+ return result
57
+ }).pipe(
58
+ Effect.tapError((error) =>
59
+ Effect.sync(() => {
60
+ aiLogger.warn`Slack social-chat tool failed: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}, error=${error}`
61
+ }),
62
+ ),
63
+ ),
64
+ ),
65
+ },
66
+ ]
67
+ }),
68
+ )
69
+ }
70
+
71
+ function runSocialAgentTurnEffect(params: {
72
+ agentId: string
73
+ mode: 'fixedThreadMode' | 'threadMode'
74
+ threadType: 'group'
75
+ onboardingActive: boolean
76
+ linearInstalled: boolean
77
+ systemWorkspaceDetails?: string
78
+ preSeededMemoriesSection?: string
79
+ retrievedKnowledgeSection?: string
80
+ learnedSkillsSection?: string
81
+ userMessageText?: string
82
+ additionalInstructionSections?: string[]
83
+ prompt: string
84
+ tools: ToolSet
85
+ abortSignal: AbortSignal
86
+ }): Effect.Effect<{ text: string; displayName: string }, SocialChatAgentRunnerError> {
87
+ return Effect.gen(function* () {
88
+ const runtimeConfig = getAgentRuntimeConfig({
89
+ agentId: params.agentId,
90
+ threadType: params.threadType,
91
+ mode: params.mode,
92
+ onboardingActive: params.onboardingActive,
93
+ linearInstalled: params.linearInstalled,
94
+ systemWorkspaceDetails: params.systemWorkspaceDetails,
95
+ preSeededMemoriesSection: params.preSeededMemoriesSection,
96
+ retrievedKnowledgeSection: params.retrievedKnowledgeSection,
97
+ learnedSkillsSection: params.learnedSkillsSection,
98
+ userMessageText: params.userMessageText,
99
+ ruleOptions: {
100
+ includeMemr3Rule: SOCIAL_CHAT_RETRIEVAL_TOOL_NAMES.some((toolName) => toolName in params.tools),
101
+ includeDomainReasoningFallbackRule: params.agentId === 'cpo' || params.agentId === 'mentor',
102
+ },
103
+ additionalInstructionSections: params.additionalInstructionSections,
104
+ })
105
+
106
+ const agentFactoryEntry = getResolvedAgentFactoryConfig().createAgent[runtimeConfig.id]
107
+ if (typeof agentFactoryEntry !== 'function') {
108
+ return yield* new SocialChatAgentRunnerError({
109
+ message: `Social chat agent factory is not configured for ${runtimeConfig.id}`,
110
+ })
111
+ }
112
+ const agentFactory = agentFactoryEntry
113
+
114
+ const agent = agentFactory({
115
+ mode: params.mode,
116
+ tools: params.tools,
117
+ extraInstructions: runtimeConfig.extraInstructions,
118
+ stopWhen: [stepCountIs(runtimeConfig.maxSteps)],
119
+ })
120
+ const response = yield* Effect.promise(() =>
121
+ agent.generate({ prompt: params.prompt, abortSignal: params.abortSignal }),
122
+ )
123
+ const text = response.text.trim()
124
+ if (!text) {
125
+ return yield* new SocialChatAgentRunnerError({
126
+ message: `Social chat agent ${params.agentId} returned an empty response.`,
127
+ })
128
+ }
129
+
130
+ return {
131
+ text,
132
+ displayName:
133
+ typeof runtimeConfig.displayName === 'string' && runtimeConfig.displayName.trim()
134
+ ? runtimeConfig.displayName.trim()
135
+ : params.agentId,
136
+ }
137
+ })
138
+ }
139
+
140
+ export function runSocialAgentTurn(params: {
141
+ agentId: string
142
+ mode: 'fixedThreadMode' | 'threadMode'
143
+ threadType: 'group'
144
+ onboardingActive: boolean
145
+ linearInstalled: boolean
146
+ systemWorkspaceDetails?: string
147
+ preSeededMemoriesSection?: string
148
+ retrievedKnowledgeSection?: string
149
+ learnedSkillsSection?: string
150
+ userMessageText?: string
151
+ additionalInstructionSections?: string[]
152
+ prompt: string
153
+ tools: ToolSet
154
+ abortSignal: AbortSignal
155
+ }): Promise<{ text: string; displayName: string }> {
156
+ return Effect.runPromise(runSocialAgentTurnEffect(params))
157
+ }
@@ -1,8 +1,18 @@
1
1
  import { stripSlackToolExecutionNoticeMarkdown } from '@lota-sdk/shared'
2
2
  import type { Message, Thread } from 'chat'
3
+ import { Schema, Effect } from 'effect'
3
4
 
4
- import type { SocialChatHistoryMessage } from '../services/social-chat-history.service'
5
- import { toHistoryMessages, toOptionalTrimmedString } from './thread-chat-helpers'
5
+ import type {
6
+ SocialChatHistoryMessage,
7
+ SocialChatHistoryMetadata,
8
+ SocialChatHistoryPart,
9
+ } from '../../services/social-chat-history.service'
10
+ import { toHistoryMessages, toOptionalTrimmedString } from '../thread-chat-helpers'
11
+
12
+ class SocialChatHistoryError extends Schema.TaggedErrorClass<SocialChatHistoryError>()('SocialChatHistoryError', {
13
+ message: Schema.String,
14
+ cause: Schema.optional(Schema.Defect),
15
+ }) {}
6
16
 
7
17
  export function readSlackAuthorName(message: Message): string | undefined {
8
18
  return (
@@ -14,13 +24,9 @@ export function readSlackAuthorName(message: Message): string | undefined {
14
24
  }
15
25
 
16
26
  export function buildSocialChatThreadTranscript(
17
- messages: Array<{
18
- role: 'user' | 'assistant'
19
- parts: Array<Record<string, unknown>>
20
- metadata?: Record<string, unknown>
21
- }>,
27
+ messages: ReadonlyArray<Pick<SocialChatHistoryMessage, 'role' | 'parts' | 'metadata'>>,
22
28
  ): string {
23
- const historyMessages = toHistoryMessages(messages)
29
+ const historyMessages = toHistoryMessages([...messages])
24
30
  if (historyMessages.length === 0) return 'No prior thread history.'
25
31
 
26
32
  return historyMessages
@@ -58,11 +64,11 @@ export function normalizeSocialHistoryMessage(params: {
58
64
  mediaType: attachment.mimeType ?? 'application/octet-stream',
59
65
  sizeBytes: typeof attachment.size === 'number' ? attachment.size : null,
60
66
  storageKey: attachment.url ?? 'external',
61
- }))
67
+ })) satisfies SocialChatHistoryPart[]
62
68
  const parts = [...(text ? [{ type: 'text', text }] : []), ...fileParts]
63
69
  if (parts.length === 0) return null
64
70
 
65
- const metadata: Record<string, unknown> = {
71
+ const metadata: SocialChatHistoryMetadata = {
66
72
  platform: 'slack',
67
73
  channelId: params.channelId,
68
74
  threadId: params.message.threadId,
@@ -97,14 +103,30 @@ export function normalizeSocialHistoryMessage(params: {
97
103
  }
98
104
  }
99
105
 
100
- export async function collectThreadMessages(thread: Thread, incomingMessage: Message): Promise<Message[]> {
101
- try {
102
- const messages: Message[] = []
103
- for await (const message of thread.allMessages) {
104
- messages.push(message)
105
- }
106
- return messages.length > 0 ? messages : [incomingMessage]
107
- } catch {
108
- return thread.recentMessages.length > 0 ? [...thread.recentMessages] : [incomingMessage]
109
- }
106
+ export function collectThreadMessages(thread: Thread, incomingMessage: Message): Promise<Message[]> {
107
+ return Effect.runPromise(
108
+ Effect.catch(
109
+ Effect.gen(function* () {
110
+ const collected: Message[] = []
111
+ const iterator = thread.allMessages[Symbol.asyncIterator]()
112
+
113
+ for (;;) {
114
+ const next = yield* Effect.tryPromise({
115
+ try: () => iterator.next(),
116
+ catch: (error: unknown) =>
117
+ new SocialChatHistoryError({ message: 'Failed to read thread messages', cause: error }),
118
+ })
119
+ if (next.done) break
120
+ collected.push(next.value)
121
+ }
122
+
123
+ return collected.length > 0
124
+ ? collected
125
+ : thread.recentMessages.length > 0
126
+ ? [...thread.recentMessages]
127
+ : [incomingMessage]
128
+ }),
129
+ () => Effect.succeed(thread.recentMessages.length > 0 ? [...thread.recentMessages] : [incomingMessage]),
130
+ ),
131
+ )
110
132
  }