@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
@@ -1,118 +0,0 @@
1
- import { stepCountIs } from 'ai'
2
- import type { ToolLoopAgent, ToolSet } from 'ai'
3
-
4
- import { createAgent, getAgentRuntimeConfig } from '../config/agent-defaults'
5
- import { aiLogger } from '../config/logger'
6
-
7
- type SocialChatAgent = ToolLoopAgent<never, ToolSet>
8
-
9
- interface ExecutableToolDefinition {
10
- execute: (...args: unknown[]) => Promise<unknown>
11
- }
12
-
13
- const SOCIAL_CHAT_RETRIEVAL_TOOL_NAMES = [
14
- 'memorySearch',
15
- 'conversationSearch',
16
- 'queryKnowledge',
17
- 'researchTopic',
18
- 'searchWeb',
19
- 'fetchWebpage',
20
- 'inspectWebsite',
21
- ] as const
22
-
23
- export function withLoggedSocialToolSet(
24
- tools: ToolSet,
25
- params: { agentId: string; channelId: string; threadId: string; executedToolNames: string[] },
26
- ): ToolSet {
27
- return Object.fromEntries(
28
- Object.entries(tools).map(([toolName, toolDefinition]) => {
29
- if (
30
- typeof toolDefinition !== 'object' ||
31
- !('execute' in toolDefinition) ||
32
- typeof toolDefinition.execute !== 'function'
33
- ) {
34
- return [toolName, toolDefinition]
35
- }
36
-
37
- const executableTool = toolDefinition as typeof toolDefinition & ExecutableToolDefinition
38
- return [
39
- toolName,
40
- {
41
- ...toolDefinition,
42
- execute: async (...args: unknown[]): Promise<unknown> => {
43
- aiLogger.info`Slack social-chat tool start: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
44
- try {
45
- const result: unknown = await executableTool.execute(...args)
46
- params.executedToolNames.push(toolName)
47
- aiLogger.info`Slack social-chat tool finish: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
48
- return result
49
- } catch (error) {
50
- aiLogger.warn`Slack social-chat tool failed: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}, error=${error}`
51
- throw error
52
- }
53
- },
54
- },
55
- ]
56
- }),
57
- )
58
- }
59
-
60
- export async function runSocialAgentTurn(params: {
61
- agentId: string
62
- mode: 'fixedThreadMode' | 'threadMode'
63
- threadType: 'group'
64
- onboardingActive: boolean
65
- linearInstalled: boolean
66
- systemWorkspaceDetails?: string
67
- preSeededMemoriesSection?: string
68
- retrievedKnowledgeSection?: string
69
- learnedSkillsSection?: string
70
- userMessageText?: string
71
- additionalInstructionSections?: string[]
72
- prompt: string
73
- tools: ToolSet
74
- abortSignal: AbortSignal
75
- }): Promise<{ text: string; displayName: string }> {
76
- const runtimeConfig = getAgentRuntimeConfig({
77
- agentId: params.agentId,
78
- threadType: params.threadType,
79
- mode: params.mode,
80
- onboardingActive: params.onboardingActive,
81
- linearInstalled: params.linearInstalled,
82
- systemWorkspaceDetails: params.systemWorkspaceDetails,
83
- preSeededMemoriesSection: params.preSeededMemoriesSection,
84
- retrievedKnowledgeSection: params.retrievedKnowledgeSection,
85
- learnedSkillsSection: params.learnedSkillsSection,
86
- userMessageText: params.userMessageText,
87
- ruleOptions: {
88
- includeMemr3Rule: SOCIAL_CHAT_RETRIEVAL_TOOL_NAMES.some((toolName) => toolName in params.tools),
89
- includeDomainReasoningFallbackRule: params.agentId === 'cpo' || params.agentId === 'mentor',
90
- },
91
- additionalInstructionSections: params.additionalInstructionSections,
92
- }) as Record<string, unknown>
93
-
94
- const agentFactory = createAgent[runtimeConfig.id as string]
95
- if (typeof agentFactory !== 'function') {
96
- throw new Error(`Social chat agent factory is not configured for ${String(runtimeConfig.id)}`)
97
- }
98
-
99
- const agent = agentFactory({
100
- mode: params.mode,
101
- tools: params.tools,
102
- extraInstructions: runtimeConfig.extraInstructions,
103
- stopWhen: [stepCountIs(runtimeConfig.maxSteps as number)],
104
- }) as SocialChatAgent
105
- const response = await agent.generate({ prompt: params.prompt, abortSignal: params.abortSignal })
106
- const text = response.text.trim()
107
- if (!text) {
108
- throw new Error(`Social chat agent ${params.agentId} returned an empty response.`)
109
- }
110
-
111
- return {
112
- text,
113
- displayName:
114
- typeof runtimeConfig.displayName === 'string' && runtimeConfig.displayName.trim()
115
- ? runtimeConfig.displayName.trim()
116
- : params.agentId,
117
- }
118
- }
@@ -1,516 +0,0 @@
1
- import { createSlackAdapter } from '@chat-adapter/slack'
2
- import { createIoRedisState } from '@chat-adapter/state-ioredis'
3
- import {
4
- buildSlackSocialReplyMarkdown,
5
- CONSULT_SPECIALIST_TOOL_NAME,
6
- ConsultSpecialistArgsSchema,
7
- } from '@lota-sdk/shared'
8
- import type { ChatMessage, ConsultSpecialistArgs } from '@lota-sdk/shared'
9
- import { tool as createTool } from 'ai'
10
- import { Chat, ConsoleLogger } from 'chat'
11
- import type { Message, Thread, WebhookOptions } from 'chat'
12
- import type IORedis from 'ioredis'
13
-
14
- import { agentDisplayNames, teamConsultParticipants } from '../config/agent-defaults'
15
- import { aiLogger } from '../config/logger'
16
- import { recordIdToString } from '../db/record-id'
17
- import { TABLES } from '../db/tables'
18
- import { enqueueRegularChatMemoryDigest, enqueueSkillExtraction } from '../queues/organization-learning.queue'
19
- import { enqueuePostChatMemory } from '../queues/post-chat-memory.queue'
20
- import type {
21
- BuildSocialChatAgentToolsParams,
22
- LotaRuntimeSocialChatConfig,
23
- LotaSocialChatResolvedContext,
24
- } from '../runtime/runtime-config'
25
- import { getRuntimeAdapters } from '../runtime/runtime-extensions'
26
- import { learnedSkillService } from '../services/learned-skill.service'
27
- import { memoryService } from '../services/memory.service'
28
- import { socialChatHistoryService } from '../services/social-chat-history.service'
29
- import { safeEnqueue } from '../utils/async'
30
- import { buildAgentPromptContext } from './agent-prompt-context'
31
- import { createServerRunAbortController } from './agent-stream-helpers'
32
- import { runSocialAgentTurn, withLoggedSocialToolSet } from './social-chat-agent-runner'
33
- import {
34
- collectThreadMessages,
35
- createSocialChatCursorId,
36
- normalizeSocialHistoryMessage,
37
- readSlackAuthorName,
38
- buildSocialChatThreadTranscript,
39
- } from './social-chat-history'
40
- import {
41
- buildLeadSocialChatPrompt,
42
- buildSocialChatIdentitySection,
43
- buildSpecialistSocialChatPrompt,
44
- } from './social-chat-prompts'
45
- import { runSpecialistSession } from './specialist-runner'
46
- import {
47
- buildAgentHistoryMessages,
48
- extractMessageText,
49
- toHistoryMessages,
50
- toOptionalTrimmedString,
51
- } from './thread-chat-helpers'
52
-
53
- const DEFAULT_SOCIAL_CHAT_AGENT_ID = 'socialChat'
54
- const DEFAULT_SOCIAL_CHAT_AGENT_DISPLAY_NAME = 'Lota'
55
- const DEFAULT_SOCIAL_CHAT_STATE_PREFIX = 'lota:social:chat-sdk'
56
- const DEFAULT_SOCIAL_CHAT_DEDUPE_TTL_MS = 15 * 60 * 1000
57
- const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
58
-
59
- export interface LotaRuntimeSocialChat {
60
- enabled: boolean
61
- initialize(): Promise<void>
62
- shutdown(): Promise<void>
63
- webhooks: { slack(request: Request, options?: WebhookOptions): Promise<Response> }
64
- }
65
-
66
- interface SlackSocialMessageContext {
67
- channelId: string
68
- threadId: string
69
- messageId: string
70
- text: string
71
- authorId?: string
72
- authorName?: string
73
- }
74
-
75
- function createDisabledSocialChatRuntime(): LotaRuntimeSocialChat {
76
- return {
77
- enabled: false,
78
- async initialize() {},
79
- async shutdown() {},
80
- webhooks: {
81
- async slack() {
82
- return new Response('Social chat is disabled.', { status: 404 })
83
- },
84
- },
85
- }
86
- }
87
-
88
- function createAssistantMessage(params: { agentId: string; agentName: string; text: string }): ChatMessage {
89
- return {
90
- id: Bun.randomUUIDv7(),
91
- role: 'assistant',
92
- parts: [{ type: 'text', text: params.text }],
93
- metadata: { agentId: params.agentId, agentName: params.agentName, createdAt: Date.now() },
94
- }
95
- }
96
-
97
- function toSafeJobIdSegment(value: string): string {
98
- return value.replace(/[^a-zA-Z0-9_-]/g, '_')
99
- }
100
-
101
- function createSocialMemoryDedupeKey(params: { workspaceId: string; threadId: string; messageId: string }): string {
102
- return [
103
- 'social-memory',
104
- toSafeJobIdSegment(params.workspaceId),
105
- toSafeJobIdSegment(params.threadId),
106
- toSafeJobIdSegment(params.messageId),
107
- ].join('-')
108
- }
109
-
110
- function buildBuildToolsParams(params: {
111
- agentId: string
112
- context: LotaSocialChatResolvedContext
113
- workspaceIdString: string
114
- userIdString: string
115
- messageContext: SlackSocialMessageContext
116
- memoryBlock: string
117
- onAppendMemoryBlock: (value: string) => void
118
- }): BuildSocialChatAgentToolsParams {
119
- return {
120
- agentId: params.agentId,
121
- workspaceId: params.context.workspaceId,
122
- workspaceIdString: params.workspaceIdString,
123
- userId: params.context.userId,
124
- userIdString: params.userIdString,
125
- userName: params.context.userName,
126
- platform: 'slack',
127
- channelId: params.messageContext.channelId,
128
- threadId: params.messageContext.threadId,
129
- incomingMessageId: params.messageContext.messageId,
130
- incomingText: params.messageContext.text,
131
- memoryBlock: params.memoryBlock,
132
- onAppendMemoryBlock: params.onAppendMemoryBlock,
133
- context: null,
134
- }
135
- }
136
-
137
- export function createSocialChatRuntime(params: {
138
- redisClient: IORedis
139
- socialChat?: LotaRuntimeSocialChatConfig
140
- }): LotaRuntimeSocialChat {
141
- const socialChatConfig = params.socialChat
142
- const slackConfig = socialChatConfig?.slack
143
- const slackEnabled = Boolean(slackConfig?.botToken?.trim() && slackConfig.signingSecret?.trim())
144
-
145
- if (!socialChatConfig || !slackEnabled) {
146
- return createDisabledSocialChatRuntime()
147
- }
148
-
149
- const socialAgentId = toOptionalTrimmedString(socialChatConfig.agentId) ?? DEFAULT_SOCIAL_CHAT_AGENT_ID
150
- const socialAgentDisplayName =
151
- toOptionalTrimmedString(socialChatConfig.agentDisplayName) ?? DEFAULT_SOCIAL_CHAT_AGENT_DISPLAY_NAME
152
- const stateKeyPrefix =
153
- toOptionalTrimmedString(socialChatConfig.stateRedisKeyPrefix) ?? DEFAULT_SOCIAL_CHAT_STATE_PREFIX
154
- const chat = new Chat({
155
- userName: toOptionalTrimmedString(slackConfig?.userName) ?? socialAgentDisplayName,
156
- adapters: {
157
- slack: createSlackAdapter({
158
- botToken: slackConfig?.botToken,
159
- signingSecret: slackConfig?.signingSecret,
160
- userName: toOptionalTrimmedString(slackConfig?.userName) ?? socialAgentDisplayName,
161
- }),
162
- },
163
- state: createIoRedisState({
164
- client: params.redisClient,
165
- keyPrefix: stateKeyPrefix,
166
- logger: new ConsoleLogger('warn', 'lota-social-chat'),
167
- }),
168
- dedupeTtlMs: slackConfig?.dedupeTtlMs ?? DEFAULT_SOCIAL_CHAT_DEDUPE_TTL_MS,
169
- })
170
-
171
- const initialize = async () => {
172
- await chat.initialize()
173
- }
174
-
175
- const shutdown = async () => {
176
- await chat.shutdown()
177
- }
178
-
179
- const handleMessage = async (thread: Thread, incomingMessage: Message) => {
180
- const rawSlackMessage = incomingMessage.raw as { channel?: unknown } | undefined
181
- const channelId = toOptionalTrimmedString(rawSlackMessage?.channel) ?? thread.channelId
182
- const messageContext: SlackSocialMessageContext = {
183
- channelId,
184
- threadId: thread.id,
185
- messageId: incomingMessage.id,
186
- text: incomingMessage.text.trim(),
187
- authorId: toOptionalTrimmedString(incomingMessage.author.userId) ?? undefined,
188
- authorName: readSlackAuthorName(incomingMessage),
189
- }
190
- aiLogger.info`Slack social-chat message received: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}, messageId=${messageContext.messageId}, author=${messageContext.authorName ?? 'unknown'}, textLength=${messageContext.text.length}`
191
-
192
- const resolvedContext = await socialChatConfig.resolveContext({
193
- platform: 'slack',
194
- channelId: messageContext.channelId,
195
- threadId: messageContext.threadId,
196
- messageId: messageContext.messageId,
197
- text: messageContext.text,
198
- authorId: messageContext.authorId,
199
- authorName: messageContext.authorName,
200
- })
201
- const workspaceIdString = recordIdToString(resolvedContext.workspaceId, TABLES.ORGANIZATION)
202
- const userIdString = recordIdToString(resolvedContext.userId, TABLES.USER)
203
- aiLogger.info`Slack social-chat context resolved: workspaceId=${workspaceIdString}, userId=${userIdString}`
204
-
205
- const threadMessages = await collectThreadMessages(thread, incomingMessage)
206
- const normalizedMessages = threadMessages
207
- .map((message) =>
208
- normalizeSocialHistoryMessage({
209
- workspaceId: workspaceIdString,
210
- channelId: messageContext.channelId,
211
- agentId: socialAgentId,
212
- agentDisplayName: socialAgentDisplayName,
213
- message,
214
- }),
215
- )
216
- .filter((message): message is NonNullable<typeof message> => message !== null)
217
- await socialChatHistoryService.upsertMessages(normalizedMessages)
218
-
219
- const historyBeforeReply = await socialChatHistoryService.listThreadMessages({
220
- workspaceId: workspaceIdString,
221
- threadId: messageContext.threadId,
222
- })
223
- const currentUserMessage =
224
- historyBeforeReply.find((message) => message.messageId === incomingMessage.id) ?? historyBeforeReply.at(-1)
225
- const priorHistory = currentUserMessage
226
- ? historyBeforeReply.filter((message) => message.cursor.id !== currentUserMessage.cursor.id)
227
- : historyBeforeReply
228
-
229
- const workspaceProvider = getRuntimeAdapters().workspaceProvider
230
- const workspace = workspaceProvider ? await workspaceProvider.getWorkspace(resolvedContext.workspaceId) : {}
231
- const lifecycleState = workspaceProvider ? await workspaceProvider.getLifecycleState?.(workspace) : undefined
232
- const workspaceProfileState = workspaceProvider
233
- ? await workspaceProvider.readProfileProjectionState?.(workspace)
234
- : undefined
235
- const [recentDomainEvents, promptSummary] = await Promise.all([
236
- workspaceProvider?.listRecentDomainEvents?.(resolvedContext.workspaceId, 5) ??
237
- Promise.resolve([] as Array<Record<string, unknown>>),
238
- workspaceProvider?.buildPromptSummary
239
- ? workspaceProvider.buildPromptSummary(resolvedContext.workspaceId).catch(() => undefined)
240
- : Promise.resolve(undefined),
241
- ])
242
-
243
- const promptContext = buildAgentPromptContext({
244
- workspaceName:
245
- workspaceProfileState?.workspaceName ??
246
- toOptionalTrimmedString((workspace as { name?: unknown }).name) ??
247
- undefined,
248
- summaryBlock: workspaceProfileState?.summaryBlock,
249
- promptSummary,
250
- userName: messageContext.authorName,
251
- recentDomainEvents,
252
- })
253
- const retrievedKnowledgeSection =
254
- lifecycleState?.bootstrapActive || messageContext.text.length === 0
255
- ? undefined
256
- : await workspaceProvider?.buildRetrievedKnowledgeSection?.({
257
- workspaceId: workspaceIdString,
258
- userId: userIdString,
259
- query: messageContext.text,
260
- })
261
-
262
- const [preSeededMemoriesSection, learnedSkillsSection] = await Promise.all([
263
- memoryService.getTopMemories({
264
- orgId: workspaceIdString,
265
- agentName: socialAgentId,
266
- limit: PRESEEDED_MEMORY_LOOKUP_LIMIT,
267
- }),
268
- lifecycleState?.bootstrapActive
269
- ? Promise.resolve(undefined)
270
- : learnedSkillService
271
- .retrieveForTurn({
272
- orgId: workspaceIdString,
273
- agentId: socialAgentId,
274
- query: messageContext.text,
275
- limit: 3,
276
- minConfidence: 0.6,
277
- })
278
- .catch((error) => {
279
- aiLogger.warn`Failed to retrieve learned skills for ${socialAgentId}: ${error}`
280
- return undefined
281
- }),
282
- ])
283
-
284
- let memoryBlock = ''
285
- const consultParticipants = [
286
- ...new Set(
287
- (await socialChatConfig.getConsultParticipants?.({
288
- workspaceId: resolvedContext.workspaceId,
289
- workspaceIdString,
290
- platform: 'slack',
291
- })) ?? [...teamConsultParticipants],
292
- ),
293
- ].filter((agentId) => agentId !== socialAgentId)
294
- const executedToolNames: string[] = []
295
-
296
- const baseTools = withLoggedSocialToolSet(
297
- await socialChatConfig.buildAgentTools(
298
- buildBuildToolsParams({
299
- agentId: socialAgentId,
300
- context: resolvedContext,
301
- workspaceIdString,
302
- userIdString,
303
- messageContext,
304
- memoryBlock,
305
- onAppendMemoryBlock: (value: string) => {
306
- memoryBlock = value
307
- },
308
- }),
309
- ),
310
- {
311
- agentId: socialAgentId,
312
- channelId: messageContext.channelId,
313
- threadId: messageContext.threadId,
314
- executedToolNames,
315
- },
316
- )
317
-
318
- const transcript = buildSocialChatThreadTranscript(historyBeforeReply)
319
- const runAbort = createServerRunAbortController()
320
-
321
- const consultSpecialistTool = createTool({
322
- description: 'Consult one specialist teammate for targeted guidance before replying to the user.',
323
- inputSchema: ConsultSpecialistArgsSchema,
324
- execute: async ({ agentId, task }: ConsultSpecialistArgs) => {
325
- if (!consultParticipants.includes(agentId)) {
326
- throw new Error(`Agent "${agentId}" is not an allowed social-chat specialist.`)
327
- }
328
-
329
- const { result: specialistRun } = await runSpecialistSession({
330
- initialMemoryBlock: '',
331
- buildTools: async ({ memoryBlock: currentMemoryBlock, onAppendMemoryBlock }) =>
332
- withLoggedSocialToolSet(
333
- await socialChatConfig.buildAgentTools(
334
- buildBuildToolsParams({
335
- agentId,
336
- context: resolvedContext,
337
- workspaceIdString,
338
- userIdString,
339
- messageContext,
340
- memoryBlock: currentMemoryBlock,
341
- onAppendMemoryBlock,
342
- }),
343
- ),
344
- { agentId, channelId: messageContext.channelId, threadId: messageContext.threadId, executedToolNames },
345
- ),
346
- run: async ({ tools }) => {
347
- const [specialistPreSeededMemories, specialistLearnedSkills] = await Promise.all([
348
- memoryService.getTopMemories({
349
- orgId: workspaceIdString,
350
- agentName: agentId,
351
- limit: PRESEEDED_MEMORY_LOOKUP_LIMIT,
352
- }),
353
- lifecycleState?.bootstrapActive
354
- ? Promise.resolve(undefined)
355
- : learnedSkillService
356
- .retrieveForTurn({ orgId: workspaceIdString, agentId, query: task, limit: 3, minConfidence: 0.6 })
357
- .catch((error) => {
358
- aiLogger.warn`Failed to retrieve learned skills for ${agentId}: ${error}`
359
- return undefined
360
- }),
361
- ])
362
-
363
- return await runSocialAgentTurn({
364
- agentId,
365
- mode: 'fixedThreadMode',
366
- threadType: 'group',
367
- onboardingActive: lifecycleState?.bootstrapActive ?? false,
368
- linearInstalled: false,
369
- systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
370
- preSeededMemoriesSection: specialistPreSeededMemories,
371
- retrievedKnowledgeSection,
372
- learnedSkillsSection: specialistLearnedSkills,
373
- userMessageText: task,
374
- additionalInstructionSections: [
375
- `You are supporting ${socialAgentDisplayName} in a Slack social-chat thread. Stay within your role.`,
376
- ],
377
- tools,
378
- prompt: buildSpecialistSocialChatPrompt({
379
- requesterName: socialAgentDisplayName,
380
- agentName: agentDisplayNames[agentId] ?? agentId,
381
- task,
382
- transcript,
383
- }),
384
- abortSignal: runAbort.signal,
385
- })
386
- },
387
- })
388
- const text = specialistRun.text
389
-
390
- return createAssistantMessage({ agentId, agentName: agentDisplayNames[agentId] ?? agentId, text })
391
- },
392
- toModelOutput: ({ output }) => {
393
- const message = output
394
- const agentName =
395
- typeof message.metadata?.agentName === 'string' && message.metadata.agentName.trim().length > 0
396
- ? message.metadata.agentName.trim()
397
- : 'Specialist'
398
- const summary = extractMessageText(message).trim()
399
- return {
400
- type: 'text',
401
- value: summary ? `${agentName}: ${summary}` : `${agentName} completed the requested task.`,
402
- }
403
- },
404
- })
405
-
406
- await thread.startTyping('Thinking...').catch(() => undefined)
407
- aiLogger.info`Slack social-chat generating reply: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}`
408
- const leadRun = await runSocialAgentTurn({
409
- agentId: socialAgentId,
410
- mode: 'threadMode',
411
- threadType: 'group',
412
- onboardingActive: lifecycleState?.bootstrapActive ?? false,
413
- linearInstalled: false,
414
- systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
415
- preSeededMemoriesSection,
416
- retrievedKnowledgeSection,
417
- learnedSkillsSection,
418
- userMessageText: messageContext.text,
419
- additionalInstructionSections: [buildSocialChatIdentitySection(socialAgentDisplayName)],
420
- tools: { ...baseTools, [CONSULT_SPECIALIST_TOOL_NAME]: consultSpecialistTool },
421
- prompt: buildLeadSocialChatPrompt({
422
- agentDisplayName: socialAgentDisplayName,
423
- channelId: messageContext.channelId,
424
- threadId: messageContext.threadId,
425
- transcript,
426
- latestUserMessage: messageContext.text,
427
- latestAuthorName: messageContext.authorName,
428
- }),
429
- abortSignal: runAbort.signal,
430
- })
431
- const responseText = leadRun.text
432
-
433
- const replyMarkdown = buildSlackSocialReplyMarkdown({ replyMarkdown: responseText, executedToolNames })
434
- const sentMessage = await thread.post({ markdown: replyMarkdown })
435
- aiLogger.info`Slack social-chat reply posted: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}, replyMessageId=${sentMessage.id}`
436
- const normalizedResponse = normalizeSocialHistoryMessage({
437
- workspaceId: workspaceIdString,
438
- channelId: messageContext.channelId,
439
- agentId: socialAgentId,
440
- agentDisplayName: socialAgentDisplayName,
441
- message: sentMessage,
442
- textOverride: responseText,
443
- })
444
- if (normalizedResponse) {
445
- await socialChatHistoryService.upsertMessages([normalizedResponse])
446
- }
447
-
448
- const priorHistoryMessages = toHistoryMessages(priorHistory)
449
- const agentMessages = normalizedResponse ? buildAgentHistoryMessages([normalizedResponse]) : []
450
- if (messageContext.text && agentMessages.length > 0) {
451
- await safeEnqueue(
452
- () =>
453
- enqueuePostChatMemory(
454
- {
455
- orgId: workspaceIdString,
456
- threadId: `social:slack:${messageContext.threadId}`,
457
- sourceId: createSocialChatCursorId({
458
- workspaceId: workspaceIdString,
459
- threadId: messageContext.threadId,
460
- messageId: messageContext.messageId,
461
- }),
462
- userMessage: messageContext.text,
463
- historyMessages: priorHistoryMessages,
464
- agentMessages,
465
- memoryBlock: memoryBlock.trim() ? memoryBlock : undefined,
466
- source: 'social_chat',
467
- sourceMetadata: {
468
- platform: 'slack',
469
- channelId: messageContext.channelId,
470
- threadId: messageContext.threadId,
471
- messageId: messageContext.messageId,
472
- authorId: messageContext.authorId,
473
- authorName: messageContext.authorName,
474
- },
475
- },
476
- {
477
- dedupeKey: createSocialMemoryDedupeKey({
478
- workspaceId: workspaceIdString,
479
- threadId: messageContext.threadId,
480
- messageId: messageContext.messageId,
481
- }),
482
- },
483
- ),
484
- { operationName: 'social post-chat memory extraction enqueue' },
485
- )
486
- }
487
-
488
- await safeEnqueue(() => enqueueRegularChatMemoryDigest({ orgId: workspaceIdString }), {
489
- operationName: 'social regular chat memory digest enqueue',
490
- })
491
- await safeEnqueue(() => enqueueSkillExtraction({ orgId: workspaceIdString }), {
492
- operationName: 'social skill extraction enqueue',
493
- })
494
- }
495
-
496
- chat.onNewMention(async (thread, message) => {
497
- aiLogger.info`Slack social-chat new mention received: threadId=${thread.id}, messageId=${message.id}`
498
- await thread.subscribe()
499
- await handleMessage(thread, message)
500
- })
501
- chat.onSubscribedMessage(async (thread, message) => {
502
- aiLogger.info`Slack social-chat subscribed thread message received: threadId=${thread.id}, messageId=${message.id}`
503
- await handleMessage(thread, message)
504
- })
505
-
506
- return {
507
- enabled: true,
508
- initialize,
509
- shutdown,
510
- webhooks: {
511
- async slack(request: Request, options?: WebhookOptions) {
512
- return chat.webhooks.slack(request, options)
513
- },
514
- },
515
- }
516
- }