@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
@@ -3,8 +3,8 @@ import type IORedis from 'ioredis'
3
3
 
4
4
  import type { chatLogger } from '../config/logger'
5
5
  import { sha256Hex } from '../utils/crypto'
6
- import { DEFAULT_JOB_RETENTION, getQueueJobService } from '../workers/worker-utils'
7
- import type { WorkerHandle } from '../workers/worker-utils'
6
+ import { DEFAULT_JOB_RETENTION } from '../workers/worker-utils'
7
+ import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
8
8
  import { createQueueFactory } from './queue-factory'
9
9
 
10
10
  export type DocumentSourceChannel = string
@@ -56,6 +56,7 @@ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcess
56
56
  getConnectionForBullMQ: () => IORedis
57
57
  getWorkerPath: () => string
58
58
  logger: typeof chatLogger
59
+ queueJobService: QueueJobService
59
60
  queueName?: string
60
61
  workerName?: string
61
62
  concurrency?: number
@@ -75,6 +76,7 @@ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcess
75
76
  connectionProvider: params.getConnectionForBullMQ,
76
77
  defaultJobOptions: { ...DEFAULT_JOB_RETENTION, attempts: 3, backoff: { type: 'exponential', delay: 1000 } },
77
78
  processorPath: params.getWorkerPath(),
79
+ queueJobService: params.queueJobService,
78
80
  })
79
81
 
80
82
  return {
@@ -86,7 +88,7 @@ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcess
86
88
  )
87
89
 
