@lota-sdk/core 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -0,0 +1,630 @@
1
+ import { createSlackAdapter } from '@chat-adapter/slack'
2
+ import { createIoRedisState } from '@chat-adapter/state-ioredis'
3
+ import type { IoRedisStateClientOptions } from '@chat-adapter/state-ioredis'
4
+ import {
5
+ buildSlackSocialReplyMarkdown,
6
+ CONSULT_SPECIALIST_TOOL_NAME,
7
+ ConsultSpecialistArgsSchema,
8
+ } from '@lota-sdk/shared'
9
+ import type { ChatMessage, ConsultSpecialistArgs } from '@lota-sdk/shared'
10
+ import { tool as createTool } from 'ai'
11
+ import { Chat, ConsoleLogger } from 'chat'
12
+ import type { Message, Thread, WebhookOptions } from 'chat'
13
+ import type { Context, Cause } from 'effect'
14
+ import { Clock, Effect, Schema } from 'effect'
15
+
16
+ import { getAgentDisplayNames, getTeamConsultParticipants } from '../../config/agent-defaults'
17
+ import { aiLogger } from '../../config/logger'
18
+ import { recordIdToString } from '../../db/record-id'
19
+ import { TABLES } from '../../db/tables'
20
+ import { ForbiddenError } from '../../effect/errors'
21
+ import { effectTryMaybeAsync, effectTryPromise } from '../../effect/helpers'
22
+ import { enqueueRegularChatMemoryDigest, enqueueSkillExtraction } from '../../queues/organization-learning.queue'
23
+ import { enqueuePostChatMemory } from '../../queues/post-chat-memory.queue'
24
+ import type { LearnedSkillServiceTag } from '../../services/learned-skill.service'
25
+ import type { MemoryServiceTag } from '../../services/memory/memory.service'
26
+ import type { SocialChatHistoryError, makeSocialChatHistoryService } from '../../services/social-chat-history.service'
27
+ import { safeEnqueue } from '../../utils/async'
28
+ import { buildAgentPromptContext } from '../agent-prompt-context'
29
+ import { createServerRunAbortController } from '../agent-stream-helpers'
30
+ import type {
31
+ BuildSocialChatAgentToolsParams,
32
+ LotaRuntimeSocialChatConfig,
33
+ LotaSocialChatResolvedContext,
34
+ } from '../runtime-config'
35
+ import { getRuntimeAdapters } from '../runtime-extensions'
36
+ import { runSpecialistSession } from '../specialist-runner'
37
+ import {
38
+ buildAgentHistoryMessages,
39
+ extractMessageText,
40
+ toHistoryMessages,
41
+ toOptionalTrimmedString,
42
+ } from '../thread-chat-helpers'
43
+ import { runSocialAgentTurn, withLoggedSocialToolSet } from './social-chat-agent-runner'
44
+ import {
45
+ collectThreadMessages,
46
+ createSocialChatCursorId,
47
+ normalizeSocialHistoryMessage,
48
+ readSlackAuthorName,
49
+ buildSocialChatThreadTranscript,
50
+ } from './social-chat-history'
51
+ import {
52
+ buildLeadSocialChatPrompt,
53
+ buildSocialChatIdentitySection,
54
+ buildSpecialistSocialChatPrompt,
55
+ } from './social-chat-prompts'
56
+
57
+ export interface SocialChatRuntimeServices {
58
+ learnedSkillService: Context.Service.Shape<typeof LearnedSkillServiceTag>
59
+ memoryService: Context.Service.Shape<typeof MemoryServiceTag>
60
+ socialChatHistoryService: ReturnType<typeof makeSocialChatHistoryService>
61
+ }
62
+
63
+ class SocialChatServiceError extends Schema.TaggedErrorClass<SocialChatServiceError>()('SocialChatServiceError', {
64
+ message: Schema.String,
65
+ cause: Schema.optional(Schema.Defect),
66
+ }) {}
67
+
68
+ const DEFAULT_SOCIAL_CHAT_AGENT_ID = 'socialChat'
69
+ const DEFAULT_SOCIAL_CHAT_AGENT_DISPLAY_NAME = 'Lota'
70
+ const DEFAULT_SOCIAL_CHAT_STATE_PREFIX = 'lota:social:chat-sdk'
71
+ const DEFAULT_SOCIAL_CHAT_DEDUPE_TTL_MS = 15 * 60 * 1000
72
+ const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
73
+
74
+ export interface LotaRuntimeSocialChat {
75
+ enabled: boolean
76
+ initialize(): Promise<void>
77
+ shutdown(): Promise<void>
78
+ webhooks: { slack(request: Request, options?: WebhookOptions): Promise<Response> }
79
+ }
80
+
81
+ type SocialChatStateRedisClient = IoRedisStateClientOptions['client']
82
+
83
+ interface SlackSocialMessageContext {
84
+ channelId: string
85
+ threadId: string
86
+ messageId: string
87
+ text: string
88
+ authorId?: string
89
+ authorName?: string
90
+ }
91
+
92
+ function createDisabledSocialChatRuntime(): LotaRuntimeSocialChat {
93
+ return {
94
+ enabled: false,
95
+ initialize: () => Promise.resolve(),
96
+ shutdown: () => Promise.resolve(),
97
+ webhooks: { slack: () => Promise.resolve(new Response('Social chat is disabled.', { status: 404 })) },
98
+ }
99
+ }
100
+
101
+ function createAssistantMessage(params: {
102
+ agentId: string
103
+ agentName: string
104
+ text: string
105
+ createdAt: number
106
+ }): ChatMessage {
107
+ return {
108
+ id: Bun.randomUUIDv7(),
109
+ role: 'assistant',
110
+ parts: [{ type: 'text', text: params.text }],
111
+ metadata: { agentId: params.agentId, agentName: params.agentName, createdAt: params.createdAt },
112
+ }
113
+ }
114
+
115
+ function getAgentDisplayName(agentId: string): string {
116
+ return getAgentDisplayNames()[agentId] ?? agentId
117
+ }
118
+
119
+ function toSafeJobIdSegment(value: string): string {
120
+ return value.replace(/[^a-zA-Z0-9_-]/g, '_')
121
+ }
122
+
123
+ function createSocialMemoryDedupeKey(params: { workspaceId: string; threadId: string; messageId: string }): string {
124
+ return [
125
+ 'social-memory',
126
+ toSafeJobIdSegment(params.workspaceId),
127
+ toSafeJobIdSegment(params.threadId),
128
+ toSafeJobIdSegment(params.messageId),
129
+ ].join('-')
130
+ }
131
+
132
+ function buildBuildToolsParams(params: {
133
+ agentId: string
134
+ context: LotaSocialChatResolvedContext
135
+ workspaceIdString: string
136
+ userIdString: string
137
+ messageContext: SlackSocialMessageContext
138
+ memoryBlock: string
139
+ onAppendMemoryBlock: (value: string) => void
140
+ }): BuildSocialChatAgentToolsParams {
141
+ return {
142
+ agentId: params.agentId,
143
+ workspaceId: params.context.workspaceId,
144
+ workspaceIdString: params.workspaceIdString,
145
+ userId: params.context.userId,
146
+ userIdString: params.userIdString,
147
+ userName: params.context.userName,
148
+ platform: 'slack',
149
+ channelId: params.messageContext.channelId,
150
+ threadId: params.messageContext.threadId,
151
+ incomingMessageId: params.messageContext.messageId,
152
+ incomingText: params.messageContext.text,
153
+ memoryBlock: params.memoryBlock,
154
+ onAppendMemoryBlock: params.onAppendMemoryBlock,
155
+ context: null,
156
+ }
157
+ }
158
+
159
+ export function createSocialChatRuntime(params: {
160
+ redisClient: SocialChatStateRedisClient
161
+ socialChat?: LotaRuntimeSocialChatConfig
162
+ services: SocialChatRuntimeServices
163
+ }): LotaRuntimeSocialChat {
164
+ const socialChatConfig = params.socialChat
165
+ const slackConfig = socialChatConfig?.slack
166
+ const slackEnabled = Boolean(slackConfig?.botToken?.trim() && slackConfig.signingSecret?.trim())
167
+
168
+ if (!socialChatConfig || !slackEnabled) {
169
+ return createDisabledSocialChatRuntime()
170
+ }
171
+
172
+ const socialAgentId = toOptionalTrimmedString(socialChatConfig.agentId) ?? DEFAULT_SOCIAL_CHAT_AGENT_ID
173
+ const socialAgentDisplayName =
174
+ toOptionalTrimmedString(socialChatConfig.agentDisplayName) ?? DEFAULT_SOCIAL_CHAT_AGENT_DISPLAY_NAME
175
+ const stateKeyPrefix =
176
+ toOptionalTrimmedString(socialChatConfig.stateRedisKeyPrefix) ?? DEFAULT_SOCIAL_CHAT_STATE_PREFIX
177
+ const chat = new Chat({
178
+ userName: toOptionalTrimmedString(slackConfig?.userName) ?? socialAgentDisplayName,
179
+ adapters: {
180
+ slack: createSlackAdapter({
181
+ botToken: slackConfig?.botToken,
182
+ signingSecret: slackConfig?.signingSecret,
183
+ userName: toOptionalTrimmedString(slackConfig?.userName) ?? socialAgentDisplayName,
184
+ }),
185
+ },
186
+ state: createIoRedisState({
187
+ client: params.redisClient,
188
+ keyPrefix: stateKeyPrefix,
189
+ logger: new ConsoleLogger('warn', 'lota-social-chat'),
190
+ }),
191
+ dedupeTtlMs: slackConfig?.dedupeTtlMs ?? DEFAULT_SOCIAL_CHAT_DEDUPE_TTL_MS,
192
+ })
193
+
194
+ const initialize = () => Effect.runPromise(effectTryPromise(() => chat.initialize()))
195
+ const shutdown = () => Effect.runPromise(effectTryPromise(() => chat.shutdown()))
196
+
197
+ const handleMessage = (
198
+ thread: Thread,
199
+ incomingMessage: Message,
200
+ ): Effect.Effect<
201
+ void,
202
+ Cause.UnknownError | ForbiddenError | SocialChatHistoryError | SocialChatServiceError,
203
+ never
204
+ > =>
205
+ Effect.scoped(
206
+ Effect.gen(function* () {
207
+ const { memoryService, learnedSkillService, socialChatHistoryService } = params.services
208
+ const currentContext = yield* Effect.context()
209
+ const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
210
+ const rawSlackMessage = incomingMessage.raw as { channel?: unknown } | undefined
211
+ const channelId = toOptionalTrimmedString(rawSlackMessage?.channel) ?? thread.channelId
212
+ const messageContext: SlackSocialMessageContext = {
213
+ channelId,
214
+ threadId: thread.id,
215
+ messageId: incomingMessage.id,
216
+ text: incomingMessage.text.trim(),
217
+ authorId: toOptionalTrimmedString(incomingMessage.author.userId) ?? undefined,
218
+ authorName: readSlackAuthorName(incomingMessage),
219
+ }
220
+ aiLogger.debug`Slack social-chat message received: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}, messageId=${messageContext.messageId}, author=${messageContext.authorName ?? 'unknown'}, textLength=${messageContext.text.length}`
221
+
222
+ const resolvedContext = yield* effectTryMaybeAsync(() =>
223
+ socialChatConfig.resolveContext({
224
+ platform: 'slack',
225
+ channelId: messageContext.channelId,
226
+ threadId: messageContext.threadId,
227
+ messageId: messageContext.messageId,
228
+ text: messageContext.text,
229
+ authorId: messageContext.authorId,
230
+ authorName: messageContext.authorName,
231
+ }),
232
+ )
233
+ const workspaceIdString = recordIdToString(resolvedContext.workspaceId, TABLES.ORGANIZATION)
234
+ const userIdString = recordIdToString(resolvedContext.userId, TABLES.USER)
235
+ aiLogger.debug`Slack social-chat context resolved: workspaceId=${workspaceIdString}, userId=${userIdString}`
236
+
237
+ const threadMessages = yield* effectTryPromise(() => collectThreadMessages(thread, incomingMessage))
238
+ const normalizedMessages = threadMessages
239
+ .map((message) =>
240
+ normalizeSocialHistoryMessage({
241
+ workspaceId: workspaceIdString,
242
+ channelId: messageContext.channelId,
243
+ agentId: socialAgentId,
244
+ agentDisplayName: socialAgentDisplayName,
245
+ message,
246
+ }),
247
+ )
248
+ .filter((message): message is NonNullable<typeof message> => message !== null)
249
+ yield* socialChatHistoryService.upsertMessages(normalizedMessages)
250
+
251
+ const historyBeforeReply = yield* socialChatHistoryService.listThreadMessages({
252
+ workspaceId: workspaceIdString,
253
+ threadId: messageContext.threadId,
254
+ })
255
+ const currentUserMessage =
256
+ historyBeforeReply.find((message) => message.messageId === incomingMessage.id) ?? historyBeforeReply.at(-1)
257
+ const priorHistory = currentUserMessage
258
+ ? historyBeforeReply.filter((message) => message.cursor.id !== currentUserMessage.cursor.id)
259
+ : historyBeforeReply
260
+
261
+ const workspaceProvider = getRuntimeAdapters().workspaceProvider
262
+ const getWorkspace = workspaceProvider?.getWorkspace
263
+ ? workspaceProvider.getWorkspace.bind(workspaceProvider)
264
+ : undefined
265
+ const getLifecycleState = workspaceProvider?.getLifecycleState
266
+ ? workspaceProvider.getLifecycleState.bind(workspaceProvider)
267
+ : undefined
268
+ const readProfileProjectionState = workspaceProvider?.readProfileProjectionState
269
+ ? workspaceProvider.readProfileProjectionState.bind(workspaceProvider)
270
+ : undefined
271
+ const listRecentDomainEvents = workspaceProvider?.listRecentDomainEvents
272
+ ? workspaceProvider.listRecentDomainEvents.bind(workspaceProvider)
273
+ : undefined
274
+ const buildPromptSummary = workspaceProvider?.buildPromptSummary
275
+ ? workspaceProvider.buildPromptSummary.bind(workspaceProvider)
276
+ : undefined
277
+ const buildRetrievedKnowledgeSection = workspaceProvider?.buildRetrievedKnowledgeSection
278
+ ? workspaceProvider.buildRetrievedKnowledgeSection.bind(workspaceProvider)
279
+ : undefined
280
+ const getConsultParticipants = socialChatConfig.getConsultParticipants
281
+
282
+ const workspace = getWorkspace
283
+ ? yield* effectTryMaybeAsync(() => getWorkspace(resolvedContext.workspaceId))
284
+ : {}
285
+ const lifecycleState = getLifecycleState
286
+ ? yield* effectTryMaybeAsync(() => getLifecycleState(workspace))
287
+ : undefined
288
+ const workspaceProfileState = readProfileProjectionState
289
+ ? yield* effectTryMaybeAsync(() => readProfileProjectionState(workspace))
290
+ : undefined
291
+ const recentDomainEvents = listRecentDomainEvents
292
+ ? yield* effectTryMaybeAsync(() => listRecentDomainEvents(resolvedContext.workspaceId, 5))
293
+ : ([] as Array<Record<string, unknown>>)
294
+ const promptSummary = buildPromptSummary
295
+ ? yield* effectTryMaybeAsync(() => buildPromptSummary(resolvedContext.workspaceId)).pipe(
296
+ Effect.orElseSucceed(() => undefined),
297
+ )
298
+ : undefined
299
+
300
+ const promptContext = buildAgentPromptContext({
301
+ workspaceName:
302
+ workspaceProfileState?.workspaceName ??
303
+ toOptionalTrimmedString((workspace as { name?: unknown }).name) ??
304
+ undefined,
305
+ summaryBlock: workspaceProfileState?.summaryBlock,
306
+ promptSummary,
307
+ userName: messageContext.authorName,
308
+ recentDomainEvents,
309
+ })
310
+ const retrievedKnowledgeSection =
311
+ lifecycleState?.bootstrapActive || messageContext.text.length === 0
312
+ ? undefined
313
+ : buildRetrievedKnowledgeSection
314
+ ? yield* effectTryMaybeAsync(() =>
315
+ buildRetrievedKnowledgeSection({
316
+ workspaceId: workspaceIdString,
317
+ userId: userIdString,
318
+ query: messageContext.text,
319
+ }),
320
+ )
321
+ : undefined
322
+
323
+ const preSeededMemoriesSection = yield* memoryService
324
+ .getTopMemories({ orgId: workspaceIdString, agentName: socialAgentId, limit: PRESEEDED_MEMORY_LOOKUP_LIMIT })
325
+ .pipe(
326
+ Effect.mapError(
327
+ (cause) =>
328
+ new SocialChatServiceError({
329
+ message: `Failed to load pre-seeded memories for ${socialAgentId}.`,
330
+ cause,
331
+ }),
332
+ ),
333
+ )
334
+ const learnedSkillsSection = lifecycleState?.bootstrapActive
335
+ ? undefined
336
+ : yield* learnedSkillService
337
+ .retrieveForTurn({
338
+ orgId: workspaceIdString,
339
+ agentId: socialAgentId,
340
+ query: messageContext.text,
341
+ limit: 3,
342
+ minConfidence: 0.6,
343
+ })
344
+ .pipe(Effect.orElseSucceed(() => undefined))
345
+
346
+ let memoryBlock = ''
347
+ const consultedAgents = getConsultParticipants
348
+ ? yield* effectTryMaybeAsync(() =>
349
+ getConsultParticipants({
350
+ workspaceId: resolvedContext.workspaceId,
351
+ workspaceIdString,
352
+ platform: 'slack',
353
+ }),
354
+ )
355
+ : [...getTeamConsultParticipants()]
356
+ const consultParticipants = [...new Set(consultedAgents)].filter((agentId) => agentId !== socialAgentId)
357
+ const executedToolNames: string[] = []
358
+
359
+ const baseTools = withLoggedSocialToolSet(
360
+ yield* effectTryMaybeAsync(() =>
361
+ socialChatConfig.buildAgentTools(
362
+ buildBuildToolsParams({
363
+ agentId: socialAgentId,
364
+ context: resolvedContext,
365
+ workspaceIdString,
366
+ userIdString,
367
+ messageContext,
368
+ memoryBlock,
369
+ onAppendMemoryBlock: (value: string) => {
370
+ memoryBlock = value
371
+ },
372
+ }),
373
+ ),
374
+ ),
375
+ {
376
+ agentId: socialAgentId,
377
+ channelId: messageContext.channelId,
378
+ threadId: messageContext.threadId,
379
+ executedToolNames,
380
+ },
381
+ )
382
+
383
+ const transcript = buildSocialChatThreadTranscript(historyBeforeReply)
384
+ const runAbort = yield* Effect.acquireRelease(
385
+ Effect.sync(() => createServerRunAbortController()),
386
+ (controller) => Effect.sync(() => controller.dispose()),
387
+ )
388
+
389
+ const consultSpecialistTool = createTool({
390
+ description: 'Consult one specialist teammate for targeted guidance before replying to the user.',
391
+ inputSchema: ConsultSpecialistArgsSchema,
392
+ execute: ({ agentId, task }: ConsultSpecialistArgs) =>
393
+ runPromiseWithCurrentContext(
394
+ Effect.gen(function* () {
395
+ if (!consultParticipants.includes(agentId)) {
396
+ return yield* new ForbiddenError({
397
+ message: `Agent "${agentId}" is not an allowed social-chat specialist.`,
398
+ })
399
+ }
400
+
401
+ const { result: specialistRun } = yield* effectTryMaybeAsync(() =>
402
+ runSpecialistSession({
403
+ initialMemoryBlock: '',
404
+ buildTools: ({ memoryBlock: currentMemoryBlock, onAppendMemoryBlock }) =>
405
+ runPromiseWithCurrentContext(
406
+ Effect.gen(function* () {
407
+ const tools = yield* effectTryMaybeAsync(() =>
408
+ socialChatConfig.buildAgentTools(
409
+ buildBuildToolsParams({
410
+ agentId,
411
+ context: resolvedContext,
412
+ workspaceIdString,
413
+ userIdString,
414
+ messageContext,
415
+ memoryBlock: currentMemoryBlock,
416
+ onAppendMemoryBlock,
417
+ }),
418
+ ),
419
+ )
420
+
421
+ return withLoggedSocialToolSet(tools, {
422
+ agentId,
423
+ channelId: messageContext.channelId,
424
+ threadId: messageContext.threadId,
425
+ executedToolNames,
426
+ })
427
+ }),
428
+ ),
429
+ run: ({ tools }) =>
430
+ runPromiseWithCurrentContext(
431
+ Effect.gen(function* () {
432
+ const specialistPreSeededMemories = yield* memoryService
433
+ .getTopMemories({
434
+ orgId: workspaceIdString,
435
+ agentName: agentId,
436
+ limit: PRESEEDED_MEMORY_LOOKUP_LIMIT,
437
+ })
438
+ .pipe(
439
+ Effect.mapError(
440
+ (cause) =>
441
+ new SocialChatServiceError({
442
+ message: `Failed to load pre-seeded memories for specialist ${agentId}.`,
443
+ cause,
444
+ }),
445
+ ),
446
+ )
447
+ const specialistLearnedSkills = lifecycleState?.bootstrapActive
448
+ ? undefined
449
+ : yield* learnedSkillService
450
+ .retrieveForTurn({
451
+ orgId: workspaceIdString,
452
+ agentId,
453
+ query: task,
454
+ limit: 3,
455
+ minConfidence: 0.6,
456
+ })
457
+ .pipe(Effect.orElseSucceed(() => undefined))
458
+
459
+ return yield* effectTryPromise(() =>
460
+ runSocialAgentTurn({
461
+ agentId,
462
+ mode: 'fixedThreadMode',
463
+ threadType: 'group',
464
+ onboardingActive: lifecycleState?.bootstrapActive ?? false,
465
+ linearInstalled: false,
466
+ systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
467
+ preSeededMemoriesSection: specialistPreSeededMemories,
468
+ retrievedKnowledgeSection,
469
+ learnedSkillsSection: specialistLearnedSkills,
470
+ userMessageText: task,
471
+ additionalInstructionSections: [
472
+ `You are supporting ${socialAgentDisplayName} in a Slack social-chat thread. Stay within your role.`,
473
+ ],
474
+ tools,
475
+ prompt: buildSpecialistSocialChatPrompt({
476
+ requesterName: socialAgentDisplayName,
477
+ agentName: getAgentDisplayName(agentId),
478
+ task,
479
+ transcript,
480
+ }),
481
+ abortSignal: runAbort.signal,
482
+ }),
483
+ )
484
+ }),
485
+ ),
486
+ }),
487
+ )
488
+ const text = specialistRun.text
489
+ const createdAt = yield* Clock.currentTimeMillis
490
+
491
+ return createAssistantMessage({ agentId, agentName: getAgentDisplayName(agentId), text, createdAt })
492
+ }),
493
+ ),
494
+ toModelOutput: ({ output }) => {
495
+ const message = output
496
+ const agentName =
497
+ typeof message.metadata?.agentName === 'string' && message.metadata.agentName.trim().length > 0
498
+ ? message.metadata.agentName.trim()
499
+ : 'Specialist'
500
+ const summary = extractMessageText(message).trim()
501
+ return {
502
+ type: 'text',
503
+ value: summary ? `${agentName}: ${summary}` : `${agentName} completed the requested task.`,
504
+ }
505
+ },
506
+ })
507
+
508
+ yield* effectTryPromise(() => thread.startTyping('Thinking...')).pipe(Effect.orElseSucceed(() => undefined))
509
+ aiLogger.debug`Slack social-chat generating reply: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}`
510
+ const leadRun = yield* effectTryPromise(() =>
511
+ runSocialAgentTurn({
512
+ agentId: socialAgentId,
513
+ mode: 'threadMode',
514
+ threadType: 'group',
515
+ onboardingActive: lifecycleState?.bootstrapActive ?? false,
516
+ linearInstalled: false,
517
+ systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
518
+ preSeededMemoriesSection,
519
+ retrievedKnowledgeSection,
520
+ learnedSkillsSection,
521
+ userMessageText: messageContext.text,
522
+ additionalInstructionSections: [buildSocialChatIdentitySection(socialAgentDisplayName)],
523
+ tools: { ...baseTools, [CONSULT_SPECIALIST_TOOL_NAME]: consultSpecialistTool },
524
+ prompt: buildLeadSocialChatPrompt({
525
+ agentDisplayName: socialAgentDisplayName,
526
+ channelId: messageContext.channelId,
527
+ threadId: messageContext.threadId,
528
+ transcript,
529
+ latestUserMessage: messageContext.text,
530
+ latestAuthorName: messageContext.authorName,
531
+ }),
532
+ abortSignal: runAbort.signal,
533
+ }),
534
+ )
535
+ const responseText = leadRun.text
536
+
537
+ const replyMarkdown = buildSlackSocialReplyMarkdown({ replyMarkdown: responseText, executedToolNames })
538
+ const sentMessage = yield* effectTryPromise(() => thread.post({ markdown: replyMarkdown }))
539
+ aiLogger.debug`Slack social-chat reply posted: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}, replyMessageId=${sentMessage.id}`
540
+ const normalizedResponse = normalizeSocialHistoryMessage({
541
+ workspaceId: workspaceIdString,
542
+ channelId: messageContext.channelId,
543
+ agentId: socialAgentId,
544
+ agentDisplayName: socialAgentDisplayName,
545
+ message: sentMessage,
546
+ textOverride: responseText,
547
+ })
548
+ if (normalizedResponse) {
549
+ yield* socialChatHistoryService.upsertMessages([normalizedResponse])
550
+ }
551
+
552
+ const priorHistoryMessages = toHistoryMessages(priorHistory)
553
+ const agentMessages = normalizedResponse ? buildAgentHistoryMessages([normalizedResponse]) : []
554
+ if (messageContext.text && agentMessages.length > 0) {
555
+ yield* effectTryPromise(() =>
556
+ safeEnqueue(
557
+ () =>
558
+ enqueuePostChatMemory(
559
+ {
560
+ orgId: workspaceIdString,
561
+ threadId: `social:slack:${messageContext.threadId}`,
562
+ sourceId: createSocialChatCursorId({
563
+ workspaceId: workspaceIdString,
564
+ threadId: messageContext.threadId,
565
+ messageId: messageContext.messageId,
566
+ }),
567
+ userMessage: messageContext.text,
568
+ historyMessages: priorHistoryMessages,
569
+ agentMessages,
570
+ memoryBlock: memoryBlock.trim() ? memoryBlock : undefined,
571
+ source: 'social_chat',
572
+ sourceMetadata: {
573
+ platform: 'slack',
574
+ channelId: messageContext.channelId,
575
+ threadId: messageContext.threadId,
576
+ messageId: messageContext.messageId,
577
+ authorId: messageContext.authorId,
578
+ authorName: messageContext.authorName,
579
+ },
580
+ },
581
+ {
582
+ dedupeKey: createSocialMemoryDedupeKey({
583
+ workspaceId: workspaceIdString,
584
+ threadId: messageContext.threadId,
585
+ messageId: messageContext.messageId,
586
+ }),
587
+ },
588
+ ),
589
+ { operationName: 'social post-chat memory extraction enqueue' },
590
+ ),
591
+ )
592
+ }
593
+
594
+ yield* effectTryPromise(() =>
595
+ safeEnqueue(() => enqueueRegularChatMemoryDigest({ orgId: workspaceIdString }), {
596
+ operationName: 'social regular chat memory digest enqueue',
597
+ }),
598
+ )
599
+ yield* effectTryPromise(() =>
600
+ safeEnqueue(() => enqueueSkillExtraction({ orgId: workspaceIdString }), {
601
+ operationName: 'social skill extraction enqueue',
602
+ }),
603
+ )
604
+ }),
605
+ )
606
+
607
+ chat.onNewMention((thread, message) => {
608
+ aiLogger.debug`Slack social-chat new mention received: threadId=${thread.id}, messageId=${message.id}`
609
+ return Effect.runPromise(
610
+ Effect.gen(function* () {
611
+ yield* effectTryPromise(() => thread.subscribe())
612
+ return yield* handleMessage(thread, message)
613
+ }),
614
+ )
615
+ })
616
+ chat.onSubscribedMessage((thread, message) => {
617
+ aiLogger.debug`Slack social-chat subscribed thread message received: threadId=${thread.id}, messageId=${message.id}`
618
+ return Effect.runPromise(handleMessage(thread, message))
619
+ })
620
+
621
+ return {
622
+ enabled: true,
623
+ initialize,
624
+ shutdown,
625
+ webhooks: {
626
+ slack: (request: Request, options?: WebhookOptions) =>
627
+ Effect.runPromise(effectTryPromise(() => chat.webhooks.slack(request, options))),
628
+ },
629
+ }
630
+ }
@@ -1,18 +1,44 @@
1
1
  import type { ToolSet } from 'ai'
