@lota-sdk/core 0.4.10 → 0.4.12

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 (110) hide show
  1. package/package.json +3 -3
  2. package/src/ai-gateway/ai-gateway.ts +214 -98
  3. package/src/ai-gateway/index.ts +16 -1
  4. package/src/config/agent-defaults.ts +4 -120
  5. package/src/config/logger.ts +18 -34
  6. package/src/config/model-constants.ts +1 -0
  7. package/src/config/thread-defaults.ts +1 -18
  8. package/src/create-runtime.ts +90 -28
  9. package/src/db/base.service.ts +30 -38
  10. package/src/db/service.ts +489 -545
  11. package/src/effect/index.ts +0 -2
  12. package/src/effect/layers.ts +6 -13
  13. package/src/embeddings/provider.ts +2 -7
  14. package/src/index.ts +4 -5
  15. package/src/queues/autonomous-job.queue.ts +159 -113
  16. package/src/queues/context-compaction.queue.ts +39 -25
  17. package/src/queues/delayed-node-promotion.queue.ts +56 -29
  18. package/src/queues/document-processor.queue.ts +5 -3
  19. package/src/queues/index.ts +1 -0
  20. package/src/queues/memory-consolidation.queue.ts +79 -53
  21. package/src/queues/organization-learning.queue.ts +63 -39
  22. package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
  23. package/src/queues/plan-scheduler.queue.ts +100 -84
  24. package/src/queues/post-chat-memory.queue.ts +55 -33
  25. package/src/queues/queue-factory.ts +40 -41
  26. package/src/queues/queues.service.ts +61 -0
  27. package/src/queues/title-generation.queue.ts +42 -31
  28. package/src/redis/org-memory-lock.ts +24 -9
  29. package/src/redis/redis-lease-lock.ts +8 -1
  30. package/src/runtime/agent-identity-overrides.ts +7 -3
  31. package/src/runtime/agent-runtime-policy.ts +9 -4
  32. package/src/runtime/agent-stream-helpers.ts +9 -4
  33. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  34. package/src/runtime/context-compaction/context-compaction.ts +9 -7
  35. package/src/runtime/domain-layer.ts +15 -4
  36. package/src/runtime/execution-plan-visibility.ts +5 -2
  37. package/src/runtime/graph-designer.ts +0 -22
  38. package/src/runtime/index.ts +2 -0
  39. package/src/runtime/indexed-repositories-policy.ts +2 -6
  40. package/src/runtime/live-turn-trace.ts +344 -0
  41. package/src/runtime/plugin-resolution.ts +29 -12
  42. package/src/runtime/post-turn-side-effects.ts +139 -141
  43. package/src/runtime/runtime-config.ts +0 -6
  44. package/src/runtime/runtime-extensions.ts +0 -54
  45. package/src/runtime/runtime-lifecycle.ts +4 -4
  46. package/src/runtime/runtime-services.ts +125 -53
  47. package/src/runtime/runtime-worker-registry.ts +113 -30
  48. package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
  49. package/src/runtime/social-chat/social-chat-history.ts +3 -1
  50. package/src/runtime/social-chat/social-chat.ts +35 -20
  51. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
  52. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  53. package/src/runtime/thread-chat-helpers.ts +18 -9
  54. package/src/runtime/thread-turn-context.ts +7 -47
  55. package/src/runtime/turn-lifecycle.ts +6 -14
  56. package/src/services/agent-activity.service.ts +168 -175
  57. package/src/services/agent-executor.service.ts +35 -16
  58. package/src/services/attachment.service.ts +4 -70
  59. package/src/services/autonomous-job.service.ts +53 -61
  60. package/src/services/context-compaction.service.ts +7 -9
  61. package/src/services/execution-plan/execution-plan-graph.ts +106 -115
  62. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  63. package/src/services/execution-plan/execution-plan.service.ts +67 -50
  64. package/src/services/global-orchestrator.service.ts +18 -7
  65. package/src/services/graph-full-routing.ts +7 -6
  66. package/src/services/memory/memory-conversation.ts +10 -5
  67. package/src/services/memory/memory.service.ts +11 -8
  68. package/src/services/ownership-dispatcher.service.ts +16 -5
  69. package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
  70. package/src/services/plan/plan-agent-query.service.ts +12 -8
  71. package/src/services/plan/plan-completion-side-effects.ts +93 -101
  72. package/src/services/plan/plan-cycle.service.ts +7 -45
  73. package/src/services/plan/plan-deadline.service.ts +28 -17
  74. package/src/services/plan/plan-event-delivery.service.ts +47 -40
  75. package/src/services/plan/plan-executor-context.ts +2 -0
  76. package/src/services/plan/plan-executor-graph.ts +366 -391
  77. package/src/services/plan/plan-executor.service.ts +13 -91
  78. package/src/services/plan/plan-scheduler.service.ts +62 -49
  79. package/src/services/plan/plan-transaction-events.ts +1 -1
  80. package/src/services/recent-activity-title.service.ts +6 -2
  81. package/src/services/thread/thread-bootstrap.ts +11 -9
  82. package/src/services/thread/thread-message.service.ts +6 -5
  83. package/src/services/thread/thread-turn-execution.ts +86 -82
  84. package/src/services/thread/thread-turn-preparation.service.ts +92 -45
  85. package/src/services/thread/thread-turn-streaming.ts +60 -28
  86. package/src/services/thread/thread-turn.ts +212 -46
  87. package/src/services/thread/thread.service.ts +21 -6
  88. package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
  89. package/src/system-agents/thread-router.agent.ts +23 -20
  90. package/src/tools/execution-plan.tool.ts +8 -3
  91. package/src/tools/fetch-webpage.tool.ts +10 -9
  92. package/src/tools/firecrawl-client.ts +0 -15
  93. package/src/tools/remember-memory.tool.ts +3 -6
  94. package/src/tools/research-topic.tool.ts +12 -3
  95. package/src/tools/search-web.tool.ts +10 -9
  96. package/src/tools/search.tool.ts +4 -5
  97. package/src/tools/team-think.tool.ts +139 -121
  98. package/src/workers/bootstrap.ts +9 -10
  99. package/src/workers/memory-consolidation.worker.ts +4 -1
  100. package/src/workers/organization-learning.worker.ts +15 -2
  101. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  102. package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
  103. package/src/workers/skill-extraction.runner.ts +13 -15
  104. package/src/workers/worker-utils.ts +6 -18
  105. package/src/effect/awaitable-effect.ts +0 -96
  106. package/src/effect/runtime-ref.ts +0 -25
  107. package/src/effect/runtime.ts +0 -46
  108. package/src/redis/runtime-connection.ts +0 -20
  109. package/src/runtime/runtime-accessors.ts +0 -92
  110. package/src/runtime/runtime-token.ts +0 -47
