@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,10 +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'
8
+ import { OPENROUTER_FAST_RERANK_MODEL_ID } from '../config/model-constants'
6
9
  import type { LotaThreadConfig, ThreadBootstrapWelcomeConfig } from '../config/thread-defaults'
7
10
  import type { RecordIdRef } from '../db/record-id'
11
+ import { getCurrentRuntime } from '../effect/runtime-ref'
12
+ import { RuntimeConfigServiceTag } from '../effect/services'
8
13
  import type { NotificationService } from '../services/notification.service'
9
14
  import { isRecord } from '../utils/string'
10
15
  import type { GraphDesigner } from './graph-designer'
@@ -13,6 +18,11 @@ import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime-extens
13
18
  import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
14
19
 
15
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
+ }
16
26
 
17
27
  function isStringOrUrl(value: unknown): value is string | URL {
18
28
  return typeof value === 'string' || value instanceof URL
@@ -22,26 +32,14 @@ function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
22
32
  return typeof value === 'function'
23
33
  }
24
34
 
25
- function isStringRecord(value: unknown): value is Record<string, string> {
26
- return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string')
35
+ function isToolSet(value: unknown): value is ToolSet {
36
+ return isRecord(value)
27
37
  }
28
38
 
29
39
  function isAgentFactoryRegistry(value: unknown): value is AgentFactory {
30
40
  return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'function')
31
41
  }
32
42
 
33
- function isPluginRuntimeRecord(value: unknown): value is Record<string, LotaPlugin> {
34
- return isRecord(value)
35
- }
36
-
37
- function isSystemExecutorRecord(value: unknown): value is Record<string, SystemNodeExecutor> {
38
- return isRecord(value)
39
- }
40
-
41
- function isToolProviderRecord(value: unknown): value is Record<string, unknown> {
42
- return isRecord(value)
43
- }
44
-
45
43
  function isNotificationService(value: unknown): value is NotificationService {
46
44
  return isRecord(value) && isFunction(value.notify) && isFunction(value.remind) && isFunction(value.escalate)
47
45
  }
@@ -145,7 +143,7 @@ function isSocialChatConfig(value: unknown): value is LotaRuntimeSocialChatConfi
145
143
  }
146
144
 
