@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
@@ -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
  }