@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
@@ -1,9 +1,9 @@
1
1
  import { ConsultTeamArgsSchema, withMessageCreatedAt } from '@lota-sdk/shared'
2
2
  import type { ChatMessage, ConsultTeamResultData } from '@lota-sdk/shared'
3
3
  import { convertToModelMessages, tool as createTool } from 'ai'
4
- import { Schema, Effect, Fiber, Queue, Ref } from 'effect'
4
+ import { Schema, Deferred, Effect, Exit, Fiber, Queue, Ref, Scope } from 'effect'
5
5
 
6
- import { getAgentDisplayNames, getTeamConsultParticipants } from '../../config/agent-defaults'
6
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
7
7
  import { nowEpochMillis } from '../../utils/date-time'
8
8
  import { createTimedAbortSignal } from '../agent-stream-helpers'
9
9
  import { buildModelInputMessagesWithUploadMetadata, buildReadableUploadMetadataText } from '../chat-attachments'
@@ -91,6 +91,7 @@ export interface TeamConsultationParticipantRunner {
91
91
  }
92
92
 
93
93
  export interface CreateConsultTeamToolParams {
94
+ agentConfig: ResolvedAgentConfig
94
95
  historyMessages: ChatMessage[]
95
96
  latestUserMessageId: string
96
97
  availableUploads: ReadableUploadMetadataLike[]
@@ -114,8 +115,6 @@ interface ConsultTeamStreamResources {
114
115
  workerFiber: Fiber.Fiber<unknown, unknown>
115
116
  }
116
117
 
117
- type TeamConsultationResourcesError = TeamConsultationError
118
-
119
118
  function createConsultTeamStreamResources(params: {
120
119
  task: string
121
120
  uploadMetadataText: string
@@ -272,14 +271,17 @@ function createConsultTeamStreamResources(params: {
272
271
  }
273
272
 
274
273
  class ConsultTeamSnapshotIterable implements AsyncIterableIterator<ConsultTeamResultData> {
275
- private readonly initPromise: Promise<ConsultTeamStreamResources>
274
+ private readonly scope: Scope.Closeable
275
+ private readonly resourcesDeferred: Deferred.Deferred<ConsultTeamStreamResources, TeamConsultationError>
276
+ private readonly buildEffect: Effect.Effect<ConsultTeamStreamResources, TeamConsultationError>
277
+ private initStarted = false
276
278
  private closed = false
277
279
  private completed = false
278
280
 
279
281
  constructor(params: CreateConsultTeamToolParams, task: string) {
280
- const teamConsultParticipants = getTeamConsultParticipants()
282
+ const teamConsultParticipants = params.agentConfig.teamConsultParticipants
281
283
  const uploadMetadataText = buildReadableUploadMetadataText(params.availableUploads)
282
- const agentDisplayNames = getAgentDisplayNames()
284
+ const agentDisplayNames = params.agentConfig.displayNames
283
285
  const resolveDisplayName = (agentId: string) => {
284
286
  if (params.displayNamesById && Object.hasOwn(params.displayNamesById, agentId)) {
285
287
  const override = params.displayNamesById[agentId]
@@ -291,38 +293,52 @@ class ConsultTeamSnapshotIterable implements AsyncIterableIterator<ConsultTeamRe
291
293
  return agentDisplayNames[agentId] ?? agentId
292
294
  }
293
295
 
294
- this.initPromise = Effect.runPromise(
295
- createConsultTeamStreamResources({
296
- task,
297
- uploadMetadataText,
298
- teamConsultParticipants,
299
- resolveDisplayName,
300
- historyMessages: params.historyMessages,
301
- latestUserMessageId: params.latestUserMessageId,
302
- availableUploads: params.availableUploads,
303
- systemWorkspaceDetails: params.systemWorkspaceDetails,
304
- getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
305
- retrievedKnowledgeSection: params.retrievedKnowledgeSection,
306
- abortSignal: params.abortSignal,
307
- participantRunner: params.participantRunner,
308
- onReadError: params.onReadError,
309
- }),
310
- )
296
+ this.scope = Scope.makeUnsafe()
297
+ this.resourcesDeferred = Deferred.makeUnsafe<ConsultTeamStreamResources, TeamConsultationError>()
298
+ this.buildEffect = createConsultTeamStreamResources({
299
+ task,
300
+ uploadMetadataText,
301
+ teamConsultParticipants,
302
+ resolveDisplayName,
303
+ historyMessages: params.historyMessages,
304
+ latestUserMessageId: params.latestUserMessageId,
305
+ availableUploads: params.availableUploads,
306
+ systemWorkspaceDetails: params.systemWorkspaceDetails,
307
+ getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
308
+ retrievedKnowledgeSection: params.retrievedKnowledgeSection,
309
+ abortSignal: params.abortSignal,
310
+ participantRunner: params.participantRunner,
311
+ onReadError: params.onReadError,
312
+ })
311
313
  }
312
314
 
313
- private initResourcesEffect(): Effect.Effect<ConsultTeamStreamResources, TeamConsultationResourcesError> {
314
- return Effect.tryPromise({
315
- try: () => this.initPromise,
316
- catch: (error) =>
317
- new TeamConsultationError({ message: 'Failed to initialize team consultation stream.', cause: error }),
318
- })
315
+ /**
316
+ * Acquire-release: on first call, build the stream resources and register
317
+ * cleanup as a finalizer in the iterable's scope. Subsequent calls await the
318
+ * cached `Deferred`. If init fails, no finalizer is registered, so closing
319
+ * the scope is a no-op for the leaked-resource path.
320
+ */
321
+ private acquireResourcesEffect(): Effect.Effect<ConsultTeamStreamResources, TeamConsultationError> {
322
+ if (this.initStarted) {
323
+ return Deferred.await(this.resourcesDeferred)
324
+ }
325
+ this.initStarted = true
326
+
327
+ const acquire = Effect.acquireRelease(this.buildEffect, ({ snapshotQueue, workerFiber }) =>
328
+ Fiber.interrupt(workerFiber).pipe(Effect.andThen(Queue.shutdown(snapshotQueue))),
329
+ ).pipe(Scope.provide(this.scope))
330
+
331
+ return acquire.pipe(
332
+ Effect.exit,
333
+ Effect.flatMap((exit) =>
334
+ Deferred.done(this.resourcesDeferred, exit).pipe(Effect.andThen(Deferred.await(this.resourcesDeferred))),
335
+ ),
336
+ )
319
337
  }
320
338
 
321
- private cleanupEffect(
322
- snapshotQueue: Queue.Queue<ConsultTeamSnapshotItem>,
323
- workerFiber: Fiber.Fiber<unknown, unknown>,
324
- ): Effect.Effect<void, never> {
325
- return Effect.andThen(Fiber.interrupt(workerFiber), Queue.shutdown(snapshotQueue))
339
+ /** Close the scope idempotently. Runs cleanup finalizers iff init succeeded. */
340
+ private closeScopeEffect(): Effect.Effect<void, never> {
341
+ return Scope.close(this.scope, Exit.void).pipe(Effect.catchCause(() => Effect.void))
326
342
  }
327
343
 
328
344
  [Symbol.asyncIterator](): AsyncIterableIterator<ConsultTeamResultData> {
@@ -337,7 +353,7 @@ class ConsultTeamSnapshotIterable implements AsyncIterableIterator<ConsultTeamRe
337
353
  return Effect.runPromise(
338
354
  Effect.gen(
339
355
  function* (this: ConsultTeamSnapshotIterable) {
340
- const { snapshotQueue, workerFiber } = yield* this.initResourcesEffect()
356
+ const { snapshotQueue } = yield* this.acquireResourcesEffect()
341
357
 
342
358
  if (this.completed) {
343
359
  return { done: true, value: undefined as never }
@@ -346,46 +362,37 @@ class ConsultTeamSnapshotIterable implements AsyncIterableIterator<ConsultTeamRe
346
362
  const item = yield* Queue.take(snapshotQueue)
347
363
  if (item === DONE) {
348
364
  this.completed = true
349
- yield* this.cleanupEffect(snapshotQueue, workerFiber)
365
+ this.closed = true
366
+ yield* this.closeScopeEffect()
350
367
  return { done: true, value: undefined as never }
351
368
  }
352
369
 
353
370
  return { done: false, value: item }
354
371
  }.bind(this),
372
+ ).pipe(
373
+ Effect.tapCause(() =>
374
+ Effect.sync(() => {
375
+ this.closed = true
376
+ }).pipe(Effect.andThen(this.closeScopeEffect())),
377
+ ),
355
378
  ),
356
379
  )
357
380
  }
358
381
 
359
382
  return(): Promise<IteratorResult<ConsultTeamResultData>> {
360
383
  this.closed = true
361
- return Effect.runPromise(
362
- Effect.gen(
363
- function* (this: ConsultTeamSnapshotIterable) {
364
- const { snapshotQueue, workerFiber } = yield* this.initResourcesEffect()
365
- yield* this.cleanupEffect(snapshotQueue, workerFiber)
366
- return { done: true, value: undefined as never }
367
- }.bind(this),
368
- ),
369
- )
384
+ return Effect.runPromise(this.closeScopeEffect().pipe(Effect.as({ done: true, value: undefined as never })))
370
385
  }
371
386
 
372
387
  throw(error: unknown): Promise<never> {
373
388
  this.closed = true
374
- return Effect.runPromise(
375
- Effect.gen(
376
- function* (this: ConsultTeamSnapshotIterable) {
377
- const { snapshotQueue, workerFiber } = yield* this.initResourcesEffect()
378
- yield* this.cleanupEffect(snapshotQueue, workerFiber)
379
- return yield* Effect.fail(error as never)
380
- }.bind(this),
381
- ),
382
- )
389
+ return Effect.runPromise(this.closeScopeEffect().pipe(Effect.andThen(Effect.fail(error as never))))
383
390
  }
384
391
  }
385
392
 
386
393
  export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
387
- const teamConsultParticipants = getTeamConsultParticipants()
388
- const agentDisplayNames = getAgentDisplayNames()
394
+ const teamConsultParticipants = params.agentConfig.teamConsultParticipants
395
+ const agentDisplayNames = params.agentConfig.displayNames
389
396
  const resolveDisplayName = (agentId: string) => {
390
397
  if (params.displayNamesById && Object.hasOwn(params.displayNamesById, agentId)) {
391
398
  const override = params.displayNamesById[agentId]
@@ -1,12 +1,17 @@
1
- import { getAgentDisplayNames, getLeadAgentDisplayName } from '../../config/agent-defaults'
1
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
2
2
 
3
- function resolveDisplayName(agentId: string): string {
4
- return getAgentDisplayNames()[agentId] ?? agentId
3
+ function resolveDisplayName(agentConfig: ResolvedAgentConfig, agentId: string): string {
4
+ return agentConfig.displayNames[agentId] ?? agentId
5
5
  }
6
6
 
7
- export function buildTeamConsultationResponseGuard(params: { agentId: string; task: string }) {
8
- const agentName = resolveDisplayName(params.agentId)
9
- const leadAgentDisplayName = getLeadAgentDisplayName()
7
+ export function buildTeamConsultationResponseGuard(params: {
8
+ agentConfig: ResolvedAgentConfig
9
+ agentId: string
10
+ task: string
11
+ }) {
12
+ const agentName = resolveDisplayName(params.agentConfig, params.agentId)
13
+ const leadAgentDisplayName =
14
+ params.agentConfig.displayNames[params.agentConfig.leadAgentId] ?? params.agentConfig.leadAgentId
10
15
  const mentorConstraint =
11
16
  params.agentId === 'mentor'
12
17
  ? ['- As Mentor, answer as an experienced operator reviewing launch discipline, not as a coach or therapist.']
@@ -1,7 +1,8 @@
1
1
  import { toTimestamp } from '@lota-sdk/shared'
2
2
  import type { ChatMessage } from '@lota-sdk/shared'
3
3
 
4
- import { getAgentDisplayNames, resolveAgentNameAlias } from '../config/agent-defaults'
4
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
5
+ import { resolveAgentNameAlias } from '../config/agent-defaults'
5
6
  import type { ChatMessageLike, ReadableUploadMetadataLike } from './chat-types'
6
7
 
7
8
  export interface ThreadHistoryMessage {
@@ -29,13 +30,13 @@ export function readInstructionSections(value: unknown): string[] {
29
30
  .filter((section) => section.length > 0)
30
31
  }
31
32
 
32
- function getAgentName(message: ChatMessageLike): string | undefined {
33
+ function getAgentName(agentConfig: ResolvedAgentConfig, message: ChatMessageLike): string | undefined {
33
34
  const metadata = message.metadata
34
35
  if (!metadata || typeof metadata !== 'object') return undefined
35
36
  const value = (metadata as Record<string, unknown>).agentName
36
37
  if (typeof value !== 'string' || !value.trim()) return undefined
37
- const resolvedAgentName = resolveAgentNameAlias(value)
38
- return resolvedAgentName ? (getAgentDisplayNames()[resolvedAgentName] ?? value.trim()) : value.trim()
38
+ const resolvedAgentName = resolveAgentNameAlias(agentConfig, value)
39
+ return resolvedAgentName ? (agentConfig.displayNames[resolvedAgentName] ?? value.trim()) : value.trim()
39
40
  }
40
41
 
41
42
  export function extractMessageText(message: ChatMessageLike): string {
@@ -45,7 +46,11 @@ export function extractMessageText(message: ChatMessageLike): string {
45
46
  .trim()
46
47
  }
47
48
 
48
- export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): ThreadHistoryMessage[] {
49
+ export function toHistoryMessages(
50
+ agentConfig: ResolvedAgentConfig,
51
+ messages: ChatMessageLike[],
52
+ maxItems = 24,
53
+ ): ThreadHistoryMessage[] {
49
54
  return messages
50
55
  .map((message): ThreadHistoryMessage | null => {
51
56
  const content = extractMessageText(message)
@@ -55,7 +60,7 @@ export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): T
55
60
  return { role: 'user', content }
56
61
  }
57
62
  if (message.role === 'assistant') {
58
- const agentName = getAgentName(message)
63
+ const agentName = getAgentName(agentConfig, message)
59
64
  return { role: 'agent', content, ...(agentName ? { agentName } : {}) }
60
65
  }
61
66
  return null
@@ -65,6 +70,7 @@ export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): T
65
70
  }
66
71
 
67
72
  export function buildConversationSummary(params: {
73
+ agentConfig: ResolvedAgentConfig
68
74
  userMessageText: string
69
75
  assistantMessages: ChatMessageLike[]
70
76
  }): string {
@@ -76,15 +82,18 @@ export function buildConversationSummary(params: {
76
82
  for (const message of params.assistantMessages) {
77
83
  const content = extractMessageText(message)
78
84
  if (!content) continue
79
- const agentName = getAgentName(message)
85
+ const agentName = getAgentName(params.agentConfig, message)
80
86
  lines.push(agentName ? `${agentName}: ${content}` : `Assistant: ${content}`)
81
87
  }
82
88
 
83
89
  return lines.join('\n\n').trim()
84
90
  }
85
91
 
86
- export function buildAgentHistoryMessages(messages: ChatMessageLike[]): Array<{ content: string; agentName?: string }> {
87
- return toHistoryMessages(messages)
92
+ export function buildAgentHistoryMessages(
93
+ agentConfig: ResolvedAgentConfig,
94
+ messages: ChatMessageLike[],
95
+ ): Array<{ content: string; agentName?: string }> {
96
+ return toHistoryMessages(agentConfig, messages)
88
97
  .filter((message) => message.role === 'agent')
89
98
  .map((message) => ({ content: message.content, ...(message.agentName ? { agentName: message.agentName } : {}) }))
90
99
  }
@@ -1,12 +1,7 @@
1
1
  import { Schema, Effect } from 'effect'
2
2
 
3
3
  import type { RecordIdRef } from '../db/record-id'
4
- import { recordIdToString } from '../db/record-id'
5
- import { TABLES } from '../db/tables'
6
- import {
7
- effectTryMaybeAsync as effectTryMaybeAsyncShared,
8
- effectTryPromise as effectTryPromiseShared,
9
- } from '../effect/helpers'
4
+ import { effectTryMaybeAsync as effectTryMaybeAsyncShared, makeEffectTryPromiseWithMessage } from '../effect/helpers'
10
5
  import { buildAgentPromptContext } from './agent-prompt-context'
11
6
  import {
12
7
  buildIndexedRepositoriesContext,
@@ -14,10 +9,9 @@ import {
14
9
  getLinearInstallationByOrgId,
15
10
  } from './plugin-resolution'
16
11
  import type {
12
+ LotaRuntimeAdapters,
17
13
  LotaRuntimeIndexedRepositoriesContext,
18
14
  LotaRuntimeTurnHooks,
19
- LotaRuntimeWorkspaceLifecycleState,
20
- LotaRuntimeWorkspaceProjectionState,
21
15
  LotaRuntimeWorkspaceProvider,
22
16
  } from './runtime-extensions'
23
17
  import { readInstructionSections, readOptionalString } from './thread-chat-helpers'
@@ -35,9 +29,9 @@ class ThreadTurnContextError extends Schema.TaggedErrorClass<ThreadTurnContextEr
35
29
  cause: Schema.optional(Schema.Defect),
36
30
  }) {}
37
31
 
38
- function effectTryPromise<A>(thunk: () => PromiseLike<A>, message: string): Effect.Effect<A, ThreadTurnContextError> {
39
- return effectTryPromiseShared(thunk, (cause) => new ThreadTurnContextError({ message, cause }))
40
- }
32
+ const effectTryPromise = makeEffectTryPromiseWithMessage(
33
+ (message, cause) => new ThreadTurnContextError({ message, cause }),
34
+ )
41
35
 
42
36
  function effectTryMaybeAsync<A>(
43
37
  evaluate: () => A | PromiseLike<A>,
@@ -46,21 +40,7 @@ function effectTryMaybeAsync<A>(
46
40
  return effectTryMaybeAsyncShared(evaluate, (cause) => new ThreadTurnContextError({ message, cause }))
47
41
  }
48
42
 
49
- interface AssembledThreadTurnContext {
50
- workspace: Record<string, unknown>
51
- workspaceLifecycleState: LotaRuntimeWorkspaceLifecycleState | undefined
52
- workspaceProfileState: LotaRuntimeWorkspaceProjectionState | undefined
53
- onboardingActive: boolean
54
- linearInstalled: boolean
55
- githubInstalled: boolean
56
- indexedRepoContext: LotaRuntimeIndexedRepositoriesContext
57
- promptContext: { systemWorkspaceDetails?: string }
58
- retrievedKnowledgeSection: string | undefined
59
- buildContextResult: Record<string, unknown> | null
60
- hookInstructionSections: string[]
61
- }
62
-
63
- const assembleThreadTurnContextEffect = Effect.fn('ThreadTurnContext.assemble')(function* (params: {
43
+ export const assembleThreadTurnContext = Effect.fn('ThreadTurnContext.assemble')(function* (params: {
64
44
  thread: unknown
65
45
  threadRef: RecordIdRef
66
46
  orgRef: RecordIdRef
@@ -72,6 +52,8 @@ const assembleThreadTurnContextEffect = Effect.fn('ThreadTurnContext.assemble')(
72
52
  workspacePromise: Promise<Record<string, unknown>>
73
53
  workspaceProvider?: LotaRuntimeWorkspaceProvider
74
54
  turnHooks: LotaRuntimeTurnHooks
55
+ runtimeAdapters: LotaRuntimeAdapters
56
+ pluginRuntime?: Record<string, unknown>
75
57
  }) {
76
58
  const workspace = yield* effectTryPromise(() => params.workspacePromise, 'Failed to load thread workspace').pipe(
77
59
  Effect.withSpan('ThreadTurnContext.loadWorkspace'),
@@ -88,21 +70,21 @@ const assembleThreadTurnContextEffect = Effect.fn('ThreadTurnContext.assemble')(
88
70
 
89
71
  const onboardingActive = workspaceLifecycleState?.bootstrapActive ?? false
90
72
  const linearInstallation = yield* effectTryPromise(
91
- () => getLinearInstallationByOrgId(params.orgRef),
73
+ () => getLinearInstallationByOrgId(params.pluginRuntime, params.orgRef),
92
74
  'Failed to load Linear installation',
93
75
  ).pipe(
94
76
  Effect.catch(() => Effect.succeed(null)),
95
77
  Effect.withSpan('ThreadTurnContext.loadLinearInstallation'),
96
78
  )
97
79
  const githubInstallation = yield* effectTryPromise(
98
- () => getGithubInstallationForOrganization(params.orgIdString),
80
+ () => getGithubInstallationForOrganization(params.pluginRuntime, params.orgIdString),
99
81
  'Failed to load GitHub installation',
100
82
  ).pipe(
101
83
  Effect.catch(() => Effect.succeed(null)),
102
84
  Effect.withSpan('ThreadTurnContext.loadGithubInstallation'),
103
85
  )
104
86
  const indexedRepoContext = yield* effectTryPromise(
105
- () => buildIndexedRepositoriesContext(params.orgIdString),
87
+ () => buildIndexedRepositoriesContext(params.runtimeAdapters, params.orgIdString),
106
88
  'Failed to build indexed repository context',
107
89
  ).pipe(
108
90
  Effect.catch(() =>
@@ -129,16 +111,16 @@ const assembleThreadTurnContextEffect = Effect.fn('ThreadTurnContext.assemble')(
129
111
  Effect.withSpan('ThreadTurnContext.buildPromptSummary'),
130
112
  )
131
113
 
132
- let linearInstalled = Boolean(linearInstallation)
133
- let githubInstalled = Boolean(githubInstallation)
134
- let promptContext = buildAgentPromptContext({
114
+ const initialLinearInstalled = Boolean(linearInstallation)
115
+ const initialGithubInstalled = Boolean(githubInstallation)
116
+ const initialPromptContext = buildAgentPromptContext({
135
117
  workspaceName: workspaceProfileState?.workspaceName ?? readOptionalString(workspace.name) ?? undefined,
136
118
  summaryBlock: workspaceProfileState?.summaryBlock,
137
119
  promptSummary,
138
120
  userName: params.userName ?? undefined,
139
121
  recentDomainEvents,
140
122
  })
141
- let retrievedKnowledgeSection: string | undefined = !params.messageText
123
+ const initialRetrievedKnowledgeSection: string | undefined = !params.messageText
142
124
  ? undefined
143
125
  : yield* effectTryMaybeAsync(
144
126
  () =>
@@ -161,36 +143,33 @@ const assembleThreadTurnContextEffect = Effect.fn('ThreadTurnContext.assemble')(
161
143
  workspace,
162
144
  onboardingActive,
163
145
  messageText: params.messageText,
164
- linearInstalled,
165
- githubInstalled,
146
+ linearInstalled: initialLinearInstalled,
147
+ githubInstalled: initialGithubInstalled,
166
148
  indexedRepoContext,
167
- promptContext,
149
+ promptContext: initialPromptContext,
168
150
  workspaceLifecycleState,
169
151
  workspaceProfileState,
170
152
  promptSummary,
171
153
  recentDomainEvents,
172
- retrievedKnowledgeSection,
154
+ retrievedKnowledgeSection: initialRetrievedKnowledgeSection,
173
155
  }),
174
156
  'Failed to build thread context',
175
157
  ).pipe(Effect.withSpan('ThreadTurnContext.buildContextHook'))
176
158
  const buildContextResult = isRecord(buildContextResultValue) ? buildContextResultValue : null
177
159
 
178
160
  const buildContextPromptDetails = readOptionalString(buildContextResult?.systemWorkspaceDetails)
179
- if (buildContextPromptDetails) {
180
- promptContext = { systemWorkspaceDetails: buildContextPromptDetails }
181
- }
161
+ const promptContext = buildContextPromptDetails
162
+ ? { systemWorkspaceDetails: buildContextPromptDetails }
163
+ : initialPromptContext
182
164
  const buildContextRetrievedKnowledge = readOptionalString(buildContextResult?.retrievedKnowledgeSection)
183
- if (buildContextRetrievedKnowledge !== undefined) {
184
- retrievedKnowledgeSection = buildContextRetrievedKnowledge
185
- }
165
+ const retrievedKnowledgeSection =
166
+ buildContextRetrievedKnowledge !== undefined ? buildContextRetrievedKnowledge : initialRetrievedKnowledgeSection
186
167
  const buildContextLinearInstalled = readOptionalBoolean(buildContextResult?.linearInstalled)
187
- if (buildContextLinearInstalled !== undefined) {
188
- linearInstalled = buildContextLinearInstalled
189
- }
168
+ const linearInstalled =
169
+ buildContextLinearInstalled !== undefined ? buildContextLinearInstalled : initialLinearInstalled
190
170
  const buildContextGithubInstalled = readOptionalBoolean(buildContextResult?.githubInstalled)
191
- if (buildContextGithubInstalled !== undefined) {
192
- githubInstalled = buildContextGithubInstalled
193
- }
171
+ const githubInstalled =
172
+ buildContextGithubInstalled !== undefined ? buildContextGithubInstalled : initialGithubInstalled
194
173
 
195
174
  const hookInstructionSections = readInstructionSections(
196
175
  yield* effectTryMaybeAsync(
@@ -233,28 +212,3 @@ const assembleThreadTurnContextEffect = Effect.fn('ThreadTurnContext.assemble')(
233
212
  hookInstructionSections,
234
213
  }
235
214
  })
236
-
237
- export function assembleThreadTurnContext(params: {
238
- thread: unknown
239
- threadRef: RecordIdRef
240
- orgRef: RecordIdRef
241
- userRef: RecordIdRef
242
- userName?: string | null
243
- orgIdString: string
244
- userIdString: string
245
- messageText: string
246
- workspacePromise: Promise<Record<string, unknown>>
247
- workspaceProvider?: LotaRuntimeWorkspaceProvider
248
- turnHooks: LotaRuntimeTurnHooks
249
- }): Promise<AssembledThreadTurnContext> {
250
- return Effect.runPromise(
251
- assembleThreadTurnContextEffect(params).pipe(
252
- Effect.annotateSpans({
253
- threadId: recordIdToString(params.threadRef, TABLES.THREAD),
254
- orgId: params.orgIdString,
255
- userId: params.userIdString,
256
- messageLength: params.messageText.length,
257
- }),
258
- ),
259
- )
260
- }
@@ -86,17 +86,9 @@ export function finalizeTurnRunEffect(params: {
86
86
  })
87
87
  }
88
88
 
89
- export function finalizeTurnRun(params: {
90
- serverRunId: string
91
- getEntity: () => MaybeEffect<{ lastCompactedMessageId?: string | null; compactionSummary?: string | null }>
92
- getUncompactedMessages: (cursor?: string) => MaybeEffect<ChatMessage[]>
93
- assessCompaction: (summaryText: string, messages: ChatMessage[]) => { shouldCompact: boolean }
94
- enqueueCompaction: () => MaybeEffect<void>
95
- unregisterRun: (runId: string) => void
96
- clearActiveRunId: (runId: string) => MaybeEffect<void>
97
- disposeAbort: () => void
98
- activeStreamId?: string
99
- clearActiveStreamId?: (streamId: string) => MaybeEffect<void>
100
- }): Promise<void> {
101
- return Effect.runPromise(finalizeTurnRunEffect(params))
102
- }
89
+ /**
90
+ * Alias for `finalizeTurnRunEffect`. Kept as the public name consumers use.
91
+ * Callers pipe the returned Effect through their own runtime rather than
92
+ * relying on a process-wide promise adapter.
93
+ */
94
+ export { finalizeTurnRunEffect as finalizeTurnRun }