@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
package/src/db/startup.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { recordIdSchema } from '@lota-sdk/shared'
2
- import { Duration, Effect, Schedule } from 'effect'
2
+ import { Duration, Effect, Match, Schedule } from 'effect'
3
3
  import { BoundQuery, RecordId } from 'surrealdb'
4
4
  import { z } from 'zod'
5
5
 
@@ -146,35 +146,46 @@ function waitForDatabaseBootstrapEffectInternal(params: {
146
146
  }),
147
147
  )
148
148
 
149
+ const logFailure = Match.type<BootstrapWaitFailure>().pipe(
150
+ Match.tag('schema-not-ready', (failure) => {
151
+ params.logger?.info?.(
152
+ `Waiting for ${params.label} schema readiness (${attempt}, expected=${expectedFingerprint}, current=${failure.currentFingerprint})`,
153
+ )
154
+ }),
155
+ Match.tag('read-failed', (failure) => {
156
+ params.logger?.warn?.(
157
+ `Waiting for ${params.label} schema readiness (${attempt}): ${getErrorMessage(failure.cause)}`,
158
+ )
159
+ }),
160
+ Match.exhaustive,
161
+ )
162
+
163
+ const toTimeoutError = Match.type<BootstrapWaitFailure>().pipe(
164
+ Match.tag(
165
+ 'schema-not-ready',
166
+ () => new DatabaseError({ message: `Timed out waiting for ${params.label} schema readiness` }),
167
+ ),
168
+ Match.tag(
169
+ 'read-failed',
170
+ (failure) =>
171
+ new DatabaseError({ message: `Timed out waiting for ${params.label} schema readiness`, cause: failure.cause }),
172
+ ),
173
+ Match.exhaustive,
174
+ )
175
+
149
176
  return waitForExpectedFingerprint.pipe(
150
177
  Effect.tapError((failure) =>
151
178
  Effect.sync(() => {
152
179
  attempt++
153
180
  if (!shouldLogRetry(attempt)) return
154
-
155
- if (failure._tag === 'schema-not-ready') {
156
- params.logger?.info?.(
157
- `Waiting for ${params.label} schema readiness (${attempt}, expected=${expectedFingerprint}, current=${failure.currentFingerprint})`,
158
- )
159
- return
160
- }
161
-
162
- params.logger?.warn?.(
163
- `Waiting for ${params.label} schema readiness (${attempt}): ${getErrorMessage(failure.cause)}`,
164
- )
181
+ logFailure(failure)
165
182
  }),
166
183
  ),
167
184
  Effect.retry({
168
185
  times: Math.max(0, Math.ceil(maxWaitMs / retryDelayMs) - 1),
169
186
  schedule: Schedule.fixed(Duration.millis(retryDelayMs)),
170
187
  }),
171
- Effect.mapError(
172
- (failure) =>
173
- new DatabaseError({
174
- message: `Timed out waiting for ${params.label} schema readiness`,
175
- ...(failure._tag === 'read-failed' ? { cause: failure.cause } : {}),
176
- }),
177
- ),
188
+ Effect.mapError(toTimeoutError),
178
189
  )
179
190
  }
180
191
 
@@ -1,9 +1,5 @@
1
1
  import { Effect } from 'effect'
2
2
 
3
- const awaitableEffectMarker = Symbol('AwaitableEffect')
4
-
5
- type AwaitableEffectState<A> = { promise?: Promise<A> }
6
-
7
3
  export type AwaitableEffect<A, E> = Effect.Effect<A, E, never> & Promise<A>
8
4
 
9
5
  export type AwaitableValue<T> = T extends (...args: infer TArgs) => Effect.Effect<infer A, infer E, never>
@@ -22,62 +18,75 @@ export type MaybeAwaitableService<T extends object> = T | AwaitableService<T>
22
18
 
23
19
  type AwaitableEffectOptions = { runPromise?: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A> }
24
20
 
