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