@@ -1,9 +1,4 @@
1
- import type {
2
- ExecutionPlanToolResultData,
3
- PlanFailureClass,
4
- PlanNodeResultSubmission,
5
- SerializableExecutionPlan,
6
- } from '@lota-sdk/shared'
1
+ import type { PlanFailureClass, PlanNodeResultSubmission } from '@lota-sdk/shared'
7
2
  import { PlanNodeAttemptSchema, PlanNodeRunSchema } from '@lota-sdk/shared'
8
3
  import { Context, Schema, Effect, Layer } from 'effect'
9
4
  import type { z } from 'zod'
@@ -15,8 +10,9 @@ import type { DatabaseTransaction } from '../../db/service'
15
10
  import { TABLES } from '../../db/tables'
16
11
  import { BadRequestError, NotFoundError } from '../../effect/errors'
17
12
  import { effectTryPromise as effectTryPromiseShared } from '../../effect/helpers'
18
- import { runPromise } from '../../effect/runtime'
19
13
  import { DatabaseServiceTag } from '../../effect/services'
14
+ import type { DelayedNodePromotionQueueRuntime } from '../../queues/delayed-node-promotion.queue'
15
+ import { LotaQueuesServiceTag } from '../../queues/queues.service'
20
16
  import { GeneratedDocumentStorageServiceTag } from '../../storage/generated-document-storage.service'
21
17
  import { nowDate } from '../../utils/date-time'
22
18
  import { toError } from '../../utils/errors'
@@ -76,6 +72,7 @@ interface PlanExecutorDeps {
76
72
  planSchedulerService: Context.Service.Shape<typeof PlanSchedulerServiceTag>
77
73
  planValidatorService: Context.Service.Shape<typeof PlanValidatorServiceTag>
78
74
  qualityMetricsService: Context.Service.Shape<typeof QualityMetricsServiceTag>
75
+ delayedNodePromotionQueue: DelayedNodePromotionQueueRuntime
79
76
  }
80
77
 
81
78
  type PlanExecutorService = ReturnType<typeof makePlanExecutorService>
@@ -1113,7 +1110,7 @@ function handleAcceptedHumanNodeResponseEffect(
1113
1110
  })
1114
1111
  }
1115
1112
 