21
+ type Callable = (...args: Array<unknown>) => unknown
22
+
23
+ function isCallable(value: unknown): value is Callable {
24
+ return typeof value === 'function'
25
+ }
26
+
27
+ function isRuntimeEffect(value: unknown): value is Effect.Effect<unknown, never, never> {
28
+ return Effect.isEffect(value)
29
+ }
30
+
31
+ /**
32
+ * Wraps an Effect so it can be `await`-ed as if it were a Promise, while still
33
+ * passing `Effect.isEffect` checks and exposing the underlying Effect properties.
34
+ *
35
+ * Implementation: returns a `Proxy` around the Effect that only intercepts the
36
+ * PromiseLike methods (`then`/`catch`/`finally`) plus `Symbol.toStringTag`. The
37
+ * Promise is created lazily on first `await` and cached for the wrapper's
38
+ * lifetime so repeated awaits share a result. The original Effect object is
39
+ * never mutated.
40
+ */
25
41
  export function toAwaitableEffect<A, E>(
26
42
  effect: Effect.Effect<A, E, never>,
27
43
  options?: AwaitableEffectOptions,
28
44
  ): AwaitableEffect<A, E> {
29
- const existing = effect as AwaitableEffect<A, E> & { [awaitableEffectMarker]?: AwaitableEffectState<A> }
45
+ const runPromise = options?.runPromise ?? Effect.runPromise
46
+ let cachedPromise: Promise<A> | undefined
47
+ const getPromise = (): Promise<A> => (cachedPromise ??= runPromise(effect))
30
48
 
31
- if (existing[awaitableEffectMarker]) {
32
- return existing
33
- }
49
+ const then: Promise<A>['then'] = (onFulfilled, onRejected) => getPromise().then(onFulfilled, onRejected)
50
+ const catchMethod: Promise<A>['catch'] = (onRejected) => getPromise().catch(onRejected)
51
+ const finallyMethod: Promise<A>['finally'] = (onFinally) => getPromise().finally(onFinally)
34
52
 
35
- const state: AwaitableEffectState<A> = {}
36
- const runPromise = options?.runPromise ?? Effect.runPromise
37
- const ensurePromise = () => (state.promise ??= runPromise(effect))
38
- const candidate = Object.create(
39
- Object.getPrototypeOf(effect),
40
- Object.getOwnPropertyDescriptors(effect),
41
- ) as AwaitableEffect<A, E> & { [awaitableEffectMarker]?: AwaitableEffectState<A> }
42
-
43
- const then: Promise<A>['then'] = (onfulfilled, onrejected) => ensurePromise().then(onfulfilled, onrejected)
44
- const catchMethod: Promise<A>['catch'] = (onrejected) => ensurePromise().catch(onrejected)
45
- const finallyMethod: Promise<A>['finally'] = (onfinally) => ensurePromise().finally(onfinally)
46
-
47
- void Object.defineProperties(candidate, {
48
- then: { value: then, configurable: true },
49
- catch: { value: catchMethod, configurable: true },
50
- finally: { value: finallyMethod, configurable: true },
51
- [Symbol.toStringTag]: { value: 'Promise', configurable: true },
52
- [awaitableEffectMarker]: { value: state },
53
- })
54
-
55
- return candidate
53
+ return new Proxy(effect as object, {
54
+ get(target, property, receiver) {
55
+ if (property === 'then') return then
56
+ if (property === 'catch') return catchMethod
57
+ if (property === 'finally') return finallyMethod
58
+ if (property === Symbol.toStringTag) return 'Promise'
59
+ return Reflect.get(target, property, receiver) as unknown
60
+ },
61
+ }) as AwaitableEffect<A, E>
56
62
  }
57
63
 
