@lota-sdk/core 0.4.9 → 0.4.11

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 (182) 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 +164 -82
  4. package/src/ai-gateway/index.ts +16 -1
  5. package/src/config/agent-defaults.ts +4 -107
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/config/background-processing.ts +1 -1
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +22 -25
  10. package/src/config/thread-defaults.ts +1 -10
  11. package/src/create-runtime.ts +145 -670
  12. package/src/db/base.service.ts +30 -38
  13. package/src/db/memory-query-builder.ts +2 -1
  14. package/src/db/memory-store.ts +29 -20
  15. package/src/db/memory.ts +188 -195
  16. package/src/db/service-normalization.ts +97 -64
  17. package/src/db/service.ts +496 -384
  18. package/src/db/startup.ts +30 -19
  19. package/src/effect/helpers.ts +30 -5
  20. package/src/effect/index.ts +7 -7
  21. package/src/effect/layers.ts +75 -72
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -71
  24. package/src/index.ts +13 -12
  25. package/src/queues/autonomous-job.queue.ts +177 -143
  26. package/src/queues/context-compaction.queue.ts +41 -39
  27. package/src/queues/delayed-node-promotion.queue.ts +61 -42
  28. package/src/queues/document-processor.queue.ts +5 -3
  29. package/src/queues/index.ts +1 -0
  30. package/src/queues/memory-consolidation.queue.ts +79 -53
  31. package/src/queues/organization-learning.queue.ts +70 -33
  32. package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
  33. package/src/queues/plan-scheduler.queue.ts +101 -97
  34. package/src/queues/post-chat-memory.queue.ts +56 -46
  35. package/src/queues/queue-factory.ts +146 -69
  36. package/src/queues/queues.service.ts +61 -0
  37. package/src/queues/title-generation.queue.ts +44 -44
  38. package/src/redis/connection.ts +181 -164
  39. package/src/redis/org-memory-lock.ts +24 -9
  40. package/src/redis/redis-lease-lock.ts +8 -1
  41. package/src/redis/stream-context.ts +17 -9
  42. package/src/runtime/agent-identity-overrides.ts +7 -3
  43. package/src/runtime/agent-runtime-policy.ts +10 -5
  44. package/src/runtime/agent-stream-helpers.ts +24 -15
  45. package/src/runtime/chat-run-orchestration.ts +1 -1
  46. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  47. package/src/runtime/context-compaction/context-compaction.ts +131 -85
  48. package/src/runtime/domain-layer.ts +203 -0
  49. package/src/runtime/execution-plan-visibility.ts +5 -2
  50. package/src/runtime/graph-designer.ts +0 -14
  51. package/src/runtime/helper-model.ts +8 -4
  52. package/src/runtime/index.ts +1 -1
  53. package/src/runtime/indexed-repositories-policy.ts +2 -6
  54. package/src/runtime/memory/memory-block.ts +19 -9
  55. package/src/runtime/memory/memory-pipeline.ts +53 -66
  56. package/src/runtime/memory/memory-scope.ts +33 -29
  57. package/src/runtime/plugin-resolution.ts +58 -62
  58. package/src/runtime/post-turn-side-effects.ts +139 -161
  59. package/src/runtime/retrieval-adapters.ts +4 -4
  60. package/src/runtime/runtime-config.ts +3 -9
  61. package/src/runtime/runtime-extensions.ts +0 -43
  62. package/src/runtime/runtime-lifecycle.ts +124 -0
  63. package/src/runtime/runtime-services.ts +455 -0
  64. package/src/runtime/runtime-worker-registry.ts +113 -30
  65. package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
  66. package/src/runtime/social-chat/social-chat-history.ts +24 -13
  67. package/src/runtime/social-chat/social-chat.ts +420 -369
  68. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
  69. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  70. package/src/runtime/thread-chat-helpers.ts +18 -9
  71. package/src/runtime/thread-turn-context.ts +28 -74
  72. package/src/runtime/turn-lifecycle.ts +6 -14
  73. package/src/services/agent-activity.service.ts +169 -176
  74. package/src/services/agent-executor.service.ts +207 -196
  75. package/src/services/artifact.service.ts +10 -5
  76. package/src/services/attachment.service.ts +16 -48
  77. package/src/services/autonomous-job.service.ts +81 -87
  78. package/src/services/background-work.service.ts +54 -0
  79. package/src/services/chat-run-registry.service.ts +3 -1
  80. package/src/services/context-compaction.service.ts +8 -10
  81. package/src/services/document-chunk.service.ts +8 -17
  82. package/src/services/execution-plan/execution-plan-graph.ts +122 -109
  83. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  84. package/src/services/execution-plan/execution-plan.service.ts +68 -51
  85. package/src/services/feedback-loop.service.ts +1 -1
  86. package/src/services/global-orchestrator.service.ts +49 -15
  87. package/src/services/graph-full-routing.ts +49 -37
  88. package/src/services/index.ts +1 -0
  89. package/src/services/institutional-memory.service.ts +8 -17
  90. package/src/services/learned-skill.service.ts +38 -35
  91. package/src/services/memory/memory-conversation.ts +10 -5
  92. package/src/services/memory/memory-errors.ts +27 -0
  93. package/src/services/memory/memory-org-memory.ts +14 -3
  94. package/src/services/memory/memory-preseeded.ts +10 -4
  95. package/src/services/memory/memory-utils.ts +2 -1
  96. package/src/services/memory/memory.service.ts +37 -52
  97. package/src/services/memory/rerank.service.ts +3 -11
  98. package/src/services/monitoring-window.service.ts +1 -1
  99. package/src/services/mutating-approval.service.ts +1 -1
  100. package/src/services/node-workspace.service.ts +2 -2
  101. package/src/services/notification.service.ts +16 -4
  102. package/src/services/organization-member.service.ts +1 -1
  103. package/src/services/organization.service.ts +34 -51
  104. package/src/services/ownership-dispatcher.service.ts +148 -95
  105. package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
  106. package/src/services/plan/plan-agent-query.service.ts +13 -9
  107. package/src/services/plan/plan-approval.service.ts +52 -48
  108. package/src/services/plan/plan-artifact.service.ts +2 -2
  109. package/src/services/plan/plan-builder.service.ts +2 -2
  110. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  111. package/src/services/plan/plan-compiler.service.ts +1 -1
  112. package/src/services/plan/plan-completion-side-effects.ts +99 -113
  113. package/src/services/plan/plan-coordination.service.ts +1 -1
  114. package/src/services/plan/plan-cycle.service.ts +171 -202
  115. package/src/services/plan/plan-deadline.service.ts +304 -307
  116. package/src/services/plan/plan-event-delivery.service.ts +84 -72
  117. package/src/services/plan/plan-executor-context.ts +2 -0
  118. package/src/services/plan/plan-executor-graph.ts +375 -353
  119. package/src/services/plan/plan-executor-helpers.ts +60 -75
  120. package/src/services/plan/plan-executor.service.ts +494 -489
  121. package/src/services/plan/plan-run.service.ts +12 -19
  122. package/src/services/plan/plan-scheduler.service.ts +89 -82
  123. package/src/services/plan/plan-template.service.ts +1 -1
  124. package/src/services/plan/plan-transaction-events.ts +8 -5
  125. package/src/services/plan/plan-validator.service.ts +1 -1
  126. package/src/services/plan/plan-workspace.service.ts +17 -11
  127. package/src/services/plugin-executor.service.ts +26 -21
  128. package/src/services/quality-metrics.service.ts +1 -1
  129. package/src/services/queue-job.service.ts +8 -17
  130. package/src/services/recent-activity-title.service.ts +22 -10
  131. package/src/services/recent-activity.service.ts +1 -1
  132. package/src/services/skill-resolver.service.ts +1 -1
  133. package/src/services/social-chat-history.service.ts +37 -20
  134. package/src/services/system-executor.service.ts +25 -20
  135. package/src/services/thread/thread-bootstrap.ts +37 -19
  136. package/src/services/thread/thread-listing.ts +2 -1
  137. package/src/services/thread/thread-memory-block.ts +18 -5
  138. package/src/services/thread/thread-message.service.ts +30 -13
  139. package/src/services/thread/thread-title.service.ts +1 -1
  140. package/src/services/thread/thread-turn-execution.ts +87 -83
  141. package/src/services/thread/thread-turn-preparation.service.ts +65 -40
  142. package/src/services/thread/thread-turn-streaming.ts +32 -36
  143. package/src/services/thread/thread-turn.ts +43 -29
  144. package/src/services/thread/thread.service.ts +32 -8
  145. package/src/services/user.service.ts +1 -1
  146. package/src/services/write-intent-validator.service.ts +1 -1
  147. package/src/storage/attachment-storage.service.ts +7 -4
  148. package/src/storage/generated-document-storage.service.ts +1 -1
  149. package/src/system-agents/context-compaction.agent.ts +1 -1
  150. package/src/system-agents/helper-agent-options.ts +1 -1
  151. package/src/system-agents/memory-reranker.agent.ts +1 -1
  152. package/src/system-agents/memory.agent.ts +1 -1
  153. package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
  154. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  155. package/src/system-agents/skill-extractor.agent.ts +1 -1
  156. package/src/system-agents/skill-manager.agent.ts +1 -1
  157. package/src/system-agents/thread-router.agent.ts +23 -20
  158. package/src/system-agents/title-generator.agent.ts +1 -1
  159. package/src/tools/execution-plan.tool.ts +36 -20
  160. package/src/tools/fetch-webpage.tool.ts +30 -22
  161. package/src/tools/firecrawl-client.ts +1 -6
  162. package/src/tools/plan-approval.tool.ts +9 -1
  163. package/src/tools/remember-memory.tool.ts +3 -6
  164. package/src/tools/research-topic.tool.ts +12 -3
  165. package/src/tools/search-web.tool.ts +26 -18
  166. package/src/tools/search.tool.ts +4 -5
  167. package/src/tools/team-think.tool.ts +139 -121
  168. package/src/utils/async.ts +15 -6
  169. package/src/utils/errors.ts +27 -15
  170. package/src/workers/bootstrap.ts +34 -58
  171. package/src/workers/memory-consolidation.worker.ts +4 -1
  172. package/src/workers/organization-learning.worker.ts +16 -3
  173. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  174. package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
  175. package/src/workers/skill-extraction.runner.ts +13 -15
  176. package/src/workers/worker-utils.ts +14 -8
  177. package/src/config/search.ts +0 -3
  178. package/src/effect/awaitable-effect.ts +0 -87
  179. package/src/effect/runtime-ref.ts +0 -25
  180. package/src/effect/runtime.ts +0 -31
  181. package/src/redis/runtime-connection.ts +0 -10
  182. package/src/runtime/agent-types.ts +0 -1