147
145
  const threadBootstrapWelcomeConfigSchema = z.object({
148
- defaultAgentId: z.string().trim().min(1),
146
+ defaultAgentId: nonEmptyStringSchema,
149
147
  buildMessageText: z.custom<ThreadBootstrapWelcomeConfig['buildMessageText']>(isFunction, {
150
148
  error: 'onboardingWelcome.buildMessageText must be a function',
151
149
  }),
@@ -155,9 +153,9 @@ const threadConfigSchema = z
155
153
  .object({
156
154
  bootstrap: z
157
155
  .object({
158
- onboardingDefaultAgents: z.array(z.string().trim().min(1)).optional(),
159
- completedDefaultAgents: z.array(z.string().trim().min(1)).optional(),
160
- 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(),
161
159
  ensureDefaultGroupOnCompleted: z.boolean().optional(),
162
160
  onboardingWelcome: threadBootstrapWelcomeConfigSchema.optional(),
163
161
  })
@@ -168,19 +166,13 @@ const threadConfigSchema = z
168
166
 
169
167
  const agentsConfigSchema = z
170
168
  .object({
171
- roster: z.array(z.string().trim().min(1)).min(1),
172
- leadAgentId: z.string().trim().min(1),
173
- displayNames: z.custom<Record<string, string>>(isStringRecord, {
174
- error: 'agents.displayNames must be a string record',
175
- }),
176
- shortDisplayNames: z
177
- .custom<Record<string, string>>(isStringRecord, { error: 'agents.shortDisplayNames must be a string record' })
178
- .optional(),
179
- descriptions: z
180
- .custom<Record<string, string>>(isStringRecord, { error: 'agents.descriptions must be a string record' })
181
- .optional(),
182
- routerModelId: z.string().trim().min(1).optional(),
183
- 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),
184
176
  getCoreThreadProfile: z
185
177
  .custom<(coreType: string) => CoreThreadProfile>(isFunction, {
186
178
  error: 'agents.getCoreThreadProfile must be a function',
@@ -216,25 +208,30 @@ const agentsConfigSchema = z
216
208
  }
217
209
  })
218
210
 
211
+ export const MemoryRerankerStrategySchema = z.enum(['helper-model', 'rerank'])
212
+ export type MemoryRerankerStrategy = z.infer<typeof MemoryRerankerStrategySchema>
213
+
219
214
  export const LotaRuntimeConfigSchema = z.object({
220
215
  database: z.object({
221
- url: z.string().trim().min(1),
222
- namespace: z.string().trim().min(1),
223
- username: z.string().trim().min(1),
224
- password: z.string().trim().min(1),
216
+ url: nonEmptyStringSchema,
217
+ namespace: nonEmptyStringSchema,
218
+ username: nonEmptyStringSchema,
219
+ password: nonEmptyStringSchema,
225
220
  }),
226
- redis: z.object({ url: z.string().trim().min(1) }),
221
+ redis: z.object({ url: nonEmptyStringSchema }),
227
222
  aiGateway: z.object({
228
- url: z.string().trim().min(1),
229
- key: z.string().trim().min(1),
230
- embeddingModel: z.string().trim().min(1).default('openai/text-embedding-3-small'),
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),
231
228
  }),
232
229
  s3: z.object({
233
- endpoint: z.string().trim().min(1),
234
- bucket: z.string().trim().min(1),
235
- region: z.string().trim().min(1).default('garage'),
236
- accessKeyId: z.string().trim().min(1),
237
- secretAccessKey: z.string().trim().min(1),
230
+ endpoint: nonEmptyStringSchema,
231
+ bucket: nonEmptyStringSchema,
232
+ region: nonEmptyStringSchema.default('garage'),
233
+ accessKeyId: nonEmptyStringSchema,
234
+ secretAccessKey: nonEmptyStringSchema,
238
235
  attachmentUrlExpiresIn: z.coerce.number().positive().default(1800),
239
236
  }),
240
237
  firecrawl: z.object({
@@ -250,20 +247,36 @@ export const LotaRuntimeConfigSchema = z.object({
250
247
  .object({
251
248
  searchK: z.coerce.number().int().positive().default(6),
252
249
  embeddingCacheTtlSeconds: z.coerce.number().int().positive().default(7200),
250
+ rerankerStrategy: MemoryRerankerStrategySchema.default('rerank'),
251
+ rerankerModelId: z.string().trim().min(1).default(OPENROUTER_FAST_RERANK_MODEL_ID),
253
252
  })
254
- .default({ searchK: 6, embeddingCacheTtlSeconds: 7200 }),
253
+ .default({
254
+ searchK: 6,
255
+ embeddingCacheTtlSeconds: 7200,
256
+ rerankerStrategy: 'rerank',
257
+ rerankerModelId: OPENROUTER_FAST_RERANK_MODEL_ID,
258
+ }),
255
259
  threads: threadConfigSchema.default({}),
256
260
  agents: agentsConfigSchema,
257
- toolProviders: z.custom<Record<string, unknown>>(isToolProviderRecord).optional(),
261
+ toolProviders: z.custom<ToolSet>(isToolSet, { error: 'toolProviders must be a tool registry object' }).optional(),
258
262
  extraSchemaFiles: z.array(z.custom<string | URL>(isStringOrUrl)).optional(),
259
263
  extraWorkers: z.custom<LotaRuntimeWorkerExtensions>(isWorkerExtensionRecord).optional(),
260
- pluginRuntime: z.custom<Record<string, LotaPlugin>>(isPluginRuntimeRecord).optional(),
261
- systemExecutors: z.custom<Record<string, SystemNodeExecutor>>(isSystemExecutorRecord).optional(),
264
+ pluginRuntime: objectRecordSchema<Record<string, LotaPlugin>>().optional(),
265
+ systemExecutors: objectRecordSchema<Record<string, SystemNodeExecutor>>().optional(),
262
266
  notificationService: z.custom<NotificationService>(isNotificationService).optional(),
263
- runtimeAdapters: z.custom<LotaRuntimeAdapters>(isRecord).optional(),
264
- turnHooks: z.custom<LotaRuntimeTurnHooks>(isRecord).optional(),
267
+ runtimeAdapters: objectRecordSchema<LotaRuntimeAdapters>().optional(),
268
+ turnHooks: objectRecordSchema<LotaRuntimeTurnHooks>().optional(),
265
269
  graphDesigner: z.custom<GraphDesigner>(isGraphDesigner).optional(),
266
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' }),
267
280
  })
268
281
 
269
282
  export type LotaRuntimeConfig = z.input<typeof LotaRuntimeConfigSchema>
@@ -288,6 +301,8 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
288
301
  'AI_GATEWAY_URL',
289
302
  'AI_GATEWAY_KEY',
290
303
  'AI_EMBEDDING_MODEL',
304
+ 'OPENROUTER_API_KEY',
305
+ 'AI_GATEWAY_MAX_CONCURRENCY',
291
306
  'S3_ENDPOINT',
292
307
  'S3_BUCKET',
293
308
  'S3_REGION',
@@ -298,28 +313,117 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
298
313
  'FIRECRAWL_API_BASE_URL',
299
314
  'LOG_LEVEL',
300
315
  'MEMORY_SEARCH_K',
316
+ 'MEMORY_RERANKER_STRATEGY',
317
+ 'MEMORY_RERANKER_MODEL_ID',
318
+ 'OTLP_BASE_URL',
319
+ 'OTLP_SERVICE_NAME',
320
+ 'OTLP_SERVICE_VERSION',
321
+ 'DEVTOOLS_URL',
301
322
  ])
302
323
 
303
- let runtimeConfig: ResolvedLotaRuntimeConfig | null = null
304
-
305
324
  export function parseLotaRuntimeConfig(config: LotaRuntimeConfig): ResolvedLotaRuntimeConfig {
306
325
  return LotaRuntimeConfigSchema.parse(config)
307
326
  }
308
327
 
309
- export function configureRuntimeConfig(config: ResolvedLotaRuntimeConfig): void {
310
- runtimeConfig = config
328
+ export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>): WorkerBootstrapEnv {
329
+ return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
311
330
  }
312
331
 
313
- export function getRuntimeConfig(): ResolvedLotaRuntimeConfig {
314
- if (!runtimeConfig) {
315
- throw new Error('Lota runtime config not configured. Call createLotaRuntime() first.')
316
- }
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
+ })
317
362
 
318
- 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
+ )
319
423
  }
320
424
 
321
- export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>): WorkerBootstrapEnv {
322
- return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
425
+ export function getRuntimeConfig(): ResolvedLotaRuntimeConfig {
426
+ return getCurrentRuntime().runSync(Effect.service(RuntimeConfigServiceTag))
323
427
  }
324
428
 
325
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
  }