@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
@@ -0,0 +1,594 @@
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 } from 'effect'
14
+ import { Cause, Clock, Effect } 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 { 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
+ const DEFAULT_SOCIAL_CHAT_AGENT_ID = 'socialChat'
64
+ const DEFAULT_SOCIAL_CHAT_AGENT_DISPLAY_NAME = 'Lota'
65
+ const DEFAULT_SOCIAL_CHAT_STATE_PREFIX = 'lota:social:chat-sdk'
66
+ const DEFAULT_SOCIAL_CHAT_DEDUPE_TTL_MS = 15 * 60 * 1000
67
+ const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
68
+
69
+ export interface LotaRuntimeSocialChat {
70
+ enabled: boolean
71
+ initialize(): Promise<void>
72
+ shutdown(): Promise<void>
73
+ webhooks: { slack(request: Request, options?: WebhookOptions): Promise<Response> }
74
+ }
75
+
76
+ type SocialChatStateRedisClient = IoRedisStateClientOptions['client']
77
+
78
+ interface SlackSocialMessageContext {
79
+ channelId: string
80
+ threadId: string
81
+ messageId: string
82
+ text: string
83
+ authorId?: string
84
+ authorName?: string
85
+ }
86
+
87
+ function createDisabledSocialChatRuntime(): LotaRuntimeSocialChat {
88
+ return {
89
+ enabled: false,
90
+ initialize: () => Promise.resolve(),
91
+ shutdown: () => Promise.resolve(),
92
+ webhooks: { slack: () => Promise.resolve(new Response('Social chat is disabled.', { status: 404 })) },
93
+ }
94
+ }
95
+
96
+ function createAssistantMessage(params: {
97
+ agentId: string
98
+ agentName: string
99
+ text: string
100
+ createdAt: number
101
+ }): ChatMessage {
102
+ return {
103
+ id: Bun.randomUUIDv7(),
104
+ role: 'assistant',
105
+ parts: [{ type: 'text', text: params.text }],
106
+ metadata: { agentId: params.agentId, agentName: params.agentName, createdAt: params.createdAt },
107
+ }
108
+ }
109
+
110
+ function getAgentDisplayName(agentId: string): string {
111
+ return getAgentDisplayNames()[agentId] ?? agentId
112
+ }
113
+
114
+ function toSafeJobIdSegment(value: string): string {
115
+ return value.replace(/[^a-zA-Z0-9_-]/g, '_')
116
+ }
117
+
118
+ function createSocialMemoryDedupeKey(params: { workspaceId: string; threadId: string; messageId: string }): string {
119
+ return [
120
+ 'social-memory',
121
+ toSafeJobIdSegment(params.workspaceId),
122
+ toSafeJobIdSegment(params.threadId),
123
+ toSafeJobIdSegment(params.messageId),
124
+ ].join('-')
125
+ }
126
+
127
+ function buildBuildToolsParams(params: {
128
+ agentId: string
129
+ context: LotaSocialChatResolvedContext
130
+ workspaceIdString: string
131
+ userIdString: string
132
+ messageContext: SlackSocialMessageContext
133
+ memoryBlock: string
134
+ onAppendMemoryBlock: (value: string) => void
135
+ }): BuildSocialChatAgentToolsParams {
136
+ return {
137
+ agentId: params.agentId,
138
+ workspaceId: params.context.workspaceId,
139
+ workspaceIdString: params.workspaceIdString,
140
+ userId: params.context.userId,
141
+ userIdString: params.userIdString,
142
+ userName: params.context.userName,
143
+ platform: 'slack',
144
+ channelId: params.messageContext.channelId,
145
+ threadId: params.messageContext.threadId,
146
+ incomingMessageId: params.messageContext.messageId,
147
+ incomingText: params.messageContext.text,
148
+ memoryBlock: params.memoryBlock,
149
+ onAppendMemoryBlock: params.onAppendMemoryBlock,
150
+ context: null,
151
+ }
152
+ }
153
+
154
+ export function createSocialChatRuntime(params: {
155
+ redisClient: SocialChatStateRedisClient
156
+ socialChat?: LotaRuntimeSocialChatConfig
157
+ services: SocialChatRuntimeServices
158
+ }): LotaRuntimeSocialChat {
159
+ const socialChatConfig = params.socialChat
160
+ const slackConfig = socialChatConfig?.slack
161
+ const slackEnabled = Boolean(slackConfig?.botToken?.trim() && slackConfig.signingSecret?.trim())
162
+
163
+ if (!socialChatConfig || !slackEnabled) {
164
+ return createDisabledSocialChatRuntime()
165
+ }
166
+
167
+ const socialAgentId = toOptionalTrimmedString(socialChatConfig.agentId) ?? DEFAULT_SOCIAL_CHAT_AGENT_ID
168
+ const socialAgentDisplayName =
169
+ toOptionalTrimmedString(socialChatConfig.agentDisplayName) ?? DEFAULT_SOCIAL_CHAT_AGENT_DISPLAY_NAME
170
+ const stateKeyPrefix =
171
+ toOptionalTrimmedString(socialChatConfig.stateRedisKeyPrefix) ?? DEFAULT_SOCIAL_CHAT_STATE_PREFIX
172
+ const chat = new Chat({
173
+ userName: toOptionalTrimmedString(slackConfig?.userName) ?? socialAgentDisplayName,
174
+ adapters: {
175
+ slack: createSlackAdapter({
176
+ botToken: slackConfig?.botToken,
177
+ signingSecret: slackConfig?.signingSecret,
178
+ userName: toOptionalTrimmedString(slackConfig?.userName) ?? socialAgentDisplayName,
179
+ }),
180
+ },
181
+ state: createIoRedisState({
182
+ client: params.redisClient,
183
+ keyPrefix: stateKeyPrefix,
184
+ logger: new ConsoleLogger('warn', 'lota-social-chat'),
185
+ }),
186
+ dedupeTtlMs: slackConfig?.dedupeTtlMs ?? DEFAULT_SOCIAL_CHAT_DEDUPE_TTL_MS,
187
+ })
188
+
189
+ const initialize = () => Effect.runPromise(effectTryPromise(() => chat.initialize()))
190
+ const shutdown = () => Effect.runPromise(effectTryPromise(() => chat.shutdown()))
191
+
192
+ const handleMessage = (
193
+ thread: Thread,
194
+ incomingMessage: Message,
195
+ ): Effect.Effect<void, Cause.UnknownError | ForbiddenError, never> =>
196
+ Effect.gen(function* () {
197
+ const { memoryService, learnedSkillService, socialChatHistoryService } = params.services
198
+ const currentContext = yield* Effect.context()
199
+ const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
200
+ const rawSlackMessage = incomingMessage.raw as { channel?: unknown } | undefined
201
+ const channelId = toOptionalTrimmedString(rawSlackMessage?.channel) ?? thread.channelId
202
+ const messageContext: SlackSocialMessageContext = {
203
+ channelId,
204
+ threadId: thread.id,
205
+ messageId: incomingMessage.id,
206
+ text: incomingMessage.text.trim(),
207
+ authorId: toOptionalTrimmedString(incomingMessage.author.userId) ?? undefined,
208
+ authorName: readSlackAuthorName(incomingMessage),
209
+ }
210
+ aiLogger.info`Slack social-chat message received: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}, messageId=${messageContext.messageId}, author=${messageContext.authorName ?? 'unknown'}, textLength=${messageContext.text.length}`
211
+
212
+ const resolvedContext = yield* effectTryMaybeAsync(() =>
213
+ socialChatConfig.resolveContext({
214
+ platform: 'slack',
215
+ channelId: messageContext.channelId,
216
+ threadId: messageContext.threadId,
217
+ messageId: messageContext.messageId,
218
+ text: messageContext.text,
219
+ authorId: messageContext.authorId,
220
+ authorName: messageContext.authorName,
221
+ }),
222
+ )
223
+ const workspaceIdString = recordIdToString(resolvedContext.workspaceId, TABLES.ORGANIZATION)
224
+ const userIdString = recordIdToString(resolvedContext.userId, TABLES.USER)
225
+ aiLogger.info`Slack social-chat context resolved: workspaceId=${workspaceIdString}, userId=${userIdString}`
226
+
227
+ const threadMessages = yield* effectTryPromise(() => collectThreadMessages(thread, incomingMessage))
228
+ const normalizedMessages = threadMessages
229
+ .map((message) =>
230
+ normalizeSocialHistoryMessage({
231
+ workspaceId: workspaceIdString,
232
+ channelId: messageContext.channelId,
233
+ agentId: socialAgentId,
234
+ agentDisplayName: socialAgentDisplayName,
235
+ message,
236
+ }),
237
+ )
238
+ .filter((message): message is NonNullable<typeof message> => message !== null)
239
+ yield* socialChatHistoryService.upsertMessages(normalizedMessages)
240
+
241
+ const historyBeforeReply = yield* socialChatHistoryService.listThreadMessages({
242
+ workspaceId: workspaceIdString,
243
+ threadId: messageContext.threadId,
244
+ })
245
+ const currentUserMessage =
246
+ historyBeforeReply.find((message) => message.messageId === incomingMessage.id) ?? historyBeforeReply.at(-1)
247
+ const priorHistory = currentUserMessage
248
+ ? historyBeforeReply.filter((message) => message.cursor.id !== currentUserMessage.cursor.id)
249
+ : historyBeforeReply
250
+
251
+ const workspaceProvider = getRuntimeAdapters().workspaceProvider
252
+ const getWorkspace = workspaceProvider?.getWorkspace
253
+ ? workspaceProvider.getWorkspace.bind(workspaceProvider)
254
+ : undefined
255
+ const getLifecycleState = workspaceProvider?.getLifecycleState
256
+ ? workspaceProvider.getLifecycleState.bind(workspaceProvider)
257
+ : undefined
258
+ const readProfileProjectionState = workspaceProvider?.readProfileProjectionState
259
+ ? workspaceProvider.readProfileProjectionState.bind(workspaceProvider)
260
+ : undefined
261
+ const listRecentDomainEvents = workspaceProvider?.listRecentDomainEvents
262
+ ? workspaceProvider.listRecentDomainEvents.bind(workspaceProvider)
263
+ : undefined
264
+ const buildPromptSummary = workspaceProvider?.buildPromptSummary
265
+ ? workspaceProvider.buildPromptSummary.bind(workspaceProvider)
266
+ : undefined
267
+ const buildRetrievedKnowledgeSection = workspaceProvider?.buildRetrievedKnowledgeSection
268
+ ? workspaceProvider.buildRetrievedKnowledgeSection.bind(workspaceProvider)
269
+ : undefined
270
+ const getConsultParticipants = socialChatConfig.getConsultParticipants
271
+
272
+ const workspace = getWorkspace ? yield* effectTryMaybeAsync(() => getWorkspace(resolvedContext.workspaceId)) : {}
273
+ const lifecycleState = getLifecycleState
274
+ ? yield* effectTryMaybeAsync(() => getLifecycleState(workspace))
275
+ : undefined
276
+ const workspaceProfileState = readProfileProjectionState
277
+ ? yield* effectTryMaybeAsync(() => readProfileProjectionState(workspace))
278
+ : undefined
279
+ const recentDomainEvents = listRecentDomainEvents
280
+ ? yield* effectTryMaybeAsync(() => listRecentDomainEvents(resolvedContext.workspaceId, 5))
281
+ : ([] as Array<Record<string, unknown>>)
282
+ const promptSummary = buildPromptSummary
283
+ ? yield* effectTryMaybeAsync(() => buildPromptSummary(resolvedContext.workspaceId)).pipe(
284
+ Effect.orElseSucceed(() => undefined),
285
+ )
286
+ : undefined
287
+
288
+ const promptContext = buildAgentPromptContext({
289
+ workspaceName:
290
+ workspaceProfileState?.workspaceName ??
291
+ toOptionalTrimmedString((workspace as { name?: unknown }).name) ??
292
+ undefined,
293
+ summaryBlock: workspaceProfileState?.summaryBlock,
294
+ promptSummary,
295
+ userName: messageContext.authorName,
296
+ recentDomainEvents,
297
+ })
298
+ const retrievedKnowledgeSection =
299
+ lifecycleState?.bootstrapActive || messageContext.text.length === 0
300
+ ? undefined
301
+ : buildRetrievedKnowledgeSection
302
+ ? yield* effectTryMaybeAsync(() =>
303
+ buildRetrievedKnowledgeSection({
304
+ workspaceId: workspaceIdString,
305
+ userId: userIdString,
306
+ query: messageContext.text,
307
+ }),
308
+ )
309
+ : undefined
310
+
311
+ const preSeededMemoriesSection = yield* memoryService
312
+ .getTopMemories({ orgId: workspaceIdString, agentName: socialAgentId, limit: PRESEEDED_MEMORY_LOOKUP_LIMIT })
313
+ .pipe(Effect.mapError((cause) => new Cause.UnknownError(cause)))
314
+ const learnedSkillsSection = lifecycleState?.bootstrapActive
315
+ ? undefined
316
+ : yield* learnedSkillService
317
+ .retrieveForTurn({
318
+ orgId: workspaceIdString,
319
+ agentId: socialAgentId,
320
+ query: messageContext.text,
321
+ limit: 3,
322
+ minConfidence: 0.6,
323
+ })
324
+ .pipe(Effect.orElseSucceed(() => undefined))
325
+
326
+ let memoryBlock = ''
327
+ const consultedAgents = getConsultParticipants
328
+ ? yield* effectTryMaybeAsync(() =>
329
+ getConsultParticipants({ workspaceId: resolvedContext.workspaceId, workspaceIdString, platform: 'slack' }),
330
+ )
331
+ : [...getTeamConsultParticipants()]
332
+ const consultParticipants = [...new Set(consultedAgents)].filter((agentId) => agentId !== socialAgentId)
333
+ const executedToolNames: string[] = []
334
+
335
+ const baseTools = withLoggedSocialToolSet(
336
+ yield* effectTryMaybeAsync(() =>
337
+ socialChatConfig.buildAgentTools(
338
+ buildBuildToolsParams({
339
+ agentId: socialAgentId,
340
+ context: resolvedContext,
341
+ workspaceIdString,
342
+ userIdString,
343
+ messageContext,
344
+ memoryBlock,
345
+ onAppendMemoryBlock: (value: string) => {
346
+ memoryBlock = value
347
+ },
348
+ }),
349
+ ),
350
+ ),
351
+ {
352
+ agentId: socialAgentId,
353
+ channelId: messageContext.channelId,
354
+ threadId: messageContext.threadId,
355
+ executedToolNames,
356
+ },
357
+ )
358
+
359
+ const transcript = buildSocialChatThreadTranscript(historyBeforeReply)
360
+ const runAbort = createServerRunAbortController()
361
+
362
+ const consultSpecialistTool = createTool({
363
+ description: 'Consult one specialist teammate for targeted guidance before replying to the user.',
364
+ inputSchema: ConsultSpecialistArgsSchema,
365
+ execute: ({ agentId, task }: ConsultSpecialistArgs) =>
366
+ runPromiseWithCurrentContext(
367
+ Effect.gen(function* () {
368
+ if (!consultParticipants.includes(agentId)) {
369
+ return yield* new ForbiddenError({
370
+ message: `Agent "${agentId}" is not an allowed social-chat specialist.`,
371
+ })
372
+ }
373
+
374
+ const { result: specialistRun } = yield* effectTryMaybeAsync(() =>
375
+ runSpecialistSession({
376
+ initialMemoryBlock: '',
377
+ buildTools: ({ memoryBlock: currentMemoryBlock, onAppendMemoryBlock }) =>
378
+ runPromiseWithCurrentContext(
379
+ Effect.gen(function* () {
380
+ const tools = yield* effectTryMaybeAsync(() =>
381
+ socialChatConfig.buildAgentTools(
382
+ buildBuildToolsParams({
383
+ agentId,
384
+ context: resolvedContext,
385
+ workspaceIdString,
386
+ userIdString,
387
+ messageContext,
388
+ memoryBlock: currentMemoryBlock,
389
+ onAppendMemoryBlock,
390
+ }),
391
+ ),
392
+ )
393
+
394
+ return withLoggedSocialToolSet(tools, {
395
+ agentId,
396
+ channelId: messageContext.channelId,
397
+ threadId: messageContext.threadId,
398
+ executedToolNames,
399
+ })
400
+ }),
401
+ ),
402
+ run: ({ tools }) =>
403
+ runPromiseWithCurrentContext(
404
+ Effect.gen(function* () {
405
+ const specialistPreSeededMemories = yield* memoryService
406
+ .getTopMemories({
407
+ orgId: workspaceIdString,
408
+ agentName: agentId,
409
+ limit: PRESEEDED_MEMORY_LOOKUP_LIMIT,
410
+ })
411
+ .pipe(Effect.mapError((cause) => new Cause.UnknownError(cause)))
412
+ const specialistLearnedSkills = lifecycleState?.bootstrapActive
413
+ ? undefined
414
+ : yield* learnedSkillService
415
+ .retrieveForTurn({
416
+ orgId: workspaceIdString,
417
+ agentId,
418
+ query: task,
419
+ limit: 3,
420
+ minConfidence: 0.6,
421
+ })
422
+ .pipe(Effect.orElseSucceed(() => undefined))
423
+
424
+ return yield* effectTryPromise(() =>
425
+ runSocialAgentTurn({
426
+ agentId,
427
+ mode: 'fixedThreadMode',
428
+ threadType: 'group',
429
+ onboardingActive: lifecycleState?.bootstrapActive ?? false,
430
+ linearInstalled: false,
431
+ systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
432
+ preSeededMemoriesSection: specialistPreSeededMemories,
433
+ retrievedKnowledgeSection,
434
+ learnedSkillsSection: specialistLearnedSkills,
435
+ userMessageText: task,
436
+ additionalInstructionSections: [
437
+ `You are supporting ${socialAgentDisplayName} in a Slack social-chat thread. Stay within your role.`,
438
+ ],
439
+ tools,
440
+ prompt: buildSpecialistSocialChatPrompt({
441
+ requesterName: socialAgentDisplayName,
442
+ agentName: getAgentDisplayName(agentId),
443
+ task,
444
+ transcript,
445
+ }),
446
+ abortSignal: runAbort.signal,
447
+ }),
448
+ )
449
+ }),
450
+ ),
451
+ }),
452
+ )
453
+ const text = specialistRun.text
454
+ const createdAt = yield* Clock.currentTimeMillis
455
+
456
+ return createAssistantMessage({ agentId, agentName: getAgentDisplayName(agentId), text, createdAt })
457
+ }),
458
+ ),
459
+ toModelOutput: ({ output }) => {
460
+ const message = output
461
+ const agentName =
462
+ typeof message.metadata?.agentName === 'string' && message.metadata.agentName.trim().length > 0
463
+ ? message.metadata.agentName.trim()
464
+ : 'Specialist'
465
+ const summary = extractMessageText(message).trim()
466
+ return {
467
+ type: 'text',
468
+ value: summary ? `${agentName}: ${summary}` : `${agentName} completed the requested task.`,
469
+ }
470
+ },
471
+ })
472
+
473
+ yield* effectTryPromise(() => thread.startTyping('Thinking...')).pipe(Effect.orElseSucceed(() => undefined))
474
+ aiLogger.info`Slack social-chat generating reply: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}`
475
+ const leadRun = yield* effectTryPromise(() =>
476
+ runSocialAgentTurn({
477
+ agentId: socialAgentId,
478
+ mode: 'threadMode',
479
+ threadType: 'group',
480
+ onboardingActive: lifecycleState?.bootstrapActive ?? false,
481
+ linearInstalled: false,
482
+ systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
483
+ preSeededMemoriesSection,
484
+ retrievedKnowledgeSection,
485
+ learnedSkillsSection,
486
+ userMessageText: messageContext.text,
487
+ additionalInstructionSections: [buildSocialChatIdentitySection(socialAgentDisplayName)],
488
+ tools: { ...baseTools, [CONSULT_SPECIALIST_TOOL_NAME]: consultSpecialistTool },
489
+ prompt: buildLeadSocialChatPrompt({
490
+ agentDisplayName: socialAgentDisplayName,
491
+ channelId: messageContext.channelId,
492
+ threadId: messageContext.threadId,
493
+ transcript,
494
+ latestUserMessage: messageContext.text,
495
+ latestAuthorName: messageContext.authorName,
496
+ }),
497
+ abortSignal: runAbort.signal,
498
+ }),
499
+ )
500
+ const responseText = leadRun.text
501
+
502
+ const replyMarkdown = buildSlackSocialReplyMarkdown({ replyMarkdown: responseText, executedToolNames })
503
+ const sentMessage = yield* effectTryPromise(() => thread.post({ markdown: replyMarkdown }))
504
+ aiLogger.info`Slack social-chat reply posted: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}, replyMessageId=${sentMessage.id}`
505
+ const normalizedResponse = normalizeSocialHistoryMessage({
506
+ workspaceId: workspaceIdString,
507
+ channelId: messageContext.channelId,
508
+ agentId: socialAgentId,
509
+ agentDisplayName: socialAgentDisplayName,
510
+ message: sentMessage,
511
+ textOverride: responseText,
512
+ })
513
+ if (normalizedResponse) {
514
+ yield* socialChatHistoryService.upsertMessages([normalizedResponse])
515
+ }
516
+
517
+ const priorHistoryMessages = toHistoryMessages(priorHistory)
518
+ const agentMessages = normalizedResponse ? buildAgentHistoryMessages([normalizedResponse]) : []
519
+ if (messageContext.text && agentMessages.length > 0) {
520
+ yield* effectTryPromise(() =>
521
+ safeEnqueue(
522
+ () =>
523
+ enqueuePostChatMemory(
524
+ {
525
+ orgId: workspaceIdString,
526
+ threadId: `social:slack:${messageContext.threadId}`,
527
+ sourceId: createSocialChatCursorId({
528
+ workspaceId: workspaceIdString,
529
+ threadId: messageContext.threadId,
530
+ messageId: messageContext.messageId,
531
+ }),
532
+ userMessage: messageContext.text,
533
+ historyMessages: priorHistoryMessages,
534
+ agentMessages,
535
+ memoryBlock: memoryBlock.trim() ? memoryBlock : undefined,
536
+ source: 'social_chat',
537
+ sourceMetadata: {
538
+ platform: 'slack',
539
+ channelId: messageContext.channelId,
540
+ threadId: messageContext.threadId,
541
+ messageId: messageContext.messageId,
542
+ authorId: messageContext.authorId,
543
+ authorName: messageContext.authorName,
544
+ },
545
+ },
546
+ {
547
+ dedupeKey: createSocialMemoryDedupeKey({
548
+ workspaceId: workspaceIdString,
549
+ threadId: messageContext.threadId,
550
+ messageId: messageContext.messageId,
551
+ }),
552
+ },
553
+ ),
554
+ { operationName: 'social post-chat memory extraction enqueue' },
555
+ ),
556
+ )
557
+ }
558
+
559
+ yield* effectTryPromise(() =>
560
+ safeEnqueue(() => enqueueRegularChatMemoryDigest({ orgId: workspaceIdString }), {
561
+ operationName: 'social regular chat memory digest enqueue',
562
+ }),
563
+ )
564
+ yield* effectTryPromise(() =>
565
+ safeEnqueue(() => enqueueSkillExtraction({ orgId: workspaceIdString }), {
566
+ operationName: 'social skill extraction enqueue',
567
+ }),
568
+ )
569
+ })
570
+
571
+ chat.onNewMention((thread, message) => {
572
+ aiLogger.info`Slack social-chat new mention received: threadId=${thread.id}, messageId=${message.id}`
573
+ return Effect.runPromise(
574
+ Effect.gen(function* () {
575
+ yield* effectTryPromise(() => thread.subscribe())
576
+ return yield* handleMessage(thread, message)
577
+ }),
578
+ )
579
+ })
580
+ chat.onSubscribedMessage((thread, message) => {
581
+ aiLogger.info`Slack social-chat subscribed thread message received: threadId=${thread.id}, messageId=${message.id}`
582
+ return Effect.runPromise(handleMessage(thread, message))
583
+ })
584
+
585
+ return {
586
+ enabled: true,
587
+ initialize,
588
+ shutdown,
589
+ webhooks: {
590
+ slack: (request: Request, options?: WebhookOptions) =>
591
+ Effect.runPromise(effectTryPromise(() => chat.webhooks.slack(request, options))),
592
+ },
593
+ }
594
+ }
@@ -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
  }