1116
- const submitNodeResultEffect = Effect.fn('PlanExecutor.submitNodeResult')(function* (
1113
+ const submitNodeResult = Effect.fn('PlanExecutor.submitNodeResult')(function* (
1117
1114
  context: PlanExecutorContext,
1118
1115
  params: {
1119
1116
  threadId: RecordIdInput
@@ -1231,21 +1228,7 @@ const submitNodeResultEffect = Effect.fn('PlanExecutor.submitNodeResult')(functi
1231
1228
  })
1232
1229
  })
1233
1230
 
1234
- /** @lintignore */
1235
- function submitNodeResult(
1236
- context: PlanExecutorContext,
1237
- params: {
1238
- threadId: RecordIdInput
1239
- runId: string
1240
- nodeId: string
1241
- emittedBy: string
1242
- result: PlanNodeResultSubmission
1243
- },
1244
- ): Promise<ExecutionPlanToolResultData> {
1245
- return submitNodeResultEffect(context, params).pipe(runPromise)
1246
- }
1247
-
1248
- const submitHumanNodeResponseEffect = Effect.fn('PlanExecutor.submitHumanNodeResponse')(function* (
1231
+ const submitHumanNodeResponse = Effect.fn('PlanExecutor.submitHumanNodeResponse')(function* (
1249
1232
  context: PlanExecutorContext,
1250
1233
  params: {
1251
1234
  threadId: RecordIdInput
@@ -1296,21 +1279,7 @@ const submitHumanNodeResponseEffect = Effect.fn('PlanExecutor.submitHumanNodeRes
1296
1279
  }).pipe(Effect.withSpan('PlanExecutor.submitHumanNodeResponse.persistTransaction'))
1297
1280
  })
1298
1281
 
1299
- /** @lintignore */
1300
- function submitHumanNodeResponse(
1301
- context: PlanExecutorContext,
1302
- params: {
1303
- threadId: RecordIdInput
1304
- approvalId?: string
1305
- respondedBy: string
1306
- response: HumanNodeResponsePayload
1307
- approvalMessageId?: string
1308
- },
1309
- ): Promise<SerializableExecutionPlan | null> {
1310
- return submitHumanNodeResponseEffect(context, params).pipe(runPromise)
1311
- }
1312
-
1313
- const resumeRunEffect = Effect.fn('PlanExecutor.resumeRun')(function* (
1282
+ const resumeRun = Effect.fn('PlanExecutor.resumeRun')(function* (
1314
1283
  context: PlanExecutorContext,
1315
1284
  params: { threadId: RecordIdInput; runId: string; emittedBy: string },
1316
1285
  ) {
@@ -1414,15 +1383,7 @@ const resumeRunEffect = Effect.fn('PlanExecutor.resumeRun')(function* (
1414
1383
  })
1415
1384
  })
1416
1385
 
1417
- /** @lintignore */
1418
- function resumeRun(
1419
- context: PlanExecutorContext,
1420
- params: { threadId: RecordIdInput; runId: string; emittedBy: string },
1421
- ): Promise<ExecutionPlanToolResultData> {
1422
- return resumeRunEffect(context, params).pipe(runPromise)
1423
- }
1424
-
1425
- const transitionNodeToRunningEffect = Effect.fn('PlanExecutor.transitionNodeToRunning')(function* (
1386
+ const transitionNodeToRunning = Effect.fn('PlanExecutor.transitionNodeToRunning')(function* (
1426
1387
  context: PlanExecutorContext,
1427
1388
  params: { runId: string; nodeId: string },
1428
1389
  ) {
@@ -1455,15 +1416,7 @@ const transitionNodeToRunningEffect = Effect.fn('PlanExecutor.transitionNodeToRu
1455
1416
  ).pipe(Effect.withSpan('PlanExecutor.transitionNodeToRunning.persistTransaction'))
1456
1417
  })
1457
1418
 
1458
- /** @lintignore */
1459
- function transitionNodeToRunning(
1460
- context: PlanExecutorContext,
1461
- params: { runId: string; nodeId: string },
1462
- ): Promise<void> {
1463
- return transitionNodeToRunningEffect(context, params).pipe(runPromise)
1464
- }
1465
-
1466
- const blockNodeOnDispatchFailureEffect = Effect.fn('PlanExecutor.blockNodeOnDispatchFailure')(function* (
1419
+ const blockNodeOnDispatchFailure = Effect.fn('PlanExecutor.blockNodeOnDispatchFailure')(function* (
1467
1420
  context: PlanExecutorContext,
1468
1421
  params: {
1469
1422
  threadId: RecordIdInput
@@ -1551,22 +1504,7 @@ const blockNodeOnDispatchFailureEffect = Effect.fn('PlanExecutor.blockNodeOnDisp
1551
1504
  }).pipe(Effect.withSpan('PlanExecutor.blockNodeOnDispatchFailure.persistTransaction'))
1552
1505
  })
1553
1506
 
1554
- /** @lintignore */
1555
- function blockNodeOnDispatchFailure(
1556
- context: PlanExecutorContext,
1557
- params: {
1558
- threadId: RecordIdInput
1559
- runId: string
1560
- nodeId: string
1561
- emittedBy: string
1562
- message: string
1563
- failureClass: PlanFailureClass
1564
- },
1565
- ): Promise<SerializableExecutionPlan> {
1566
- return blockNodeOnDispatchFailureEffect(context, params).pipe(runPromise)
1567
- }
1568
-
1569
- const promoteDelayedNodeEffect = Effect.fn('PlanExecutor.promoteDelayedNode')(function* (
1507
+ const promoteDelayedNode = Effect.fn('PlanExecutor.promoteDelayedNode')(function* (
1570
1508
  context: PlanExecutorContext,
1571
1509
  params: { runId: string; nodeId: string; emittedBy: string },
1572
1510
  ) {
@@ -1647,14 +1585,6 @@ const promoteDelayedNodeEffect = Effect.fn('PlanExecutor.promoteDelayedNode')(fu
1647
1585
  }).pipe(Effect.withSpan('PlanExecutor.promoteDelayedNode.persistTransaction'))
1648
1586
  })
1649
1587
 
1650
- /** @lintignore */
1651
- function promoteDelayedNode(
1652
- context: PlanExecutorContext,
1653
- params: { runId: string; nodeId: string; emittedBy: string },
1654
- ): Promise<void> {
1655
- return promoteDelayedNodeEffect(context, params).pipe(runPromise)
1656
- }
1657
-
1658
1588
  export function makePlanExecutorService(deps: PlanExecutorDeps) {
1659
1589
  const context: PlanExecutorContext = {
1660
1590
  databaseService: deps.db,
@@ -1671,6 +1601,7 @@ export function makePlanExecutorService(deps: PlanExecutorDeps) {
1671
1601
  planSchedulerService: deps.planSchedulerService,
1672
1602
  planValidatorService: deps.planValidatorService,
1673
1603
  qualityMetricsService: deps.qualityMetricsService,
1604
+ delayedNodePromotionQueue: deps.delayedNodePromotionQueue,
1674
1605
  planCompletionSideEffects: makePlanCompletionSideEffects({
1675
1606
  databaseService: deps.db,
1676
1607
  feedbackLoopService: deps.feedbackLoopService,
@@ -1683,25 +1614,14 @@ export function makePlanExecutorService(deps: PlanExecutorDeps) {
1683
1614
 
1684
1615
  return {
1685
1616
  submitNodeResult: (params: Parameters<typeof submitNodeResult>[1]) => submitNodeResult(context, params),
1686
- submitNodeResultEffect: (params: Parameters<typeof submitNodeResultEffect>[1]) =>
1687
- submitNodeResultEffect(context, params),
1688
1617
  submitHumanNodeResponse: (params: Parameters<typeof submitHumanNodeResponse>[1]) =>
1689
1618
  submitHumanNodeResponse(context, params),
1690
- submitHumanNodeResponseEffect: (params: Parameters<typeof submitHumanNodeResponseEffect>[1]) =>
1691
- submitHumanNodeResponseEffect(context, params),
1692
1619
  resumeRun: (params: Parameters<typeof resumeRun>[1]) => resumeRun(context, params),
1693
- resumeRunEffect: (params: Parameters<typeof resumeRunEffect>[1]) => resumeRunEffect(context, params),
1694
1620
  transitionNodeToRunning: (params: Parameters<typeof transitionNodeToRunning>[1]) =>
1695
1621
  transitionNodeToRunning(context, params),
1696
- transitionNodeToRunningEffect: (params: Parameters<typeof transitionNodeToRunningEffect>[1]) =>
1697
- transitionNodeToRunningEffect(context, params),
1698
1622
  blockNodeOnDispatchFailure: (params: Parameters<typeof blockNodeOnDispatchFailure>[1]) =>
1699
1623
  blockNodeOnDispatchFailure(context, params),
1700
- blockNodeOnDispatchFailureEffect: (params: Parameters<typeof blockNodeOnDispatchFailureEffect>[1]) =>
1701
- blockNodeOnDispatchFailureEffect(context, params),
1702
1624
  promoteDelayedNode: (params: Parameters<typeof promoteDelayedNode>[1]) => promoteDelayedNode(context, params),
1703
- promoteDelayedNodeEffect: (params: Parameters<typeof promoteDelayedNodeEffect>[1]) =>
1704
- promoteDelayedNodeEffect(context, params),
1705
1625
  syncRunGraph: (params: Parameters<typeof syncRunGraph>[1]) => syncRunGraph(context, params),
1706
1626
  resolveFailureAction,
1707
1627
  buildResolvedInput,
@@ -1717,6 +1637,7 @@ export const PlanExecutorServiceLive = Layer.effect(
1717
1637
  Effect.gen(function* () {
1718
1638
  const db = yield* DatabaseServiceTag
1719
1639
  const storage = yield* GeneratedDocumentStorageServiceTag
1640
+ const queues = yield* LotaQueuesServiceTag
1720
1641
  return makePlanExecutorService({
1721
1642
  db,
1722
1643
  storage,
@@ -1732,6 +1653,7 @@ export const PlanExecutorServiceLive = Layer.effect(
1732
1653
  planSchedulerService: yield* PlanSchedulerServiceTag,
1733
1654
  planValidatorService: yield* PlanValidatorServiceTag,
1734
1655
  qualityMetricsService: yield* QualityMetricsServiceTag,
1656
+ delayedNodePromotionQueue: queues.delayedNodePromotion,
1735
1657
  })
1736
1658
  }),
1737
1659
  )
@@ -10,6 +10,8 @@ import { TABLES } from '../../db/tables'
10
10
  import { NotFoundError } from '../../effect/errors'
11
11
  import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
12
12
  import { DatabaseServiceTag } from '../../effect/services'
13
+ import type { PlanSchedulerQueueRuntime } from '../../queues/plan-scheduler.queue'
14
+ import { LotaQueuesServiceTag } from '../../queues/queues.service'
13
15
  import { nowDate, nowEpochMillis, toDatabaseDateTime, unsafeDateFrom } from '../../utils/date-time'
14
16
 
15
17
  interface PlanSchedulerRuntimeDeps {
@@ -25,65 +27,75 @@ class PlanSchedulerError extends Schema.TaggedErrorClass<PlanSchedulerError>()('
25
27
 
26
28
  const effectTryPromise = makeEffectTryPromiseWithMessage((message, cause) => new PlanSchedulerError({ message, cause }))
27
29
 
28
- function failPlanScheduler(message: string, cause?: unknown): Effect.Effect<never, PlanSchedulerError> {
29
- return Effect.fail(new PlanSchedulerError({ message, ...(cause === undefined ? {} : { cause }) }))
30
+ function requireScheduleField<A>(value: A | undefined, message: string): A {
31
+ if (value === undefined) {
32
+ throw new PlanSchedulerError({ message })
33
+ }
34
+ return value
30
35
  }
31
36
 
32
- function requireScheduleField<A>(value: A | undefined, message: string): Effect.Effect<A, PlanSchedulerError> {
33
- return value === undefined ? failPlanScheduler(message) : Effect.succeed(value)
34
- }
37
+ function computeNextCronDate(cronExpression: string, baseTime: Date): Date {
38
+ const parsedCron = Cron.parse(cronExpression)
39
+ if (!Result.isSuccess(parsedCron)) {
40
+ throw new PlanSchedulerError({ message: `Invalid cron expression: "${cronExpression}".` })
41
+ }
35
42
 
36
- const computeNextCronDateEffect: (cronExpression: string, baseTime: Date) => Effect.Effect<Date, PlanSchedulerError> =
37
- Effect.fn('PlanScheduler.computeNextCronDate')(function* (cronExpression: string, baseTime: Date) {
38
- const parsedCron = Cron.parse(cronExpression)
39
- const cron = Result.isSuccess(parsedCron)
40
- ? parsedCron.success
41
- : yield* failPlanScheduler(`Invalid cron expression: "${cronExpression}".`)
42
-
43
- return yield* Effect.try({
44
- try: () => Cron.next(cron, baseTime),
45
- catch: (cause) =>
46
- new PlanSchedulerError({
47
- message: `Failed to compute the next fire time for cron expression "${cronExpression}".`,
48
- cause,
49
- }),
43
+ try {
44
+ return Cron.next(parsedCron.success, baseTime)
45
+ } catch (cause) {
46
+ throw new PlanSchedulerError({
47
+ message: `Failed to compute the next fire time for cron expression "${cronExpression}".`,
48
+ cause,
50
49
  })
51
- })
52
-
53
- const computeNextFireAtEffect: (spec: PlanScheduleSpec, baseTime?: Date) => Effect.Effect<Date, PlanSchedulerError> =
54
- Effect.fn('PlanScheduler.computeNextFireAt')(function* (spec: PlanScheduleSpec, baseTime: Date = nowDate()) {
55
- switch (spec.type) {
56
- case 'immediate':
57
- return baseTime
50
+ }
51
+ }
58
52
 
59
- case 'absolute': {
60
- const at = yield* requireScheduleField(spec.at, 'Absolute schedules require an "at" timestamp.')
61
- return unsafeDateFrom(at)
62
- }
53
+ // `computeNextFireAt` must stay purely synchronous — no Clock, no service lookups, no async.
54
+ // `computeNextFireAtEffect` wraps it with `Effect.try` so the same logic is composable from
55
+ // Effect callers without an `Effect.runSync` round trip.
56
+ function computeNextFireAt(spec: PlanScheduleSpec, baseTime: Date = nowDate()): Date {
57
+ switch (spec.type) {
58
+ case 'immediate':
59
+ return baseTime
63
60
 
64
- case 'relative': {
65
- const delayMs = yield* requireScheduleField(spec.delayMs, 'Relative schedules require "delayMs".')
66
- return unsafeDateFrom(baseTime.getTime() + delayMs)
67
- }
61
+ case 'absolute':
62
+ return unsafeDateFrom(requireScheduleField(spec.at, 'Absolute schedules require an "at" timestamp.'))
68
63
 
69
- case 'cron': {
70
- const cronExpression = yield* requireScheduleField(spec.cron, 'Cron schedules require a "cron" expression.')
71
- return yield* computeNextCronDateEffect(cronExpression, baseTime)
72
- }
64
+ case 'relative': {
65
+ const delayMs = requireScheduleField(spec.delayMs, 'Relative schedules require "delayMs".')
66
+ return unsafeDateFrom(baseTime.getTime() + delayMs)
67
+ }
73
68
 
74
- case 'monitoring': {
75
- const intervalMs = yield* requireScheduleField(spec.intervalMs, 'Monitoring schedules require "intervalMs".')
76
- return unsafeDateFrom(baseTime.getTime() + intervalMs)
77
- }
69
+ case 'cron': {
70
+ const cronExpression = requireScheduleField(spec.cron, 'Cron schedules require a "cron" expression.')
71
+ return computeNextCronDate(cronExpression, baseTime)
72
+ }
78
73
 
79
- default:
80
- return yield* failPlanScheduler('Unsupported schedule type in schedule specification.')
74
+ case 'monitoring': {
75
+ const intervalMs = requireScheduleField(spec.intervalMs, 'Monitoring schedules require "intervalMs".')
76
+ return unsafeDateFrom(baseTime.getTime() + intervalMs)
81
77
  }
78
+
79
+ default:
80
+ throw new PlanSchedulerError({ message: 'Unsupported schedule type in schedule specification.' })
81
+ }
82
+ }
83
+
84
+ const isPlanSchedulerError = Schema.is(PlanSchedulerError)
85
+
86
+ function computeNextFireAtEffect(spec: PlanScheduleSpec, baseTime?: Date): Effect.Effect<Date, PlanSchedulerError> {
87
+ return Effect.try({
88
+ try: () => computeNextFireAt(spec, baseTime),
89
+ catch: (cause) =>
90
+ isPlanSchedulerError(cause)
91
+ ? cause
92
+ : new PlanSchedulerError({ message: 'Failed to compute the next fire time.', cause }),
82
93
  })
94
+ }
83
95
 
84
- export function makePlanSchedulerService(db: SurrealDBService) {
85
- const loadPlanSchedulerQueue = () =>
86
- effectTryPromise(() => import('../../queues/plan-scheduler.queue'), 'Failed to load plan scheduler queue module.')
96
+ export function makePlanSchedulerService(db: SurrealDBService, schedulerQueue: PlanSchedulerQueueRuntime) {
97
+ const loadPlanSchedulerQueue = (): Effect.Effect<PlanSchedulerQueueRuntime, PlanSchedulerError> =>
98
+ Effect.succeed(schedulerQueue)
87
99
 
88
100
  const createScheduleEffect = (params: {
89
101
  organizationId: RecordIdInput
@@ -351,7 +363,7 @@ export function makePlanSchedulerService(db: SurrealDBService) {
351
363
  return {
352
364
  createSchedule: createScheduleEffect,
353
365
  computeNextFireAt(spec: PlanScheduleSpec, baseTime: Date = nowDate()): Date {
354
- return Effect.runSync(computeNextFireAtEffect(spec, baseTime))
366
+ return computeNextFireAt(spec, baseTime)
355
367
  },
356
368
  /** Called by the BullMQ worker when a fire-schedule job executes. */
357
369
  fireScheduleById: fireScheduleByIdEffect,
@@ -374,6 +386,7 @@ export const PlanSchedulerServiceLive = Layer.effect(
374
386
  PlanSchedulerServiceTag,
375
387
  Effect.gen(function* () {
376
388
  const db = yield* DatabaseServiceTag
377
- return makePlanSchedulerService(db)
389
+ const queues = yield* LotaQueuesServiceTag
390
+ return makePlanSchedulerService(db, queues.planScheduler)
378
391
  }),
379
392
  )
@@ -24,7 +24,7 @@ export function withTransactionAndEventsEffect<T, E, R>(params: {
24
24
  ),
25
25
  )
26
26
  yield* params.planEventDeliveryService
27
- .dispatchEventsEffect(emittedEvents)
27
+ .dispatchEvents(emittedEvents)
28
28
  .pipe(
29
29
  Effect.mapError(
30
30
  (error) =>
@@ -1,7 +1,9 @@
1
1
  import { Context, Effect, Layer } from 'effect'
2
2
 
3
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
3
4
  import { ServiceError } from '../effect/errors'
4
5
  import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
6
+ import { AgentConfigServiceTag } from '../effect/services'
5
7
  import type { HelperModelRuntime } from '../runtime/helper-model'
6
8
  import { HelperModelTag } from '../runtime/helper-model'
7
9
  import { normalizeTitle } from '../runtime/title-helpers'
@@ -45,6 +47,7 @@ function buildRefinementPromptInput(
45
47
  }
46
48
 
47
49
  export function makeRecentActivityTitleService(
50
+ agentConfig: ResolvedAgentConfig,
48
51
  recentActivityService: ReturnType<typeof makeRecentActivityService>,
49
52
  helperModelRuntime: HelperModelRuntime,
50
53
  ) {
@@ -65,7 +68,7 @@ export function makeRecentActivityTitleService(
65
68
  () =>
66
69
  helperModelRuntime.generateHelperText({
67
70
  tag: 'recent-activity-title-refinement',
68
- createAgent: createRecentActivityTitleRefinerAgent,
71
+ createAgent: (options) => createRecentActivityTitleRefinerAgent(agentConfig, options),
69
72
  defaultSystemPrompt: RECENT_ACTIVITY_TITLE_REFINER_PROMPT,
70
73
  timeoutMs: RECENT_ACTIVITY_TITLE_TIMEOUT_MS,
71
74
  messages: [{ role: 'user', content: promptInput }],
@@ -97,8 +100,9 @@ export class RecentActivityTitleServiceTag extends Context.Service<
97
100
  export const RecentActivityTitleServiceLive = Layer.effect(
98
101
  RecentActivityTitleServiceTag,
99
102
  Effect.gen(function* () {
103
+ const agentConfig = yield* AgentConfigServiceTag
100
104
  const recentActivityService = yield* RecentActivityServiceTag
101
105
  const helperModelRuntime = yield* HelperModelTag
102
- return makeRecentActivityTitleService(recentActivityService, helperModelRuntime)
106
+ return makeRecentActivityTitleService(agentConfig, recentActivityService, helperModelRuntime)
103
107
  }),
104
108
  )
@@ -1,9 +1,9 @@
1
1
  import { THREAD } from '@lota-sdk/shared'
2
2
  import { Effect } from 'effect'
3
3
 
4
- import { getAgentDisplayNames, getAgentRoster, getCoreThreadProfile } from '../../config/agent-defaults'
4
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
5
5
  import { serverLogger } from '../../config/logger'
6
- import { getThreadBootstrapConfig } from '../../config/thread-defaults'
6
+ import type { ResolvedThreadBootstrapConfig } from '../../config/thread-defaults'
7
7
  import type { RecordIdRef } from '../../db/record-id'
8
8
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
9
9
  import { TABLES } from '../../db/tables'
@@ -20,8 +20,8 @@ const THREAD_BOOTSTRAP_LOCK_REFRESH_INTERVAL_MS = 5_000
20
20
  const THREAD_BOOTSTRAP_LOCK_MAX_WAIT_MS = 5_000
21
21
  const THREAD_BOOTSTRAP_LOCK_RETRY_DELAY_MS = 100
22
22
 
23
- function getAgentDisplayName(agentId: string): string {
24
- return getAgentDisplayNames()[agentId] ?? agentId
23
+ function getAgentDisplayName(agentConfig: ResolvedAgentConfig, agentId: string): string {
24
+ return agentConfig.displayNames[agentId] ?? agentId
25
25
  }
26
26
 
27
27
  function buildBootstrapThreadsLockKey(userId: RecordIdRef, orgId: RecordIdRef): string {
@@ -50,6 +50,8 @@ function isDuplicateCreateErrorLike(value: unknown): value is DuplicateCreateErr
50
50
  }
51
51
 
52
52
  export function createThreadBootstrapHelpers(deps: {
53
+ agentConfig: ResolvedAgentConfig
54
+ threadBootstrapConfig: ResolvedThreadBootstrapConfig
53
55
  threadStore: ThreadRecordStore
54
56
  threadMessageService: Pick<ReturnType<typeof makeThreadMessageService>, 'ensureBootstrapWelcomeMessageEffect'>
55
57
  redis: RedisConnectionManager
@@ -137,7 +139,7 @@ export function createThreadBootstrapHelpers(deps: {
137
139
  userId,
138
140
  agentId,
139
141
  members: [agentId],
140
- title: config?.title ?? getAgentDisplayName(agentId),
142
+ title: config?.title ?? getAgentDisplayName(deps.agentConfig, agentId),
141
143
  status: 'active',
142
144
  nameGenerated: config?.nameGenerated ?? false,
143
145
  isCompacting: false,
@@ -250,7 +252,7 @@ export function createThreadBootstrapHelpers(deps: {
250
252
  const threadType = input.threadType
251
253
  if (!threadType) return yield* new BadRequestError({ message: 'Thread threads require threadType' })
252
254
  const { record } = yield* getOrCreateThreadEffect(input.organizationId, input.userId, threadType, {
253
- members: input.members ?? [...getAgentRoster()],
255
+ members: input.members ?? [...deps.agentConfig.roster],
254
256
  title,
255
257
  nameGenerated,
256
258
  })
@@ -263,7 +265,7 @@ export function createThreadBootstrapHelpers(deps: {
263
265
  type: input.type,
264
266
  agentId: input.agentId,
265
267
  threadType: input.threadType,
266
- members: input.members ?? [...getAgentRoster()],
268
+ members: input.members ?? [...deps.agentConfig.roster],
267
269
  title,
268
270
  status: 'active',
269
271
  nameGenerated,
@@ -279,7 +281,7 @@ export function createThreadBootstrapHelpers(deps: {
279
281
  orgId: RecordIdRef,
280
282
  options?: { onboardStatus?: string; userName?: string | null },
281
283
  ): Effect.Effect<void, ThreadBootstrapError> => {
282
- const bootstrapConfig = getThreadBootstrapConfig()
284
+ const bootstrapConfig = deps.threadBootstrapConfig
283
285
 
284
286
  return withLeaseLock(
285
287
  {
@@ -342,7 +344,7 @@ export function createThreadBootstrapHelpers(deps: {
342
344
  if (onboardingCompleted) {
343
345
  for (const wsType of bootstrapConfig.threadTypesAfterOnboarding) {
344
346
  if (threadThreadsByType.has(wsType)) continue
345
- const profile = getCoreThreadProfile(wsType)
347
+ const profile = deps.agentConfig.getCoreThreadProfile(wsType)
346
348
  yield* getOrCreateThreadEffect(orgId, userId, wsType, {
347
349
  members: [...profile.members],
348
350
  title: profile.config.title,
@@ -5,7 +5,7 @@ import { RecordId, surql } from 'surrealdb'
5
5
  import { z } from 'zod'
6
6
  import type { ZodTypeAny } from 'zod'
7
7
 
8
- import { getAgentDisplayNames } from '../../config/agent-defaults'
8
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
9
9
  import { CursorRowSchema, listMessageHistoryPageEffect } from '../../db/cursor-pagination'
10
10
  import type { CursorPaginationConfig } from '../../db/cursor-pagination'
11
11
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
@@ -16,7 +16,7 @@ import { ThreadMessageRowSchema } from '../../db/thread-message-row'
16
16
  import type { ThreadMessageRow } from '../../db/thread-message-row'
17
17
  import { ServiceError } from '../../effect/errors'
18
18
  import { effectTryServicePromise } from '../../effect/helpers'
19
- import { DatabaseServiceTag } from '../../effect/services'
19
+ import { AgentConfigServiceTag, DatabaseServiceTag } from '../../effect/services'
20
20
  import { sha256Hex } from '../../utils/crypto'
21
21
  import { nowEpochMillis, unsafeDateFrom } from '../../utils/date-time'
22
22
 
@@ -106,7 +106,7 @@ const threadPaginationConfig: CursorPaginationConfig = {
106
106
  `,
107
107
  }
108
108
 
109
- export function makeThreadMessageService(db: SurrealDBService) {
109
+ export function makeThreadMessageService(db: SurrealDBService, agentConfig: ResolvedAgentConfig) {
110
110
  function upsertMessages(params: { threadId: RecordIdRef; messages: ChatMessage[] }) {
111
111
  const threadId = toThreadRef(params.threadId)
112
112
  return Effect.forEach(params.messages, (message) =>
@@ -349,7 +349,7 @@ export function makeThreadMessageService(db: SurrealDBService) {
349
349
  parts: [{ type: 'text', text: messageText }],
350
350
  metadata: {
351
351
  agentId: params.agentId,
352
- agentName: getAgentDisplayNames()[params.agentId] ?? params.agentId,
352
+ agentName: agentConfig.displayNames[params.agentId] ?? params.agentId,
353
353
  createdAt: nowEpochMillis(),
354
354
  },
355
355
  },
@@ -374,6 +374,7 @@ export const ThreadMessageServiceLive = Layer.effect(
374
374
  ThreadMessageServiceTag,
375
375
  Effect.gen(function* () {
376
376
  const db = yield* DatabaseServiceTag
377
- return makeThreadMessageService(db)
377
+ const agentConfig = yield* AgentConfigServiceTag
378
+ return makeThreadMessageService(db, agentConfig)
378
379
  }),
379
380
  )