2
+ import { Schema, Effect } from 'effect'
2
3
 
3
- export async function runSpecialistSession<TResult>(params: {
4
+ class SpecialistRunnerError extends Schema.TaggedErrorClass<SpecialistRunnerError>()('SpecialistRunnerError', {
5
+ message: Schema.String,
6
+ cause: Schema.optional(Schema.Defect),
7
+ }) {}
8
+
9
+ function runSpecialistSessionEffect<TResult>(params: {
4
10
  initialMemoryBlock: string
5
11
  buildTools: (options: { memoryBlock: string; onAppendMemoryBlock: (value: string) => void }) => Promise<ToolSet>
6
12
  run: (options: { tools: ToolSet; memoryBlock: string }) => Promise<TResult>
7
- }): Promise<{ result: TResult; memoryBlock: string }> {
8
- let specialistMemoryBlock = params.initialMemoryBlock
9
- const tools = await params.buildTools({
10
- memoryBlock: specialistMemoryBlock,
11
- onAppendMemoryBlock: (value) => {
12
- specialistMemoryBlock = value
13
- },
13
+ }): Effect.Effect<{ result: TResult; memoryBlock: string }, SpecialistRunnerError> {
14
+ return Effect.gen(function* () {
15
+ let specialistMemoryBlock = params.initialMemoryBlock
16
+ const tools = yield* Effect.tryPromise({
17
+ try: () =>
18
+ params.buildTools({
19
+ memoryBlock: specialistMemoryBlock,
20
+ onAppendMemoryBlock: (value) => {
21
+ specialistMemoryBlock = value
22
+ },
23
+ }),
24
+ catch: (error: unknown) =>
25
+ new SpecialistRunnerError({ message: 'Failed to build specialist tools', cause: error }),
26
+ })
27
+
28
+ const result = yield* Effect.tryPromise({
29
+ try: () => params.run({ tools, memoryBlock: specialistMemoryBlock }),
30
+ catch: (error: unknown) =>
31
+ new SpecialistRunnerError({ message: 'Failed to run specialist session', cause: error }),
32
+ })
33
+
34
+ return { result, memoryBlock: specialistMemoryBlock }
14
35
  })
36
+ }
15
37
 
16
- const result = await params.run({ tools, memoryBlock: specialistMemoryBlock })
17
- return { result, memoryBlock: specialistMemoryBlock }
38
+ export function runSpecialistSession<TResult>(params: {
39
+ initialMemoryBlock: string
40
+ buildTools: (options: { memoryBlock: string; onAppendMemoryBlock: (value: string) => void }) => Promise<ToolSet>
41
+ run: (options: { tools: ToolSet; memoryBlock: string }) => Promise<TResult>
42
+ }): Promise<{ result: TResult; memoryBlock: string }> {
43
+ return Effect.runPromise(runSpecialistSessionEffect(params))
18
44
  }