88
90
  yield* Effect.catch(
89
- getQueueJobService().recordEnqueued({
91
+ params.queueJobService.recordEnqueued({
90
92
  queueName,
91
93
  id: queuedJob.id,
92
94
  name: queuedJob.name,
@@ -8,4 +8,5 @@ export * from './organization-learning.queue'
8
8
  export * from './plan-agent-heartbeat.queue'
9
9
  export * from './plan-scheduler.queue'
10
10
  export * from './post-chat-memory.queue'
11
+ export * from './queues.service'
11
12
  export * from './title-generation.queue'
@@ -1,12 +1,12 @@
1
1
  import { Effect } from 'effect'
2
+ import type { Context } from 'effect'
3
+ import type IORedis from 'ioredis'
2
4
 
3
5
  import { serverLogger } from '../config/logger'
4
- import {
5
- getQueueJobService,
6
- getWorkerPath,
7
- LONG_JOB_LOCK_DURATION_MS,
8
- LOW_JOB_RETENTION,
9
- } from '../workers/worker-utils'
6
+ import { RedisServiceTag } from '../effect/services'
7
+ import { QueueJobServiceTag } from '../services/queue-job.service'
8
+ import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
9
+ import { getWorkerPath, LONG_JOB_LOCK_DURATION_MS, LOW_JOB_RETENTION } from '../workers/worker-utils'
10
10
  import { createQueueFactory } from './queue-factory'
11
11
  import { runStandaloneQueueWorker } from './standalone-worker'
12
12
 
@@ -17,58 +17,84 @@ export interface MemoryConsolidationJob {
17
17
  const MEMORY_CONSOLIDATION_INTERVAL_MS = 24 * 60 * 60 * 1000
18
18
  const MEMORY_CONSOLIDATION_SCHEDULER_ID = 'memory-consolidation-recurring'
19
19
 
20
- const memoryConsolidation = createQueueFactory<MemoryConsolidationJob>({
21
- name: 'memory-consolidation',
22
- displayName: 'Memory consolidation',
23
- jobName: 'consolidate-turn',
24
- concurrency: 1,
25
- lockDuration: LONG_JOB_LOCK_DURATION_MS,
26
- defaultJobOptions: { ...LOW_JOB_RETENTION, attempts: 2, backoff: { type: 'exponential', delay: 5000 } },
27
- processorPath: getWorkerPath('memory-consolidation.worker.ts'),
28
- })
20
+ export interface MemoryConsolidationQueueRuntime {
21
+ enqueueMemoryConsolidation(job?: MemoryConsolidationJob): Promise<void>
22
+ scheduleRecurringConsolidation(): Promise<void>
23
+ startWorker(options?: { registerSignals?: boolean }): WorkerHandle
24
+ }
29
25
 
30
- export function enqueueMemoryConsolidation(job: MemoryConsolidationJob = {}) {
31
- return memoryConsolidation.enqueue(job, { jobId: job.scopeId ? `consolidate-turn:${job.scopeId}` : undefined })
26
+ interface MakeMemoryConsolidationQueueRuntimeParams {
27
+ connectionProvider: () => IORedis
28
+ queueJobService: QueueJobService
32
29
  }
33
30
 
34
- export function scheduleRecurringConsolidation() {
35
- return Effect.runPromise(
36
- Effect.gen(function* () {
37
- const queuedJob = yield* Effect.tryPromise(() =>
38
- memoryConsolidation
39
- .getQueue()
40
- .upsertJobScheduler(
41
- MEMORY_CONSOLIDATION_SCHEDULER_ID,
42
- { every: MEMORY_CONSOLIDATION_INTERVAL_MS },
43
- {
44
- name: 'consolidate',
45
- data: {},
46
- opts: { ...LOW_JOB_RETENTION, attempts: 2, backoff: { type: 'exponential', delay: 5000 } },
47
- },
48
- ),
49
- )
31
+ export function makeMemoryConsolidationQueueRuntime(
32
+ params: MakeMemoryConsolidationQueueRuntimeParams,
33
+ ): MemoryConsolidationQueueRuntime {
34
+ const { connectionProvider, queueJobService } = params
35
+ const queue = createQueueFactory<MemoryConsolidationJob>({
36
+ name: 'memory-consolidation',
37
+ displayName: 'Memory consolidation',
38
+ jobName: 'consolidate-turn',
39
+ concurrency: 1,
40
+ lockDuration: LONG_JOB_LOCK_DURATION_MS,
41
+ defaultJobOptions: { ...LOW_JOB_RETENTION, attempts: 2, backoff: { type: 'exponential', delay: 5000 } },
42
+ processorPath: getWorkerPath('memory-consolidation.worker.ts'),
43
+ connectionProvider,
44
+ queueJobService,
45
+ })
46
+
47
+ const enqueueMemoryConsolidation: MemoryConsolidationQueueRuntime['enqueueMemoryConsolidation'] = (job = {}) =>
48
+ queue.enqueue(job, { jobId: job.scopeId ? `consolidate-turn:${job.scopeId}` : undefined })
50
49
 
51
- yield* Effect.catch(
52
- getQueueJobService().recordEnqueued({
53
- queueName: 'memory-consolidation',
54
- id: queuedJob.id,
55
- name: queuedJob.name,
56
- data: queuedJob.data,
57
- opts: queuedJob.opts,
58
- attemptsMade: queuedJob.attemptsMade,
59
- timestamp: queuedJob.timestamp,
60
- }),
61
- (error) =>
62
- Effect.sync(() => {
63
- serverLogger.error`Failed to persist queued job metadata (queue=memory-consolidation, job=${queuedJob.id}): ${error}`
50
+ const scheduleRecurringConsolidation: MemoryConsolidationQueueRuntime['scheduleRecurringConsolidation'] = () =>
51
+ Effect.runPromise(
52
+ Effect.gen(function* () {
53
+ const queuedJob = yield* Effect.tryPromise(() =>
54
+ queue
55
+ .getQueue()
56
+ .upsertJobScheduler(
57
+ MEMORY_CONSOLIDATION_SCHEDULER_ID,
58
+ { every: MEMORY_CONSOLIDATION_INTERVAL_MS },
59
+ {
60
+ name: 'consolidate',
61
+ data: {},
62
+ opts: { ...LOW_JOB_RETENTION, attempts: 2, backoff: { type: 'exponential', delay: 5000 } },
63
+ },
64
+ ),
65
+ )
66
+
67
+ yield* Effect.catch(
68
+ queueJobService.recordEnqueued({
69
+ queueName: 'memory-consolidation',
70
+ id: queuedJob.id,
71
+ name: queuedJob.name,
72
+ data: queuedJob.data,
73
+ opts: queuedJob.opts,
74
+ attemptsMade: queuedJob.attemptsMade,
75
+ timestamp: queuedJob.timestamp,
64
76
  }),
65
- )
66
- }),
67
- )
68
- }
77
+ (error) =>
78
+ Effect.sync(() => {
79
+ serverLogger.error`Failed to persist queued job metadata (queue=memory-consolidation, job=${queuedJob.id}): ${error}`
80
+ }),
81
+ )
82
+ }),
83
+ )
69
84
 
70
- export const startMemoryConsolidationWorker = memoryConsolidation.startWorker
85
+ return {
86
+ enqueueMemoryConsolidation,
87
+ scheduleRecurringConsolidation,
88
+ startWorker: (options = {}) => queue.startWorker({ registerSignals: options.registerSignals, connectionProvider }),
89
+ }
90
+ }
71
91
 
72
- runStandaloneQueueWorker(() => {
73
- startMemoryConsolidationWorker()
92
+ runStandaloneQueueWorker((runtime) => {
93
+ const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
94
+ const redis = resolve(RedisServiceTag)
95
+ const memoryConsolidationQueue = makeMemoryConsolidationQueueRuntime({
96
+ connectionProvider: () => redis.getConnectionForBullMQ(),
97
+ queueJobService: resolve(QueueJobServiceTag),
98
+ })
99
+ memoryConsolidationQueue.startWorker()
74
100
  })
@@ -1,5 +1,10 @@
1
1
  import { Effect, Schema } from 'effect'
2
+ import type { Context } from 'effect'
3
+ import type IORedis from 'ioredis'
2
4
 
5
+ import { RedisServiceTag } from '../effect/services'
6
+ import { QueueJobServiceTag } from '../services/queue-job.service'
7
+ import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
3
8
  import { getWorkerPath, LONG_JOB_LOCK_DURATION_MS } from '../workers/worker-utils'
4
9
  import { createQueueFactory } from './queue-factory'
5
10
  import { runStandaloneQueueWorker } from './standalone-worker'
@@ -51,50 +56,69 @@ export function buildSkillExtractionJobOptions(orgId: string) {
51
56
  return { delay: ORGANIZATION_LEARNING_DELAY_MS, deduplication: { id: buildSkillExtractionDeduplicationId(orgId) } }
52
57
  }
53
58
 
54
- const organizationLearningQueue = createQueueFactory<OrganizationLearningJob>({
55
- name: ORGANIZATION_LEARNING_QUEUE,
56
- displayName: 'Organization learning',
57
- jobName: 'organization-learning',
58
- concurrency: 4,
59
- lockDuration: LONG_JOB_LOCK_DURATION_MS,
60
- defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 5_000 } },
61
- processorPath: getWorkerPath('organization-learning.worker.ts'),
62
- })
63
-
64
- export function enqueueRegularChatMemoryDigest(job: Omit<RegularChatMemoryDigestJob, 'kind'>) {
65
- return organizationLearningQueue.enqueue(
66
- { kind: 'regular-chat-memory-digest', ...job },
67
- buildRegularChatMemoryDigestJobOptions(job.orgId),
68
- )
59
+ export interface OrganizationLearningQueueRuntime {
60
+ enqueueRegularChatMemoryDigest(job: Omit<RegularChatMemoryDigestJob, 'kind'>): Promise<void>
61
+ enqueueSkillExtraction(job: Omit<SkillExtractionJob, 'kind'>): Promise<void>
62
+ clearRegularChatMemoryDigestDeduplicationKey(orgId: string): Promise<void>
63
+ startWorker(options?: { registerSignals?: boolean }): WorkerHandle
69
64
  }
70
65
 
71
- export function enqueueSkillExtraction(job: Omit<SkillExtractionJob, 'kind'>) {
72
- return organizationLearningQueue.enqueue(
73
- { kind: 'skill-extraction', ...job },
74
- buildSkillExtractionJobOptions(job.orgId),
75
- )
66
+ interface MakeOrganizationLearningQueueRuntimeParams {
67
+ connectionProvider: () => IORedis
68
+ queueJobService: QueueJobService
76
69
  }
77
70
 
78
- export function clearRegularChatMemoryDigestDeduplicationKey(orgId: string): Promise<void> {
79
- return Effect.runPromise(
80
- Effect.asVoid(
81
- Effect.tryPromise({
82
- try: () =>
83
- organizationLearningQueue
84
- .getQueue()
85
- .removeDeduplicationKey(buildRegularChatMemoryDigestDeduplicationId(orgId)),
86
- catch: (cause) =>
87
- new OrganizationLearningQueueError({
88
- message: `Failed to clear regular-chat memory digest deduplication key for ${orgId}.`,
89
- cause,
71
+ export function makeOrganizationLearningQueueRuntime(
72
+ params: MakeOrganizationLearningQueueRuntimeParams,
73
+ ): OrganizationLearningQueueRuntime {
74
+ const { connectionProvider, queueJobService } = params
75
+ const queue = createQueueFactory<OrganizationLearningJob>({
76
+ name: ORGANIZATION_LEARNING_QUEUE,
77
+ displayName: 'Organization learning',
78
+ jobName: 'organization-learning',
79
+ concurrency: 4,
80
+ lockDuration: LONG_JOB_LOCK_DURATION_MS,
81
+ defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 5_000 } },
82
+ processorPath: getWorkerPath('organization-learning.worker.ts'),
83
+ connectionProvider,
84
+ queueJobService,
85
+ })
86
+
87
+ const enqueueRegularChatMemoryDigest: OrganizationLearningQueueRuntime['enqueueRegularChatMemoryDigest'] = (job) =>
88
+ queue.enqueue({ kind: 'regular-chat-memory-digest', ...job }, buildRegularChatMemoryDigestJobOptions(job.orgId))
89
+
90
+ const enqueueSkillExtraction: OrganizationLearningQueueRuntime['enqueueSkillExtraction'] = (job) =>
91
+ queue.enqueue({ kind: 'skill-extraction', ...job }, buildSkillExtractionJobOptions(job.orgId))
92
+
93
+ const clearRegularChatMemoryDigestDeduplicationKey: OrganizationLearningQueueRuntime['clearRegularChatMemoryDigestDeduplicationKey'] =
94
+ (orgId) =>
95
+ Effect.runPromise(
96
+ Effect.asVoid(
97
+ Effect.tryPromise({
98
+ try: () => queue.getQueue().removeDeduplicationKey(buildRegularChatMemoryDigestDeduplicationId(orgId)),
99
+ catch: (cause) =>
100
+ new OrganizationLearningQueueError({
101
+ message: `Failed to clear regular-chat memory digest deduplication key for ${orgId}.`,
102
+ cause,
103
+ }),
90
104
  }),
91
- }),
92
- ),
93
- )
94
- }
105
+ ),
106
+ )
95
107
 
96
- export const startOrganizationLearningWorker = organizationLearningQueue.startWorker
108
+ return {
109
+ enqueueRegularChatMemoryDigest,
110
+ enqueueSkillExtraction,
111
+ clearRegularChatMemoryDigestDeduplicationKey,
112
+ startWorker: (options = {}) => queue.startWorker({ registerSignals: options.registerSignals, connectionProvider }),
113
+ }
114
+ }
97
115
 
98
- runStandaloneQueueWorker(() => {
99
- startOrganizationLearningWorker()
116
+ runStandaloneQueueWorker((runtime) => {
117
+ const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
118
+ const redis = resolve(RedisServiceTag)
119
+ const organizationLearningQueue = makeOrganizationLearningQueueRuntime({
120
+ connectionProvider: () => redis.getConnectionForBullMQ(),
121
+ queueJobService: resolve(QueueJobServiceTag),
122
+ })
123
+ organizationLearningQueue.startWorker()
100
124
  })
@@ -5,12 +5,20 @@ import type IORedis from 'ioredis'
5
5
 
6
6
  import { serverLogger } from '../config/logger'
7
7
  import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
8
- import { PlanAgentHeartbeatServiceTag } from '../services/plan/plan-agent-heartbeat.service'
9
- import type { WorkerHandle } from '../workers/worker-utils'
8
+ import { QueueJobServiceTag } from '../services/queue-job.service'
9
+ import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
10
10
  import { DEFAULT_JOB_RETENTION, LONG_JOB_LOCK_DURATION_MS } from '../workers/worker-utils'
11
11
  import { createQueueFactoryWithDeps } from './queue-factory'
12
12
  import { runStandaloneQueueWorker } from './standalone-worker'
13
13
 
14
+ // Minimal service shape used by the worker processor. Declared structurally to
15
+ // avoid importing the service tag — which would form a dependency cycle since
16
+ // PlanAgentHeartbeatServiceLive depends on LotaQueuesServiceTag.
17
+ interface PlanAgentHeartbeatWorkerServiceShape {
18
+ wakeNode(params: PlanAgentHeartbeatWakeJob): Effect.Effect<unknown, unknown, unknown>
19
+ sweep(params: { organizationId?: string }): Effect.Effect<void, unknown, unknown>
20
+ }
21
+
14
22
  class PlanAgentHeartbeatQueueError extends Schema.TaggedErrorClass<PlanAgentHeartbeatQueueError>()(
15
23
  '@lota-sdk/core/PlanAgentHeartbeatQueueError',
16
24
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
@@ -37,57 +45,11 @@ export const PLAN_AGENT_HEARTBEAT_QUEUE = 'plan-agent-heartbeat'
37
45
  const PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS = 30_000
38
46
  const PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID = 'plan-agent-heartbeat-sweep'
39
47
 
40
- interface PlanAgentHeartbeatQueueDeps {
48
+ export interface PlanAgentHeartbeatWorkerDeps {
41
49
  databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
42
- planAgentHeartbeatService: Context.Service.Shape<typeof PlanAgentHeartbeatServiceTag>
43
- }
44
-
45
- function enqueueDelayedPlanAgentHeartbeatSweep(delayMs = PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS): Promise<void> {
46
- return planAgentHeartbeatQueue.enqueue(
47
- { type: 'sweep' },
48
- { delay: delayMs, jobId: PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID },
49
- )
50
- }
51
-
52
- function processPlanAgentHeartbeatJob(
53
- deps: PlanAgentHeartbeatQueueDeps,
54
- job: Job<PlanAgentHeartbeatJob>,
55
- ): Promise<void> {
56
- const { planAgentHeartbeatService } = deps
57
- return Effect.runPromise(
58
- Effect.gen(function* () {
59
- if (job.data.type === 'wake-node') {
60
- const wakeJob = job.data
61
- yield* planAgentHeartbeatService.wakeNode(wakeJob)
62
- return
63
- }
64
-
65
- yield* planAgentHeartbeatService.sweep({ organizationId: job.data.organizationId })
66
- if (!job.data.organizationId) {
67
- yield* Effect.tryPromise({
68
- try: () => enqueueDelayedPlanAgentHeartbeatSweep(),
69
- catch: (cause) =>
70
- new PlanAgentHeartbeatQueueError({
71
- message: 'Failed to enqueue delayed plan-agent heartbeat sweep.',
72
- cause,
73
- }),
74
- })
75
- }
76
- }),
77
- )
50
+ planAgentHeartbeatService: PlanAgentHeartbeatWorkerServiceShape
78
51
  }
79
52
 
80
- const planAgentHeartbeatQueue = createQueueFactoryWithDeps<PlanAgentHeartbeatJob, PlanAgentHeartbeatQueueDeps>({
81
- name: PLAN_AGENT_HEARTBEAT_QUEUE,
82
- displayName: 'Plan agent heartbeat',
83
- jobName: 'plan-agent-heartbeat-job',
84
- concurrency: 2,
85
- lockDuration: LONG_JOB_LOCK_DURATION_MS,
86
- defaultJobOptions: { ...DEFAULT_JOB_RETENTION, attempts: 3, backoff: { type: 'exponential', delay: 5_000 } },
87
- prepare: ({ databaseService }) => databaseService.connect(),
88
- processor: processPlanAgentHeartbeatJob,
89
- })
90
-
91
53
  function buildWakeJobId(params: {
92
54
  organizationId: string
93
55
  threadId: string
@@ -100,42 +62,105 @@ function buildWakeJobId(params: {
100
62
  return `plan-agent-wake__${encode(params.runId)}__${encode(params.nodeId)}__${encode(params.agentId)}`
101
63
  }
102
64
 
103
- export function enqueuePlanAgentHeartbeatWake(params: {
104
- organizationId: string
105
- threadId: string
106
- runId: string
107
- nodeId: string
108
- agentId: string
109
- reason: string
110
- }): Promise<void> {
111
- return planAgentHeartbeatQueue.enqueue({ type: 'wake-node', ...params }, { jobId: buildWakeJobId(params) })
65
+ export interface PlanAgentHeartbeatQueueRuntime {
66
+ enqueuePlanAgentHeartbeatWake(params: {
67
+ organizationId: string
68
+ threadId: string
69
+ runId: string
70
+ nodeId: string
71
+ agentId: string
72
+ reason: string
73
+ }): Promise<void>
74
+ startWorker(options: { registerSignals?: boolean; deps: PlanAgentHeartbeatWorkerDeps }): WorkerHandle
112
75
  }
113
76
 
114
- export function startPlanAgentHeartbeatWorker(options: {
115
- registerSignals?: boolean
77
+ interface MakePlanAgentHeartbeatQueueRuntimeParams {
116
78
  connectionProvider: () => IORedis
117
- deps: PlanAgentHeartbeatQueueDeps
118
- }): WorkerHandle {
119
- const handle = planAgentHeartbeatQueue.startWorker({
120
- deps: options.deps,
121
- registerSignals: options.registerSignals,
122
- connectionProvider: options.connectionProvider,
123
- })
79
+ queueJobService: QueueJobService
80
+ }
124
81
 
125
- enqueueDelayedPlanAgentHeartbeatSweep().catch((error: unknown) => {
126
- serverLogger.error`Plan agent heartbeat scheduler setup failed: ${error}`
82
+ export function makePlanAgentHeartbeatQueueRuntime(
83
+ params: MakePlanAgentHeartbeatQueueRuntimeParams,
84
+ ): PlanAgentHeartbeatQueueRuntime {
85
+ const { connectionProvider, queueJobService } = params
86
+
87
+ const enqueueDelayedSweep = (delayMs = PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS): Promise<void> =>
88
+ queue.enqueue({ type: 'sweep' }, { delay: delayMs, jobId: PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID })
89
+
90
+ const processPlanAgentHeartbeatJob = (
91
+ deps: PlanAgentHeartbeatWorkerDeps,
92
+ job: Job<PlanAgentHeartbeatJob>,
93
+ ): Promise<void> => {
94
+ const { planAgentHeartbeatService: service } = deps
95
+ return Effect.runPromise(
96
+ Effect.gen(function* () {
97
+ if (job.data.type === 'wake-node') {
98
+ const wakeJob = job.data
99
+ yield* service.wakeNode(wakeJob)
100
+ return
101
+ }
102
+
103
+ yield* service.sweep({ organizationId: job.data.organizationId })
104
+ if (!job.data.organizationId) {
105
+ yield* Effect.tryPromise({
106
+ try: () => enqueueDelayedSweep(),
107
+ catch: (cause) =>
108
+ new PlanAgentHeartbeatQueueError({
109
+ message: 'Failed to enqueue delayed plan-agent heartbeat sweep.',
110
+ cause,
111
+ }),
112
+ })
113
+ }
114
+ }) as Effect.Effect<void, never, never>,
115
+ )
116
+ }
117
+
118
+ const queue = createQueueFactoryWithDeps<PlanAgentHeartbeatJob, PlanAgentHeartbeatWorkerDeps>({
119
+ name: PLAN_AGENT_HEARTBEAT_QUEUE,
120
+ displayName: 'Plan agent heartbeat',
121
+ jobName: 'plan-agent-heartbeat-job',
122
+ concurrency: 2,
123
+ lockDuration: LONG_JOB_LOCK_DURATION_MS,
124
+ defaultJobOptions: { ...DEFAULT_JOB_RETENTION, attempts: 3, backoff: { type: 'exponential', delay: 5_000 } },
125
+ connectionProvider,
126
+ queueJobService,
127
+ prepare: ({ databaseService }) => Effect.runPromise(databaseService.connect()),
128
+ processor: processPlanAgentHeartbeatJob,
127
129
  })
128
130
 
129
- return handle
131
+ return {
132
+ enqueuePlanAgentHeartbeatWake: (wakeParams) =>
133
+ queue.enqueue({ type: 'wake-node', ...wakeParams }, { jobId: buildWakeJobId(wakeParams) }),
134
+ startWorker: (options) => {
135
+ const handle = queue.startWorker({
136
+ deps: options.deps,
137
+ registerSignals: options.registerSignals,
138
+ connectionProvider,
139
+ })
140
+
141
+ enqueueDelayedSweep().catch((error: unknown) => {
142
+ serverLogger.error`Plan agent heartbeat scheduler setup failed: ${error}`
143
+ })
144
+
145
+ return handle
146
+ },
147
+ }
130
148
  }
131
149
 
132
150
  runStandaloneQueueWorker((runtime) => {
133
- const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
134
- startPlanAgentHeartbeatWorker({
135
- connectionProvider: () => resolve(RedisServiceTag).getConnectionForBullMQ(),
136
- deps: {
137
- databaseService: resolve(DatabaseServiceTag),
138
- planAgentHeartbeatService: resolve(PlanAgentHeartbeatServiceTag),
139
- },
140
- })
151
+ void (async () => {
152
+ const { PlanAgentHeartbeatServiceTag } = await import('../services/plan/plan-agent-heartbeat.service')
153
+ const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
154
+ const redis = resolve(RedisServiceTag)
155
+ const planAgentHeartbeatQueue = makePlanAgentHeartbeatQueueRuntime({
156
+ connectionProvider: () => redis.getConnectionForBullMQ(),
157
+ queueJobService: resolve(QueueJobServiceTag),
158
+ })
159
+ planAgentHeartbeatQueue.startWorker({
160
+ deps: {
161
+ databaseService: resolve(DatabaseServiceTag),
162
+ planAgentHeartbeatService: resolve(PlanAgentHeartbeatServiceTag),
163
+ },
164
+ })
165
+ })()
141
166
  })