@@ -3,16 +3,20 @@ import { stepCountIs } from 'ai'
3
3
  import type { ToolSet } from 'ai'
4
4
  import { Schema, Effect } from 'effect'
5
5
 
6
- import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../config/agent-defaults'
7
6
  import { aiLogger } from '../config/logger'
8
7
  import type { RecordIdRef } from '../db/record-id'
9
8
  import { recordIdToString } from '../db/record-id'
10
9
  import { TABLES } from '../db/tables'
11
10
  import { effectTryMaybeAsync as effectTryMaybeAsyncShared } from '../effect/helpers'
11
+ import {
12
+ AgentConfigServiceTag,
13
+ AgentFactoryServiceTag,
14
+ RuntimeAdaptersServiceTag,
15
+ TurnHooksServiceTag,
16
+ } from '../effect/services'
12
17
  import { readRuntimeAgentIdentityOverrides } from '../runtime/agent-identity-overrides'
13
18
  import { mergeInstructionSections } from '../runtime/instruction-sections'
14
- import { getRuntimeAdapters, getTurnHooks } from '../runtime/runtime-extensions'
15
- import type { LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
19
+ import type { LotaRuntimeAdapters, LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
16
20
  import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation/team-consultation-orchestrator'
17
21
  import type {
18
22
  DefaultRepoSections,
@@ -24,8 +28,9 @@ import type { ReadableUploadMetadata } from '../services/attachment.service'
24
28
 
25
29
  function buildTeamThinkAgentToolsEffect(
26
30
  params: LotaRuntimeTeamThinkToolsParams,
31
+ runtimeAdapters: LotaRuntimeAdapters,
27
32
  ): Effect.Effect<{ tools: ToolSet }, TeamThinkRuntimeError> {
28
- const builder = getRuntimeAdapters().buildTeamThinkAgentTools
33
+ const builder = runtimeAdapters.buildTeamThinkAgentTools
29
34
  if (!builder) {
30
35
  return Effect.succeed({ tools: {} })
31
36
  }
@@ -72,124 +77,137 @@ export function createTeamThinkTool(params: {
72
77
  toolProviders?: ToolSet
73
78
  abortSignal: AbortSignal
74
79
  }) {
75
- const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(
76
- (params.context as Record<string, unknown> | null | undefined) ?? null,
77
- )
78
- const participantRunner: TeamConsultationParticipantRunner = {
79
- buildParticipantAgent(agentId, runParams) {
80
- return Effect.runPromise(
81
- Effect.gen(function* () {
82
- const currentContext = yield* Effect.context()
83
- const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
84
- const dynamicInstructionSections = yield* effectTryMaybeAsync(
85
- () => params.getAdditionalInstructionSections?.(),
86
- 'Failed to load dynamic team-think instruction sections.',
87
- )
88
- const agentResolution = asRecord(
89
- yield* effectTryMaybeAsync(
90
- () =>
91
- getTurnHooks().resolveAgent?.({
92
- agentId,
93
- mode: 'fixedThreadMode',
94
- thread: null,
95
- threadRef: params.threadId,
96
- orgRef: params.orgId,
97
- userRef: params.userId,
98
- onboardingActive: false,
99
- linearInstalled: false,
100
- githubInstalled: params.githubInstalled,
101
- additionalInstructionSections: mergeInstructionSections(
102
- dynamicInstructionSections,
103
- params.additionalInstructionSections,
104
- ),
105
- context: (params.context as Record<string, unknown> | null | undefined) ?? null,
106
- }),
107
- 'Failed to resolve team-think participant agent.',
108
- ),
109
- )
110
- const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
111
- const config = getAgentRuntimeConfig({
112
- agentId: resolvedAgentId,
113
- threadType: 'group' as const,
114
- mode: 'fixedThreadMode',
115
- onboardingActive: false,
116
- linearInstalled: false,
117
- systemWorkspaceDetails: runParams.systemWorkspaceDetails,
118
- preSeededMemoriesSection: runParams.preSeededMemoriesSection,
119
- retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
120
- additionalInstructionSections: mergeInstructionSections(
121
- dynamicInstructionSections,
122
- params.additionalInstructionSections,
123
- readInstructionSections(agentResolution?.additionalInstructionSections),
124
- ),
125
- responseGuardSection: buildTeamConsultationResponseGuard({
80
+ return Effect.gen(function* () {
81
+ const turnHooks = yield* TurnHooksServiceTag
82
+ const runtimeAdapters = yield* RuntimeAdaptersServiceTag
83
+ const agentConfig = yield* AgentConfigServiceTag
84
+ const agentFactoryConfig = yield* AgentFactoryServiceTag
85
+ const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(
86
+ (params.context as Record<string, unknown> | null | undefined) ?? null,
87
+ )
88
+ const participantRunner: TeamConsultationParticipantRunner = {
89
+ buildParticipantAgent(agentId, runParams) {
90
+ return Effect.runPromise(
91
+ Effect.gen(function* () {
92
+ const currentContext = yield* Effect.context()
93
+ const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
94
+ const dynamicInstructionSections = yield* effectTryMaybeAsync(
95
+ () => params.getAdditionalInstructionSections?.(),
96
+ 'Failed to load dynamic team-think instruction sections.',
97
+ )
98
+ const agentResolution = asRecord(
99
+ yield* effectTryMaybeAsync(
100
+ () =>
101
+ turnHooks.resolveAgent?.({
102
+ agentId,
103
+ mode: 'fixedThreadMode',
104
+ thread: null,
105
+ threadRef: params.threadId,
106
+ orgRef: params.orgId,
107
+ userRef: params.userId,
108
+ onboardingActive: false,
109
+ linearInstalled: false,
110
+ githubInstalled: params.githubInstalled,
111
+ additionalInstructionSections: mergeInstructionSections(
112
+ dynamicInstructionSections,
113
+ params.additionalInstructionSections,
114
+ ),
115
+ context: (params.context as Record<string, unknown> | null | undefined) ?? null,
116
+ }),
117
+ 'Failed to resolve team-think participant agent.',
118
+ ),
119
+ )
120
+ const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
121
+ const config = agentFactoryConfig.getAgentRuntimeConfig({
126
122
  agentId: resolvedAgentId,
127
- task: runParams.task,
128
- }),
129
- })
130
- const { tools } = yield* buildTeamThinkAgentToolsEffect({
131
- agentId: resolvedAgentId,
132
- workspaceId: params.orgId,
133
- userId: params.userId,
134
- workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
135
- threadId: params.threadId,
136
- githubInstalled: params.githubInstalled,
137
- provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
138
- availableUploads: params.availableUploads,
139
- defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
140
- context: params.context,
141
- toolProviders: params.toolProviders,
142
- })
143
- const agentId_ = config.id || resolvedAgentId
144
- const configuredMaxSteps = config.maxSteps
145
- const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
146
- const agentFactory = getResolvedAgentFactoryConfig().createAgent[agentId_]
147
- if (!agentFactory) {
148
- return yield* new TeamThinkAgentFactoryNotConfiguredError({ agentId: agentId_ })
149
- }
150
- const agent = agentFactory({
151
- mode: 'fixedThreadMode',
152
- tools,
153
- extraInstructions: config.extraInstructions,
154
- maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
155
- stopWhen: [stepCountIs(maxSteps)],
156
- })
157
- const observer = {
158
- run: <T>(fn: () => T | Promise<T>): Promise<T> =>
159
- runPromiseWithCurrentContext(effectTryMaybeAsync(fn, `Team-think participant run failed (${agentId}).`)),
160
- recordError: (error: unknown) => {
161
- aiLogger.error`Team-think participant failed (${agentId}): ${error}`
162
- },
163
- recordAbort: (error: unknown) => {
164
- aiLogger.info`Team-think participant aborted (${agentId}): ${
165
- error instanceof Error ? error.message : String(error)
166
- }`
167
- },
168
- }
169
- return {
170
- agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
171
- observer,
172
- }
173
- }),
174
- )
175
- },
176
- }
123
+ threadType: 'group' as const,
124
+ mode: 'fixedThreadMode',
125
+ onboardingActive: false,
126
+ linearInstalled: false,
127
+ systemWorkspaceDetails: runParams.systemWorkspaceDetails,
128
+ preSeededMemoriesSection: runParams.preSeededMemoriesSection,
129
+ retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
130
+ additionalInstructionSections: mergeInstructionSections(
131
+ dynamicInstructionSections,
132
+ params.additionalInstructionSections,
133
+ readInstructionSections(agentResolution?.additionalInstructionSections),
134
+ ),
135
+ responseGuardSection: buildTeamConsultationResponseGuard({
136
+ agentConfig,
137
+ agentId: resolvedAgentId,
138
+ task: runParams.task,
139
+ }),
140
+ })
141
+ const { tools } = yield* buildTeamThinkAgentToolsEffect(
142
+ {
143
+ agentId: resolvedAgentId,
144
+ workspaceId: params.orgId,
145
+ userId: params.userId,
146
+ workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
147
+ threadId: params.threadId,
148
+ githubInstalled: params.githubInstalled,
149
+ provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
150
+ availableUploads: params.availableUploads,
151
+ defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
152
+ context: params.context,
153
+ toolProviders: params.toolProviders,
154
+ },
155
+ runtimeAdapters,
156
+ )
157
+ const agentId_ = config.id || resolvedAgentId
158
+ const configuredMaxSteps = config.maxSteps
159
+ const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
160
+ const agentFactory = agentFactoryConfig.createAgent[agentId_]
161
+ if (!agentFactory) {
162
+ return yield* new TeamThinkAgentFactoryNotConfiguredError({ agentId: agentId_ })
163
+ }
164
+ const agent = agentFactory({
165
+ mode: 'fixedThreadMode',
166
+ tools,
167
+ extraInstructions: config.extraInstructions,
168
+ maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
169
+ stopWhen: [stepCountIs(maxSteps)],
170
+ })
171
+ const observer = {
172
+ run: <T>(fn: () => T | Promise<T>): Promise<T> =>
173
+ runPromiseWithCurrentContext(
174
+ effectTryMaybeAsync(fn, `Team-think participant run failed (${agentId}).`),
175
+ ),
176
+ recordError: (error: unknown) => {
177
+ aiLogger.error`Team-think participant failed (${agentId}): ${error}`
178
+ },
179
+ recordAbort: (error: unknown) => {
180
+ aiLogger.debug`Team-think participant aborted (${agentId}): ${
181
+ error instanceof Error ? error.message : String(error)
182
+ }`
183
+ },
184
+ }
185
+ return {
186
+ agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
187
+ observer,
188
+ }
189
+ }).pipe(Effect.withSpan('tool.teamThink.buildParticipantAgent')),
190
+ )
191
+ },
192
+ }
177
193
 
178
- return createConsultTeamToolSdk({
179
- historyMessages: params.historyMessages,
180
- latestUserMessageId: params.latestUserMessageId,
181
- availableUploads: params.availableUploads,
182
- defaultRepoSectionsByAgent: params.defaultRepoSectionsByAgent,
183
- systemWorkspaceDetails: params.systemWorkspaceDetails,
184
- getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
185
- retrievedKnowledgeSection: params.retrievedKnowledgeSection,
186
- displayNamesById: agentIdentityOverrides.displayNamesById,
187
- abortSignal: params.abortSignal,
188
- participantRunner,
189
- onReadError: (agentId, error) => {
190
- if (!(error instanceof Error && error.name === 'AbortError')) {
191
- aiLogger.error`UI message read failed for team-think participant ${agentId}: ${error}`
192
- }
193
- },
194
+ return createConsultTeamToolSdk({
195
+ agentConfig,
196
+ historyMessages: params.historyMessages,
197
+ latestUserMessageId: params.latestUserMessageId,
198
+ availableUploads: params.availableUploads,
199
+ defaultRepoSectionsByAgent: params.defaultRepoSectionsByAgent,
200
+ systemWorkspaceDetails: params.systemWorkspaceDetails,
201
+ getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
202
+ retrievedKnowledgeSection: params.retrievedKnowledgeSection,
203
+ displayNamesById: agentIdentityOverrides.displayNamesById,
204
+ abortSignal: params.abortSignal,
205
+ participantRunner,
206
+ onReadError: (agentId, error) => {
207
+ if (!(error instanceof Error && error.name === 'AbortError')) {
208
+ aiLogger.error`UI message read failed for team-think participant ${agentId}: ${error}`
209
+ }
210
+ },
211
+ })
194
212
  })
195
213
  }
@@ -1,24 +1,33 @@
1
- import { Schema, Duration, Effect } from 'effect'
1
+ import { Cause, Duration, Effect, Exit, Schema } from 'effect'
2
2
 
3
3
  import { serverLogger } from '../config/logger'
4
4
  import { TimeoutError } from '../effect/errors'
5
- import { getErrorMessage } from './errors'
5
+ import { getErrorMessage, toError } from './errors'
6
6
 
7
7
  class TimedOperationError extends Schema.TaggedErrorClass<TimedOperationError>()('TimedOperationError', {
8
8
  operation: Schema.String,
9
9
  cause: Schema.Defect,
10
10
  }) {}
11
11
 
12
+ function isTimedOperationError(error: unknown): error is TimedOperationError {
13
+ return typeof error === 'object' && error !== null && '_tag' in error && error._tag === 'TimedOperationError'
14
+ }
15
+
12
16
  export function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {
13
17
  return Effect.runPromise(
14
18
  Effect.tryPromise({ try: () => promise, catch: (cause) => new TimedOperationError({ operation, cause }) }).pipe(
15
19
  Effect.timeout(Duration.millis(ms)),
16
20
  Effect.catchTag('TimeoutError', () => Effect.fail(new TimeoutError({ operation, ms }))),
17
- Effect.catchTag('TimedOperationError', (error) =>
18
- Effect.fail(error.cause instanceof Error ? error.cause : new Error(String(error.cause))),
19
- ),
21
+ Effect.exit,
20
22
  ),
21
- )
23
+ ).then((exit) => {
24
+ if (Exit.isSuccess(exit)) {
25
+ return exit.value
26
+ }
27
+
28
+ const error = Cause.squash(exit.cause)
29
+ throw isTimedOperationError(error) ? toError(error.cause) : error
30
+ })
22
31
  }
23
32
 
24
33
  export function createSafeEnqueue(logger: { warn: (message: string) => void }) {
@@ -1,5 +1,5 @@
1
1
  export { getErrorMessage } from '@lota-sdk/shared'
2
- import { Match } from 'effect'
2
+ import { Data, Match } from 'effect'
3
3
 
4
4
  import type { EffectError, ValidationIssue } from '../effect/errors'
5
5
  import { BaseServicePersistenceError, isEffectError } from '../effect/errors'
@@ -19,18 +19,16 @@ export interface AppErrorResponse {
19
19
  body: { error: { code: string; message: string } }
20
20
  }
21
21
 
22
- export type AppErrorLike = Error & { code: string; statusCode: number; toResponse?: () => AppErrorResponse }
22
+ export type AppErrorLike = Error & { code: string; statusCode: number; toResponse?: () => unknown }
23
23
 
24
- export class AppError extends Error {
24
+ export class AppError extends Data.Error<{
25
+ readonly message: string
25
26
  readonly code: string
26
27
  readonly statusCode: number
27
-
28
+ }> {
28
29
  constructor(message: string, code: string, statusCode: number) {
29
- super(message)
30
+ super({ message, code, statusCode })
30
31
  this.name = new.target.name
31
- this.code = code
32
- this.statusCode = statusCode
33
- Object.setPrototypeOf(this, new.target.prototype)
34
32
  }
35
33
 
36
34
  toResponse(): AppErrorResponse {
@@ -99,16 +97,30 @@ export function isAppErrorLike(error: unknown): error is AppErrorLike {
99
97
  return typeof candidate.code === 'string' && typeof candidate.statusCode === 'number'
100
98
  }
101
99
 
100
+ function isAppErrorResponse(value: unknown): value is AppErrorResponse {
101
+ if (typeof value !== 'object' || value === null) {
102
+ return false
103
+ }
104
+
105
+ const candidate = value as {
106
+ status?: unknown
107
+ body?: { error?: { code?: unknown; message?: unknown } | null } | null
108
+ }
109
+ return (
110
+ typeof candidate.status === 'number' &&
111
+ typeof candidate.body === 'object' &&
112
+ candidate.body !== null &&
113
+ typeof candidate.body.error === 'object' &&
114
+ candidate.body.error !== null &&
115
+ typeof candidate.body.error.code === 'string' &&
116
+ typeof candidate.body.error.message === 'string'
117
+ )
118
+ }
119
+
102
120
  export function toAppErrorResponse(error: AppErrorLike): AppErrorResponse {
103
121
  if (typeof error.toResponse === 'function') {
104
122
  const response = error.toResponse()
105
- if (
106
- response &&
107
- typeof response === 'object' &&
108
- typeof response.status === 'number' &&
109
- response.body &&
110
- typeof response.body === 'object'
111
- ) {
123
+ if (isAppErrorResponse(response)) {
112
124
  return response
113
125
  }
114
126
  }
@@ -1,14 +1,12 @@
1
- import { Config, ConfigProvider, Schema, Effect, Layer, ManagedRuntime, Redacted } from 'effect'
1
+ import { ConfigProvider, Option, Schema, Effect, Layer, ManagedRuntime, Redacted } from 'effect'
2
2
 
3
3
  import { AiGatewayLive } from '../ai-gateway/ai-gateway'
4
4
  import { EmbeddingCacheLive } from '../ai/embedding-cache'
5
- import { DEFAULT_AI_GATEWAY_URL } from '../config/constants'
6
- import { configureLotaLogger, resolveLotaLogLevel, serverLogger } from '../config/logger'
5
+ import { serverLogger } from '../config/logger'
7
6
  import { connectWithStartupRetry, waitForDatabaseBootstrap } from '../db/startup'
8
- import { buildWorkerInfrastructureLayer, setLotaSdkRuntime } from '../effect'
9
- import { DatabaseServiceTag } from '../effect/services'
10
- import { parseLotaRuntimeConfig, parseWorkerBootstrapEnv } from '../runtime/runtime-config'
11
- import { getConfiguredPluginDatabaseConnector } from '../runtime/runtime-extensions'
7
+ import { buildWorkerInfrastructureLayer } from '../effect'
8
+ import { DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
9
+ import { lotaRuntimeEnvConfig, parseLotaRuntimeConfig, parseWorkerBootstrapEnv } from '../runtime/runtime-config'
12
10
  import { FirecrawlLive } from '../tools/firecrawl-client'
13
11
  import { getErrorMessage } from '../utils/errors'
14
12
 
@@ -28,27 +26,8 @@ function toSandboxedWorkerBootstrapError(
28
26
  return new SandboxedWorkerBootstrapError({ stage, message: getErrorMessage(cause), cause })
29
27
  }
30
28
 
31
- const workerConfig = Config.all({
32
- surrealdbUrl: Config.string('SURREALDB_URL'),
33
- surrealdbNamespace: Config.string('SURREALDB_NAMESPACE'),
34
- surrealdbUser: Config.string('SURREALDB_USER'),
35
- surrealdbPassword: Config.redacted('SURREALDB_PASSWORD'),
36
- redisUrl: Config.string('REDIS_URL'),
37
- aiGatewayUrl: Config.string('AI_GATEWAY_URL').pipe(Config.withDefault(DEFAULT_AI_GATEWAY_URL)),
38
- aiGatewayKey: Config.redacted('AI_GATEWAY_KEY'),
39
- openRouterApiKey: Config.redacted('OPENROUTER_API_KEY'),
40
- s3Endpoint: Config.string('S3_ENDPOINT'),
41
- s3Bucket: Config.string('S3_BUCKET'),
42
- s3Region: Config.string('S3_REGION').pipe(Config.withDefault('garage')),
43
- s3AccessKeyId: Config.redacted('S3_ACCESS_KEY_ID'),
44
- s3SecretAccessKey: Config.redacted('S3_SECRET_ACCESS_KEY'),
45
- attachmentUrlExpiresIn: Config.string('ATTACHMENT_URL_EXPIRES_IN').pipe(Config.withDefault('1800')),
46
- firecrawlApiKey: Config.redacted('FIRECRAWL_API_KEY'),
47
- firecrawlApiBaseUrl: Config.string('FIRECRAWL_API_BASE_URL').pipe(Config.option),
48
- })
49
-
50
29
  function buildSandboxedWorkerRuntimeConfigEffect() {
51
- return workerConfig
30
+ return lotaRuntimeEnvConfig
52
31
  .parse(ConfigProvider.fromEnv())
53
32
  .pipe(
54
33
  Effect.map((env) =>
@@ -63,7 +42,9 @@ function buildSandboxedWorkerRuntimeConfigEffect() {
63
42
  aiGateway: {
64
43
  url: env.aiGatewayUrl,
65
44
  key: Redacted.value(env.aiGatewayKey),
66
- openRouterApiKey: Redacted.value(env.openRouterApiKey),
45
+ ...(Option.isSome(env.openRouterApiKey)
46
+ ? { openRouterApiKey: Redacted.value(env.openRouterApiKey.value) }
47
+ : {}),
67
48
  },
68
49
  s3: {
69
50
  endpoint: env.s3Endpoint,
@@ -75,7 +56,7 @@ function buildSandboxedWorkerRuntimeConfigEffect() {
75
56
  },
76
57
  firecrawl: {
77
58
  apiKey: Redacted.value(env.firecrawlApiKey),
78
- ...(env.firecrawlApiBaseUrl._tag === 'Some' ? { apiBaseUrl: env.firecrawlApiBaseUrl.value } : {}),
59
+ ...(Option.isSome(env.firecrawlApiBaseUrl) ? { apiBaseUrl: env.firecrawlApiBaseUrl.value } : {}),
79
60
  },
80
61
  agents: {
81
62
  roster: ['worker'],
@@ -91,23 +72,18 @@ function buildSandboxedWorkerRuntimeConfigEffect() {
91
72
  // eslint-disable-next-line typescript-eslint/no-explicit-any -- wildcard for host-provided ManagedRuntime
92
73
  type WorkerManagedRuntime = ManagedRuntime.ManagedRuntime<any, any>
93
74
 
94
- let sandboxedWorkerRuntimePromise: Promise<WorkerManagedRuntime> | null = null
95
- let sandboxedWorkerRuntime: WorkerManagedRuntime | null = null
96
- let sandboxedWorkerRuntimeSetupPromise: Promise<WorkerManagedRuntime> | null = null
75
+ let sandboxedWorkerSetupPromise: Promise<WorkerManagedRuntime> | null = null
76
+ let sandboxedWorkerInitPromise: Promise<WorkerManagedRuntime> | null = null
97
77
 
98
78
  function ensureSandboxedWorkerRuntimeConfigured(): Promise<WorkerManagedRuntime> {
99
- if (sandboxedWorkerRuntime) {
100
- return Promise.resolve(sandboxedWorkerRuntime)
101
- }
102
-
103
- if (sandboxedWorkerRuntimeSetupPromise) {
104
- return sandboxedWorkerRuntimeSetupPromise
79
+ if (sandboxedWorkerSetupPromise) {
80
+ return sandboxedWorkerSetupPromise
105
81
  }
106
82
 
107
- const setupPromise = Effect.runPromise(
83
+ // Assign before the async kicks off so concurrent callers observe the in-flight promise.
84
+ sandboxedWorkerSetupPromise = Effect.runPromise(
108
85
  Effect.gen(function* () {
109
86
  const runtimeConfig = yield* buildSandboxedWorkerRuntimeConfigEffect()
110
- yield* Effect.sync(() => configureLotaLogger(resolveLotaLogLevel(Bun.env.LOG_LEVEL) ?? 'info'))
111
87
 
112
88
  const infrastructureLayer = buildWorkerInfrastructureLayer(runtimeConfig)
113
89
 
@@ -117,27 +93,23 @@ function ensureSandboxedWorkerRuntimeConfigured(): Promise<WorkerManagedRuntime>
117
93
 
118
94
  const managedRuntime = ManagedRuntime.make(fullLayer)
119
95
 
120
- yield* Effect.sync(() => {
121
- setLotaSdkRuntime(managedRuntime)
122
- sandboxedWorkerRuntime = managedRuntime
123
- })
124
-
125
96
  return managedRuntime
126
97
  }),
127
- ).finally(() => {
128
- sandboxedWorkerRuntimeSetupPromise = null
98
+ ).catch((error) => {
99
+ sandboxedWorkerSetupPromise = null
100
+ throw error
129
101
  })
130
102
 
131
- sandboxedWorkerRuntimeSetupPromise = setupPromise
132
- return setupPromise
103
+ return sandboxedWorkerSetupPromise
133
104
  }
134
105
 
135
106
  export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime> {
136
- if (sandboxedWorkerRuntimePromise) {
137
- return sandboxedWorkerRuntimePromise
107
+ if (sandboxedWorkerInitPromise) {
108
+ return sandboxedWorkerInitPromise
138
109
  }
139
110
 
140
- const initPromise = Effect.runPromise(
111
+ // Assign before the async kicks off so concurrent callers observe the in-flight promise.
112
+ sandboxedWorkerInitPromise = Effect.runPromise(
141
113
  Effect.gen(function* () {
142
114
  const env = parseWorkerBootstrapEnv(Bun.env)
143
115
  const runtime = yield* Effect.tryPromise({
@@ -159,11 +131,16 @@ export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime
159
131
  catch: (error) => toSandboxedWorkerBootstrapError('connect-db', error),
160
132
  })
161
133
 
134
+ const runtimeAdapters = yield* Effect.tryPromise({
135
+ try: () => runtime.runPromise(Effect.service(RuntimeAdaptersServiceTag)),
136
+ catch: (error) => toSandboxedWorkerBootstrapError('initialize', error),
137
+ })
138
+
162
139
  yield* Effect.tryPromise({
163
140
  try: () =>
164
141
  connectWithStartupRetry({
165
142
  connect: () => {
166
- const connectPluginRuntimeDatabases = getConfiguredPluginDatabaseConnector()
143
+ const connectPluginRuntimeDatabases = runtimeAdapters.connectPluginDatabases
167
144
  return connectPluginRuntimeDatabases ? connectPluginRuntimeDatabases() : Promise.resolve()
168
145
  },
169
146
  label: 'sandboxed worker plugin database runtime',
@@ -186,11 +163,10 @@ export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime
186
163
 
187
164
  return runtime
188
165
  }),
189
- )
190
-
191
- sandboxedWorkerRuntimePromise = initPromise
192
- return initPromise.catch((error) => {
193
- sandboxedWorkerRuntimePromise = null
166
+ ).catch((error) => {
167
+ sandboxedWorkerInitPromise = null
194
168
  throw error
195
169
  })
170
+
171
+ return sandboxedWorkerInitPromise
196
172
  }
@@ -11,6 +11,7 @@ import { TABLES } from '../db/tables'
11
11
  import { effectTryPromise } from '../effect/helpers'
12
12
  import { DatabaseServiceTag } from '../effect/services'
13
13
  import type { MemoryConsolidationJob } from '../queues/memory-consolidation.queue'
14
+ import { QueueJobServiceTag } from '../services/queue-job.service'
14
15
  import { nowDate, unsafeDateFrom } from '../utils/date-time'
15
16
  import { getErrorMessage } from '../utils/errors'
16
17
  import { initializeSandboxedWorkerRuntime } from './bootstrap'
@@ -59,6 +60,8 @@ const memoryConsolidationDatabaseService: SurrealDBService = await runtime.runPr
59
60
  Effect.service(DatabaseServiceTag),
60
61
  )
61
62
 
63
+ const memoryConsolidationQueueJobService = await runtime.runPromise(Effect.service(QueueJobServiceTag))
64
+
62
65
  function db(): SurrealDBService {
63
66
  return memoryConsolidationDatabaseService
64
67
  }
@@ -395,4 +398,4 @@ const handler = (job: SandboxedJob<MemoryConsolidationJob>) =>
395
398
  ),
396
399
  )
397
400
 
398
- export default createTracedWorkerProcessor('memory-consolidation', handler)
401
+ export default createTracedWorkerProcessor('memory-consolidation', handler, memoryConsolidationQueueJobService)
@@ -4,10 +4,17 @@ import type { Context } from 'effect'
4
4
 
5
5
  import { serverLogger } from '../config/logger'
6
6
  import { effectTryPromise } from '../effect/helpers'
7
- import { DatabaseServiceTag, RuntimeAdaptersServiceTag, RuntimeConfigServiceTag } from '../effect/services'
7
+ import {
8
+ AgentConfigServiceTag,
9
+ DatabaseServiceTag,
10
+ RuntimeAdaptersServiceTag,
11
+ RuntimeConfigServiceTag,
12
+ } from '../effect/services'
8
13
  import type { OrganizationLearningJob } from '../queues/organization-learning.queue'
14
+ import { LotaQueuesServiceTag } from '../queues/queues.service'
9
15
  import { LearnedSkillServiceTag } from '../services/learned-skill.service'
10
16
  import { MemoryServiceTag } from '../services/memory/memory.service'
17
+ import { QueueJobServiceTag } from '../services/queue-job.service'
11
18
  import { SocialChatHistoryServiceTag } from '../services/social-chat-history.service'
12
19
  import { initializeSandboxedWorkerRuntime } from './bootstrap'
13
20
  import { runRegularChatMemoryDigest } from './regular-chat-memory-digest.runner'
@@ -18,15 +25,20 @@ import { toSandboxedWorkerError } from './utils/sandbox-error'
18
25
  import { createTracedWorkerProcessor } from './worker-utils'
19
26
 
20
27
  const runtime = await initializeSandboxedWorkerRuntime()
21
- const resolve = async <I, T>(tag: Context.Key<I, T>): Promise<T> => await runtime.runPromise(Effect.service(tag))
28
+ const resolve = <I, T>(tag: Context.Key<I, T>): Promise<T> => runtime.runPromise(Effect.service(tag))
29
+ const agentConfig = await resolve(AgentConfigServiceTag)
30
+ const queues = await resolve(LotaQueuesServiceTag)
22
31
  const regularChatDigestServices: RegularChatDigestServices = {
32
+ agentConfig,
23
33
  databaseService: await resolve(DatabaseServiceTag),
24
34
  memoryService: await resolve(MemoryServiceTag),
25
35
  socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
26
36
  runtimeAdapters: await resolve(RuntimeAdaptersServiceTag),
37
+ organizationLearningQueue: queues.organizationLearning,
27
38
  }
28
39
  const workerRuntimeConfig = await resolve(RuntimeConfigServiceTag)
29
40
  const skillExtractionServices: SkillExtractionServices = {
41
+ agentConfig,
30
42
  databaseService: await resolve(DatabaseServiceTag),
31
43
  learnedSkillService: await resolve(LearnedSkillServiceTag),
32
44
  socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
@@ -34,6 +46,7 @@ const skillExtractionServices: SkillExtractionServices = {
34
46
  embeddingModel: workerRuntimeConfig.aiGateway.embeddingModel,
35
47
  openRouterApiKey: workerRuntimeConfig.aiGateway.openRouterApiKey,
36
48
  }
49
+ const organizationLearningQueueJobService = await resolve(QueueJobServiceTag)
37
50
 
38
51
  // One sandboxed worker handles both organization-learning branches so the
39
52
  // queue can dispatch digest and skill jobs without separate worker images.
@@ -62,4 +75,4 @@ const handler = (job: SandboxedJob<OrganizationLearningJob>) =>
62
75
  ),
63
76
  )
64
77
 
65
- export default createTracedWorkerProcessor('organization-learning', handler)
78
+ export default createTracedWorkerProcessor('organization-learning', handler, organizationLearningQueueJobService)