64
+ /**
65
+ * Returns a proxy over a service whose Effect-returning methods are
66
+ * transparently wrapped with `toAwaitableEffect`. Non-Effect return values
67
+ * pass through untouched.
68
+ */
58
69
  export function toAwaitableService<T extends object>(
59
70
  service: T,
60
71
  options?: AwaitableEffectOptions,
61
72
  ): AwaitableService<T> {
62
- const wrappedMethods = new Map<PropertyKey, unknown>()
73
+ const wrappedMethods = new Map<PropertyKey, Callable>()
63
74
 
64
75
  return new Proxy(service, {
65
76
  get(target, property, receiver) {
66
77
  const value = Reflect.get(target, property, receiver)
67
- if (typeof value !== 'function') {
78
+ if (!isCallable(value)) {
68
79
  return value
69
80
  }
70
81
 
71
82
  const existing = wrappedMethods.get(property)
72
- if (existing !== undefined) {
83
+ if (existing) {
73
84
  return existing
74
85
  }
75
86
 
76
- const wrapped = (...args: unknown[]) => {
77
- const result = Reflect.apply(value, target, args)
78
- return Effect.isEffect(result)
79
- ? toAwaitableEffect(result as Effect.Effect<unknown, unknown, never>, options)
80
- : result
87
+ const wrapped: Callable = (...args) => {
88
+ const result = value.apply(target, args)
89
+ return isRuntimeEffect(result) ? toAwaitableEffect(result, options) : result
81
90
  }
82
91
 
83
92
  wrappedMethods.set(property, wrapped)
@@ -18,6 +18,12 @@ type PromiseEffectEvaluator<A, E = never, R = never> =
18
18
  | (() => PromiseLike<A> | Effect.Effect<A, E, R>)
19
19
  | ((signal: AbortSignal) => PromiseLike<A> | Effect.Effect<A, E, R>)
20
20
 
21
+ function invokePromiseEffectEvaluator<A, E, R>(
22
+ evaluate: PromiseEffectEvaluator<A, E, R>,
23
+ ): PromiseLike<A> | Effect.Effect<A, E, R> {
24
+ return (evaluate as () => PromiseLike<A> | Effect.Effect<A, E, R>)()
25
+ }
26
+
21
27
  export function effectTryPromise<A, E = never, R = never>(
22
28
  evaluate: PromiseEffectEvaluator<A, E, R>,
23
29
  ): Effect.Effect<A, Cause.UnknownError, R>
@@ -31,7 +37,7 @@ export function effectTryPromise<A, E1, E2, R>(
31
37
  ): Effect.Effect<A, E2 | Cause.UnknownError, R> {
32
38
  return Effect.suspend(() => {
33
39
  try {
34
- const value = (evaluate as () => PromiseLike<A> | Effect.Effect<A, E1, R>)()
40
+ const value = invokePromiseEffectEvaluator(evaluate)
35
41
  if (Effect.isEffect(value)) {
36
42
  if (onError) {
37
43
  return value.pipe(Effect.mapError((cause) => onError(cause)))
@@ -49,13 +55,13 @@ export function effectTryPromise<A, E1, E2, R>(
49
55
  })
50
56
  }
51
57
 
52
- export function effectTryServicePromise<A>(
53
- evaluate: PromiseEffectEvaluator<A, unknown, never>,
58
+ export function effectTryServicePromise<A, E = never, R = never>(
59
+ evaluate: PromiseEffectEvaluator<A, E, R>,
54
60
  message: string,
55
- ): Effect.Effect<A, ServiceError, never> {
61
+ ): Effect.Effect<A, ServiceError, R> {
56
62
  return Effect.suspend(() => {
57
63
  try {
58
- const value = (evaluate as () => PromiseLike<A> | Effect.Effect<A, unknown, never>)()
64
+ const value = invokePromiseEffectEvaluator(evaluate)
59
65
  if (Effect.isEffect(value)) {
60
66
  return value.pipe(Effect.mapError((cause) => new ServiceError({ message, cause })))
61
67
  }
@@ -74,6 +80,16 @@ export function makeEffectTryPromiseWithMessage<E>(onError: (message: string, ca
74
80
  ): Effect.Effect<A, E, R> => effectTryPromise(evaluate, (cause) => onError(message, cause))
75
81
  }
76
82
 
83
+ export function makeEffectTryPromiseWithOperation<E>(
84
+ onError: (operation: string, message: string, cause: unknown) => E,
85
+ ) {
86
+ return <A, E1 = never, R = never>(
87
+ operation: string,
88
+ message: string,
89
+ evaluate: PromiseEffectEvaluator<A, E1, R>,
90
+ ): Effect.Effect<A, E, R> => effectTryPromise(evaluate, (cause) => onError(operation, message, cause))
91
+ }
92
+
77
93
  export function effectTryMaybeAsync<A>(evaluate: () => A | PromiseLike<A>): Effect.Effect<A, Cause.UnknownError, never>
78
94
  export function effectTryMaybeAsync<A, E>(
79
95
  evaluate: () => A | PromiseLike<A>,
@@ -96,3 +112,12 @@ export function effectTryMaybeAsync<A, E>(
96
112
  }
97
113
  })
98
114
  }
115
+
116
+ export function iterateEffect<A, E, R>(
117
+ initial: A,
118
+ options: { while: (state: A) => boolean; body: (state: A) => Effect.Effect<A, E, R> },
119
+ ): Effect.Effect<A, E, R> {
120
+ const step = (state: A): Effect.Effect<A, E, R> =>
121
+ options.while(state) ? options.body(state).pipe(Effect.flatMap(step)) : Effect.succeed(state)
122
+ return Effect.suspend(() => step(initial))
123
+ }
@@ -6,17 +6,19 @@ export * from './services'
6
6
  export * from './zod'
7
7
  export {
8
8
  ActiveThreadRunConflictError,
9
+ AiGenerationError,
10
+ BaseServicePersistenceError,
9
11
  ConfigurationError,
10
12
  ConflictError,
11
13
  DatabaseError,
14
+ ForbiddenError,
12
15
  LockAcquisitionError,
13
16
  LockLostError,
14
17
  RedisError,
15
- isEffectError,
16
- ThreadTurnError as EffectThreadTurnError,
18
+ ServiceError,
19
+ ThreadTurnError,
17
20
  TimeoutError,
18
- ValidationError as EffectValidationError,
19
- BadRequestError as EffectBadRequestError,
20
- NotFoundError as EffectNotFoundError,
21
+ ValidationError,
22
+ isEffectError,
21
23
  } from './errors'
22
24
  export type { EffectError, ValidationIssue } from './errors'
@@ -1,5 +1,8 @@
1
1
  import type { ToolSet } from 'ai'
2
2
  import { Effect, Layer, Logger, References } from 'effect'
3
+ import { DevTools } from 'effect/unstable/devtools'
4
+ import { FetchHttpClient } from 'effect/unstable/http'
5
+ import { Otlp } from 'effect/unstable/observability'
3
6
 
4
7
  import type { CoreThreadProfile } from '../config/agent-defaults'
5
8
  import { resolveAgentConfig, resolveAgentFactoryConfig } from '../config/agent-defaults'
@@ -12,14 +15,13 @@ import { LOTA_SDK_DATABASE_NAME } from '../db/sdk-database'
12
15
  import type { SurrealDatabaseConfig } from '../db/service'
13
16
  import { SurrealDBService } from '../db/service'
14
17
  import type { CreateRedisConnectionManagerOptions } from '../redis/connection'
15
- import { createRedisConnectionManager } from '../redis/connection'
18
+ import { makeRedisConnectionManager } from '../redis/connection'
16
19
  import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
17
20
  import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from '../runtime/runtime-extensions'
18
21
  import type { LotaRuntimeWorkerExtensions } from '../runtime/runtime-worker-registry'
19
22
  import { QueueJobServiceLive } from '../services/queue-job.service'
20
23
  import { getErrorMessage } from '../utils/errors'
21
- import { DatabaseError, RedisError } from './errors'
22
- import { effectTryServicePromise } from './helpers'
24
+ import { DatabaseError } from './errors'
23
25
  import {
24
26
  AppLoggerTag,
25
27
  AgentConfigServiceTag,
@@ -61,36 +63,23 @@ export function DatabaseLive(config: SurrealDatabaseConfig) {
61
63
  }
62
64
 
63
65
  export function RedisLive(config: CreateRedisConnectionManagerOptions) {
64
- return Layer.effect(
65
- RedisServiceTag,
66
- Effect.acquireRelease(
67
- Effect.try({
68
- try: () => createRedisConnectionManager(config),
69
- catch: (error) =>
70
- new RedisError({
71
- message: `Failed to create Redis connection manager: ${getErrorMessage(error)}`,
72
- cause: error,
73
- }),
74
- }),
75
- (redisManager) =>
76
- Effect.ignore(
77
- effectTryServicePromise(
78
- () => redisManager.closeConnection(),
79
- 'Failed to close Redis connection manager',
80
- ).pipe(
81
- Effect.tapError((error) =>
82
- Effect.logWarning(`Failed to clean up Redis connection manager: ${getErrorMessage(error)}`),
83
- ),
84
- ),
85
- ),
86
- ),
87
- )
66
+ return Layer.effect(RedisServiceTag, makeRedisConnectionManager(config))
88
67
  }
89
68
 
90
69
  export function AppLoggerLive(level: LotaLogLevel = 'info') {
70
+ // Sequence `configureLotaLogger(level)` before `getLotaLoggers()` inside a
71
+ // single Effect.sync so the log-level mutation is observed when the loggers
72
+ // are assembled. `Layer.mergeAll` builds in parallel, so splitting these
73
+ // across two sub-layers is an ordering hazard.
74
+ const AppLoggersLayer = Layer.effect(
75
+ AppLoggerTag,
76
+ Effect.sync(() => {
77
+ configureLotaLogger(level)
78
+ return getLotaLoggers()
79
+ }),
80
+ )
91
81
  return Layer.mergeAll(
92
- Layer.effectDiscard(Effect.sync(() => configureLotaLogger(level))),
93
- Layer.succeed(AppLoggerTag, getLotaLoggers()),
82
+ AppLoggersLayer,
94
83
  Logger.layer([Logger.consolePretty()]),
95
84
  Layer.succeed(References.MinimumLogLevel, toEffectLogLevel(level)),
96
85
  )
@@ -143,12 +132,17 @@ export function RuntimeExtensionsLive(params: {
143
132
  // ── Shared Infrastructure Layer Builders ─────────────────────────────
144
133
 
145
134
  /**
146
- * Builds the full infrastructure layer used by `createLotaRuntime`.
147
- * Contains all config, database, redis, agent, thread, and extension layers.
135
+ * Common config-to-Layer conversions shared by the runtime and sandboxed
136
+ * worker infrastructure builders. Covers logging, runtime config, agents,
137
+ * threads, database, and redis. Agent display names are overridable for
138
+ * the socialChat agent injection flow.
148
139
  */
149
- export function buildInfrastructureLayer(
140
+ function buildBaseConfigLayers(
150
141
  runtimeConfig: ResolvedLotaRuntimeConfig,
151
- options: { resolvedAgentDisplayNames: Record<string, string>; observabilityLayer: Layer.Layer<never> },
142
+ options: {
143
+ resolvedAgentDisplayNames?: Record<string, string>
144
+ runtimeExtensions: Parameters<typeof RuntimeExtensionsLive>[0]
145
+ },
152
146
  ) {
153
147
  return Layer.mergeAll(
154
148
  AppLoggerLive(runtimeConfig.logging.level),
@@ -156,7 +150,7 @@ export function buildInfrastructureLayer(
156
150
  AgentConfigLive({
157
151
  roster: runtimeConfig.agents.roster,
158
152
  leadAgentId: runtimeConfig.agents.leadAgentId,
159
- displayNames: options.resolvedAgentDisplayNames,
153
+ displayNames: options.resolvedAgentDisplayNames ?? runtimeConfig.agents.displayNames,
160
154
  shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
161
155
  descriptions: runtimeConfig.agents.descriptions,
162
156
  routerModelId: runtimeConfig.agents.routerModelId,
@@ -170,12 +164,7 @@ export function buildInfrastructureLayer(
170
164
  pluginRuntime: runtimeConfig.pluginRuntime,
171
165
  }),
172
166
  ThreadConfigLive({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads }),
173
- RuntimeExtensionsLive({
174
- adapters: runtimeConfig.runtimeAdapters,
175
- turnHooks: runtimeConfig.turnHooks,
176
- toolProviders: runtimeConfig.toolProviders ?? {},
177
- extraWorkers: runtimeConfig.extraWorkers,
178
- }),
167
+ RuntimeExtensionsLive(options.runtimeExtensions),
179
168
  DatabaseLive({
180
169
  url: runtimeConfig.database.url,
181
170
  namespace: runtimeConfig.database.namespace,
@@ -184,45 +173,66 @@ export function buildInfrastructureLayer(
184
173
  password: runtimeConfig.database.password,
185
174
  }),
186
175
  RedisLive({ url: runtimeConfig.redis.url }),
187
- options.observabilityLayer,
188
176
  )
189
177
  }
190
178
 
191
179
  /**
192
- * Builds the minimal infrastructure layer used by sandboxed workers.
193
- * Omits observability and uses empty runtime extensions.
180
+ * Builds the observability layer (OTLP + DevTools) from a runtime config.
181
+ * Returns an empty layer when no observability endpoints are configured.
194
182
  */
195
- export function buildWorkerInfrastructureLayer(runtimeConfig: ResolvedLotaRuntimeConfig) {
196
- const infrastructureLayer = Layer.mergeAll(
197
- AppLoggerLive(runtimeConfig.logging.level),
198
- RuntimeConfigLive(runtimeConfig),
199
- AgentConfigLive({
200
- roster: runtimeConfig.agents.roster,
201
- leadAgentId: runtimeConfig.agents.leadAgentId,
202
- displayNames: runtimeConfig.agents.displayNames,
203
- shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
204
- descriptions: runtimeConfig.agents.descriptions,
205
- routerModelId: runtimeConfig.agents.routerModelId,
206
- teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
207
- getCoreThreadProfile: runtimeConfig.agents.getCoreThreadProfile,
208
- }),
209
- AgentFactoryLive({
210
- createAgent: runtimeConfig.agents.createAgent,
211
- buildAgentTools: runtimeConfig.agents.buildAgentTools,
212
- getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
213
- pluginRuntime: runtimeConfig.pluginRuntime,
214
- }),
215
- ThreadConfigLive({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads }),
216
- RuntimeExtensionsLive({}),
217
- DatabaseLive({
218
- url: runtimeConfig.database.url,
219
- namespace: runtimeConfig.database.namespace,
220
- database: LOTA_SDK_DATABASE_NAME,
221
- username: runtimeConfig.database.username,
222
- password: runtimeConfig.database.password,
183
+ function buildObservabilityLayer(runtimeConfig: ResolvedLotaRuntimeConfig): Layer.Layer<never> {
184
+ let layer: Layer.Layer<never> = Layer.empty
185
+ const observability = runtimeConfig.observability
186
+
187
+ if (observability.otlpBaseUrl) {
188
+ layer = Layer.merge(
189
+ layer,
190
+ Otlp.layerJson({
191
+ baseUrl: observability.otlpBaseUrl,
192
+ resource: {
193
+ serviceName: observability.serviceName,
194
+ ...(observability.serviceVersion ? { serviceVersion: observability.serviceVersion } : {}),
195
+ },
196
+ ...(observability.otlpHeaders ? { headers: observability.otlpHeaders } : {}),
197
+ }).pipe(Layer.provide(FetchHttpClient.layer)),
198
+ )
199
+ }
200
+
201
+ if (observability.devToolsUrl && Bun.env.NODE_ENV !== 'production') {
202
+ layer = Layer.merge(layer, DevTools.layer(observability.devToolsUrl))
203
+ }
204
+
205
+ return layer
206
+ }
207
+
208
+ /**
209
+ * Builds the full infrastructure layer used by `createLotaRuntime`. Combines
210
+ * all config, database, redis, agent, thread, and extension layers with the
211
+ * observability layer computed from the runtime config.
212
+ */
213
+ export function buildInfrastructureLayer(
214
+ runtimeConfig: ResolvedLotaRuntimeConfig,
215
+ options: { resolvedAgentDisplayNames: Record<string, string> },
216
+ ) {
217
+ return Layer.mergeAll(
218
+ buildBaseConfigLayers(runtimeConfig, {
219
+ resolvedAgentDisplayNames: options.resolvedAgentDisplayNames,
220
+ runtimeExtensions: {
221
+ adapters: runtimeConfig.runtimeAdapters,
222
+ turnHooks: runtimeConfig.turnHooks,
223
+ toolProviders: runtimeConfig.toolProviders ?? {},
224
+ extraWorkers: runtimeConfig.extraWorkers,
225
+ },
223
226
  }),
224
- RedisLive({ url: runtimeConfig.redis.url }),
227
+ buildObservabilityLayer(runtimeConfig),
225
228
  )
229
+ }
226
230
 
231
+ /**
232
+ * Builds the minimal infrastructure layer used by sandboxed workers.
233
+ * Omits observability and uses empty runtime extensions.
234
+ */
235
+ export function buildWorkerInfrastructureLayer(runtimeConfig: ResolvedLotaRuntimeConfig) {
236
+ const infrastructureLayer = buildBaseConfigLayers(runtimeConfig, { runtimeExtensions: {} })
227
237
  return QueueJobServiceLive.pipe(Layer.provideMerge(infrastructureLayer))
228
238
  }
@@ -1,4 +1,5 @@
1
- import type { Effect, ManagedRuntime } from 'effect'
1
+ import { Effect } from 'effect'
2
+ import type { Context, ManagedRuntime } from 'effect'
2
3
 
3
4
  import { clearCurrentRuntime, getCurrentRuntime, getOptionalCurrentRuntime, setCurrentRuntime } from './runtime-ref'
4
5
 
@@ -19,12 +20,26 @@ export function getLotaSdkRuntime() {
19
20
  return getCurrentRuntime()
20
21
  }
21
22
 
23
+ export function resolveLotaService<I, T>(tag: Context.Key<I, T>): T {
24
+ return getCurrentRuntime().runSync(Effect.service(tag))
25
+ }
26
+
27
+ export function resolveOptionalLotaService<I, T>(tag: Context.Key<I, T>): T | null {
28
+ const runtime = getOptionalCurrentRuntime()
29
+ return runtime ? runtime.runSync(Effect.service(tag)) : null
30
+ }
31
+
32
+ export function runPromiseWithOptionalLotaRuntime<A, E>(effect: Effect.Effect<A, E, never>): Promise<A> {
33
+ const runtime = getOptionalCurrentRuntime()
34
+ return runtime ? runtime.runPromise(effect) : Effect.runPromise(effect)
35
+ }
36
+
22
37
  /**
23
38
  * Run an effect as a Promise using the current managed runtime's context.
24
39
  * The runtime must be initialized via `createLotaRuntime()` before any call.
25
40
  */
26
- export function runPromise<A, E>(
27
- effect: Effect.Effect<A, E, never>,
41
+ export function runPromise<A, E, R>(
42
+ effect: Effect.Effect<A, E, R>,
28
43
  options?: { readonly signal?: AbortSignal },
29
44
  ): Promise<A> {
30
45
  return getCurrentRuntime().runPromise(effect, options)
@@ -11,18 +11,22 @@ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
11
11
  import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from '../runtime/runtime-extensions'
12
12
  import type { LotaRuntimeWorkerExtensions } from '../runtime/runtime-worker-registry'
13
13
 
14
- export class DatabaseServiceTag extends Context.Service<DatabaseServiceTag, SurrealDBService>()('DatabaseService') {}
14
+ export class DatabaseServiceTag extends Context.Service<DatabaseServiceTag, SurrealDBService>()(
15
+ '@lota-sdk/core/DatabaseService',
16
+ ) {}
15
17
 
16
- export class RedisServiceTag extends Context.Service<RedisServiceTag, RedisConnectionManager>()('RedisService') {}
18
+ export class RedisServiceTag extends Context.Service<RedisServiceTag, RedisConnectionManager>()(
19
+ '@lota-sdk/core/RedisService',
20
+ ) {}
17
21
 
18
- export class AppLoggerTag extends Context.Service<AppLoggerTag, LotaLoggerSet>()('AppLogger') {}
22
+ export class AppLoggerTag extends Context.Service<AppLoggerTag, LotaLoggerSet>()('@lota-sdk/core/AppLogger') {}
19
23
 
20
24
  export class RuntimeConfigServiceTag extends Context.Service<RuntimeConfigServiceTag, ResolvedLotaRuntimeConfig>()(
21
- 'RuntimeConfigService',
25
+ '@lota-sdk/core/RuntimeConfigService',
22
26
  ) {}
23
27
 
24
28
  export class AgentConfigServiceTag extends Context.Service<AgentConfigServiceTag, ResolvedAgentConfig>()(
25
- 'AgentConfigService',
29
+ '@lota-sdk/core/AgentConfigService',
26
30
  ) {}
27
31
 
28
32
  export class AgentFactoryServiceTag extends Context.Service<
@@ -33,25 +37,25 @@ export class AgentFactoryServiceTag extends Context.Service<
33
37
  readonly getAgentRuntimeConfig: AgentRuntimeConfigProvider
34
38
  readonly pluginRuntime?: Record<string, unknown>
35
39
  }
36
- >()('AgentFactoryService') {}
40
+ >()('@lota-sdk/core/AgentFactoryService') {}
37
41
 
38
42
  export class ThreadConfigServiceTag extends Context.Service<ThreadConfigServiceTag, ResolvedThreadBootstrapConfig>()(
39
- 'ThreadConfigService',
43
+ '@lota-sdk/core/ThreadConfigService',
40
44
  ) {}
41
45
 
42
46
  export class RuntimeAdaptersServiceTag extends Context.Service<RuntimeAdaptersServiceTag, LotaRuntimeAdapters>()(
43
- 'RuntimeAdaptersService',
47
+ '@lota-sdk/core/RuntimeAdaptersService',
44
48
  ) {}
45
49
 
46
50
  export class TurnHooksServiceTag extends Context.Service<TurnHooksServiceTag, LotaRuntimeTurnHooks>()(
47
- 'TurnHooksService',
51
+ '@lota-sdk/core/TurnHooksService',
48
52
  ) {}
49
53
 
50
54
  export class ToolProvidersServiceTag extends Context.Service<ToolProvidersServiceTag, ToolSet>()(
51
- 'ToolProvidersService',
55
+ '@lota-sdk/core/ToolProvidersService',
52
56
  ) {}
53
57
 
54
58
  export class RuntimeWorkerExtensionsServiceTag extends Context.Service<
55
59
  RuntimeWorkerExtensionsServiceTag,
56
60
  LotaRuntimeWorkerExtensions
57
- >()('RuntimeWorkerExtensionsService') {}
61
+ >()('@lota-sdk/core/RuntimeWorkerExtensionsService') {}