@lota-sdk/core 0.4.7 → 0.4.9
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.
- package/package.json +11 -12
- package/src/ai/embedding-cache.ts +94 -22
- package/src/ai-gateway/ai-gateway.ts +738 -223
- package/src/config/agent-defaults.ts +176 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/constants.ts +8 -2
- package/src/config/logger.ts +286 -19
- package/src/config/model-constants.ts +1 -0
- package/src/config/thread-defaults.ts +33 -21
- package/src/create-runtime.ts +725 -383
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +856 -598
- package/src/db/memory.ts +398 -275
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +255 -0
- package/src/db/service.ts +726 -761
- package/src/db/startup.ts +140 -66
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +87 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +98 -0
- package/src/effect/index.ts +22 -0
- package/src/effect/layers.ts +228 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +31 -0
- package/src/effect/services.ts +57 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +122 -71
- package/src/index.ts +46 -1
- package/src/openrouter/direct-provider.ts +29 -0
- package/src/queues/autonomous-job.queue.ts +130 -74
- package/src/queues/context-compaction.queue.ts +60 -15
- package/src/queues/delayed-node-promotion.queue.ts +52 -15
- package/src/queues/document-processor.queue.ts +52 -77
- package/src/queues/memory-consolidation.queue.ts +47 -32
- package/src/queues/organization-learning.queue.ts +13 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
- package/src/queues/plan-scheduler.queue.ts +107 -31
- package/src/queues/post-chat-memory.queue.ts +66 -24
- package/src/queues/queue-factory.ts +142 -52
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +54 -9
- package/src/redis/connection.ts +84 -32
- package/src/redis/index.ts +6 -8
- package/src/redis/org-memory-lock.ts +60 -27
- package/src/redis/redis-lease-lock.ts +200 -121
- package/src/redis/runtime-connection.ts +10 -0
- package/src/redis/stream-context.ts +84 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +4 -1
- package/src/runtime/agent-stream-helpers.ts +20 -9
- package/src/runtime/chat-run-orchestration.ts +102 -19
- package/src/runtime/chat-run-registry.ts +36 -2
- package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
- package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +11 -7
- package/src/runtime/helper-model.ts +135 -48
- package/src/runtime/index.ts +7 -7
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
- package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
- package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
- package/src/runtime/plugin-resolution.ts +144 -24
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +197 -130
- package/src/runtime/retrieval-adapters.ts +38 -4
- package/src/runtime/runtime-config.ts +165 -61
- package/src/runtime/runtime-extensions.ts +21 -34
- package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
- package/src/runtime/social-chat/social-chat.ts +594 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
- package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
- package/src/runtime/thread-chat-helpers.ts +2 -2
- package/src/runtime/thread-plan-turn.ts +2 -1
- package/src/runtime/thread-turn-context.ts +172 -94
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +329 -217
- package/src/services/artifact.service.ts +225 -148
- package/src/services/attachment.service.ts +137 -115
- package/src/services/autonomous-job.service.ts +888 -491
- package/src/services/chat-run-registry.service.ts +11 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +162 -90
- package/src/services/execution-plan/execution-plan-approval.ts +26 -0
- package/src/services/execution-plan/execution-plan-context.ts +29 -0
- package/src/services/execution-plan/execution-plan-graph.ts +256 -0
- package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
- package/src/services/execution-plan/execution-plan-spec.ts +75 -0
- package/src/services/execution-plan/execution-plan.service.ts +1041 -0
- package/src/services/feedback-loop.service.ts +132 -76
- package/src/services/global-orchestrator.service.ts +80 -170
- package/src/services/graph-full-routing.ts +182 -0
- package/src/services/index.ts +18 -20
- package/src/services/institutional-memory.service.ts +220 -123
- package/src/services/learned-skill.service.ts +364 -259
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-org-memory.ts +39 -0
- package/src/services/memory/memory-preseeded.ts +80 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
- package/src/services/memory/memory.service.ts +692 -0
- package/src/services/memory/rerank.service.ts +209 -0
- package/src/services/monitoring-window.service.ts +92 -70
- package/src/services/mutating-approval.service.ts +62 -53
- package/src/services/node-workspace.service.ts +141 -98
- package/src/services/notification.service.ts +17 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +144 -51
- package/src/services/ownership-dispatcher.service.ts +415 -264
- package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
- package/src/services/plan/plan-agent-query.service.ts +322 -0
- package/src/services/plan/plan-approval.service.ts +102 -0
- package/src/services/plan/plan-artifact.service.ts +60 -0
- package/src/services/plan/plan-builder.service.ts +76 -0
- package/src/services/plan/plan-checkpoint.service.ts +103 -0
- package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
- package/src/services/plan/plan-completion-side-effects.ts +175 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +398 -0
- package/src/services/plan/plan-deadline.service.ts +547 -0
- package/src/services/plan/plan-event-delivery.service.ts +261 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +475 -0
- package/src/services/plan/plan-executor-helpers.ts +322 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1654 -0
- package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
- package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
- package/src/services/plan/plan-run-serialization.ts +15 -0
- package/src/services/plan/plan-run.service.ts +644 -0
- package/src/services/plan/plan-scheduler.service.ts +385 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +33 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +125 -0
- package/src/services/plugin-executor.service.ts +97 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +296 -230
- package/src/services/recent-activity-title.service.ts +65 -36
- package/src/services/recent-activity.service.ts +274 -259
- package/src/services/skill-resolver.service.ts +38 -12
- package/src/services/social-chat-history.service.ts +176 -125
- package/src/services/system-executor.service.ts +91 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +369 -0
- package/src/services/thread/thread-listing.ts +198 -0
- package/src/services/thread/thread-memory-block.ts +117 -0
- package/src/services/thread/thread-message.service.ts +363 -0
- package/src/services/thread/thread-record-store.ts +155 -0
- package/src/services/thread/thread-title.service.ts +74 -0
- package/src/services/thread/thread-turn-execution.ts +280 -0
- package/src/services/thread/thread-turn-message-context.ts +73 -0
- package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
- package/src/services/thread/thread-turn-streaming.ts +402 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +343 -0
- package/src/services/thread/thread.service.ts +335 -0
- package/src/services/user.service.ts +82 -32
- package/src/services/write-intent-validator.service.ts +63 -51
- package/src/storage/attachment-parser.ts +69 -27
- package/src/storage/attachment-storage.service.ts +331 -275
- package/src/storage/generated-document-storage.service.ts +66 -34
- package/src/system-agents/agent-result.ts +3 -1
- package/src/system-agents/context-compaction.agent.ts +2 -2
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/memory-reranker.agent.ts +2 -2
- package/src/system-agents/memory.agent.ts +2 -2
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
- package/src/system-agents/skill-extractor.agent.ts +2 -2
- package/src/system-agents/skill-manager.agent.ts +2 -2
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +2 -2
- package/src/tools/execution-plan.tool.ts +220 -161
- package/src/tools/fetch-webpage.tool.ts +21 -17
- package/src/tools/firecrawl-client.ts +16 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +49 -47
- package/src/tools/read-file-parts.tool.ts +44 -33
- package/src/tools/remember-memory.tool.ts +65 -45
- package/src/tools/search-web.tool.ts +26 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +124 -83
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +17 -23
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +95 -16
- package/src/utils/hono-error-handler.ts +24 -39
- package/src/utils/index.ts +2 -1
- package/src/utils/null-proto-record.ts +41 -0
- package/src/utils/sse-keepalive.ts +124 -21
- package/src/workers/bootstrap.ts +186 -51
- package/src/workers/memory-consolidation.worker.ts +325 -237
- package/src/workers/organization-learning.worker.ts +50 -16
- package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
- package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
- package/src/workers/skill-extraction.runner.ts +176 -93
- package/src/workers/utils/file-section-chunker.ts +8 -10
- package/src/workers/utils/repo-structure-extractor.ts +349 -260
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/thread-message-query.ts +97 -38
- package/src/workers/worker-utils.ts +56 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/social-chat-agent-runner.ts +0 -118
- package/src/runtime/social-chat.ts +0 -516
- package/src/runtime/team-consultation-orchestrator.ts +0 -272
- package/src/services/adaptive-playbook.service.ts +0 -152
- package/src/services/artifact-provenance.service.ts +0 -172
- package/src/services/chat-attachments.service.ts +0 -17
- package/src/services/context-compaction-runtime.singleton.ts +0 -13
- package/src/services/execution-plan.service.ts +0 -1118
- package/src/services/memory.service.ts +0 -844
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-approval.service.ts +0 -83
- package/src/services/plan-artifact.service.ts +0 -50
- package/src/services/plan-builder.service.ts +0 -67
- package/src/services/plan-checkpoint.service.ts +0 -81
- package/src/services/plan-completion-side-effects.ts +0 -80
- package/src/services/plan-coordination.service.ts +0 -157
- package/src/services/plan-cycle.service.ts +0 -284
- package/src/services/plan-deadline.service.ts +0 -430
- package/src/services/plan-event-delivery.service.ts +0 -166
- package/src/services/plan-executor.service.ts +0 -1950
- package/src/services/plan-run.service.ts +0 -515
- package/src/services/plan-scheduler.service.ts +0 -240
- package/src/services/plan-template.service.ts +0 -177
- package/src/services/plan-validator.service.ts +0 -818
- package/src/services/plan-workspace.service.ts +0 -83
- package/src/services/thread-message.service.ts +0 -275
- package/src/services/thread-plan-registry.service.ts +0 -22
- package/src/services/thread-title.service.ts +0 -39
- package/src/services/thread-turn-preparation.service.ts +0 -1147
- package/src/services/thread-turn.ts +0 -172
- package/src/services/thread.service.ts +0 -869
- package/src/utils/env.ts +0 -8
- /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
- /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
- /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
- /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
- /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
- /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
- /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
- /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
type OrganizationOnboardStatus = string
|
|
2
1
|
import type { Job } from 'bullmq'
|
|
2
|
+
import { Effect } from 'effect'
|
|
3
|
+
import type { Context } from 'effect'
|
|
4
|
+
import type IORedis from 'ioredis'
|
|
3
5
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
+
import type { MaybeAwaitableService } from '../effect/awaitable-effect'
|
|
7
|
+
import { ConfigurationError } from '../effect/errors'
|
|
8
|
+
import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
|
|
9
|
+
import { MemoryServiceTag } from '../services/memory/memory.service'
|
|
6
10
|
import { createQueueFactory } from './queue-factory'
|
|
11
|
+
import { runStandaloneQueueWorker } from './standalone-worker'
|
|
7
12
|
|
|
8
13
|
interface PostChatMemoryMessage {
|
|
9
14
|
role: 'user' | 'agent'
|
|
@@ -17,7 +22,7 @@ interface PostChatMemoryExtractionJob {
|
|
|
17
22
|
sourceId: string
|
|
18
23
|
source?: string
|
|
19
24
|
sourceMetadata?: Record<string, unknown>
|
|
20
|
-
onboardStatus?:
|
|
25
|
+
onboardStatus?: string
|
|
21
26
|
userMessage: string
|
|
22
27
|
historyMessages: PostChatMemoryMessage[]
|
|
23
28
|
agentMessages: Array<{ content: string; agentName?: string }>
|
|
@@ -25,8 +30,23 @@ interface PostChatMemoryExtractionJob {
|
|
|
25
30
|
attachmentContext?: string
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
interface PostChatMemoryQueueDeps {
|
|
34
|
+
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
|
|
35
|
+
memoryService: MaybeAwaitableService<Context.Service.Shape<typeof MemoryServiceTag>>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let _deps: PostChatMemoryQueueDeps | null = null
|
|
39
|
+
function getDeps(): PostChatMemoryQueueDeps {
|
|
40
|
+
if (!_deps)
|
|
41
|
+
throw new ConfigurationError({
|
|
42
|
+
message: 'Post-chat memory queue is not configured. Initialize the runtime before starting the worker.',
|
|
43
|
+
key: 'queue-deps',
|
|
44
|
+
})
|
|
45
|
+
return _deps
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>): Promise<void> {
|
|
49
|
+
const { memoryService } = getDeps()
|
|
30
50
|
|
|
31
51
|
const data = job.data
|
|
32
52
|
const userMessage = data.userMessage.trim()
|
|
@@ -37,7 +57,9 @@ async function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>):
|
|
|
37
57
|
}))
|
|
38
58
|
.filter((item) => item.content.length > 0)
|
|
39
59
|
|
|
40
|
-
if (!userMessage || agentMessages.length === 0)
|
|
60
|
+
if (!userMessage || agentMessages.length === 0) {
|
|
61
|
+
return Promise.resolve()
|
|
62
|
+
}
|
|
41
63
|
|
|
42
64
|
const joinedOutput = agentMessages
|
|
43
65
|
.map((item) => (item.agentName ? `[${item.agentName}] ${item.content}` : item.content))
|
|
@@ -51,20 +73,24 @@ async function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>):
|
|
|
51
73
|
),
|
|
52
74
|
]
|
|
53
75
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
return Effect.runPromise(
|
|
77
|
+
Effect.asVoid(
|
|
78
|
+
memoryService.addConversationMemories({
|
|
79
|
+
orgId: data.orgId,
|
|
80
|
+
input: userMessage,
|
|
81
|
+
output: joinedOutput,
|
|
82
|
+
sourceId: data.sourceId,
|
|
83
|
+
source: data.source,
|
|
84
|
+
sourceMetadata: data.sourceMetadata,
|
|
85
|
+
onboardStatus: data.onboardStatus,
|
|
86
|
+
...(uniqueAgentNames.length > 0 ? { agentName: uniqueAgentNames[0] } : {}),
|
|
87
|
+
historyMessages: data.historyMessages,
|
|
88
|
+
memoryBlock: data.memoryBlock,
|
|
89
|
+
attachmentContext: data.attachmentContext,
|
|
90
|
+
agentNames: uniqueAgentNames,
|
|
91
|
+
}),
|
|
92
|
+
),
|
|
93
|
+
)
|
|
68
94
|
}
|
|
69
95
|
|
|
70
96
|
const postChatMemory = createQueueFactory<PostChatMemoryExtractionJob>({
|
|
@@ -76,14 +102,30 @@ const postChatMemory = createQueueFactory<PostChatMemoryExtractionJob>({
|
|
|
76
102
|
maxStalledCount: 10,
|
|
77
103
|
stalledInterval: 120_000,
|
|
78
104
|
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
|
|
105
|
+
prepare: () => getDeps().databaseService.connect(),
|
|
79
106
|
processor: processPostChatMemoryJob,
|
|
80
107
|
})
|
|
81
108
|
|
|
82
109
|
export function enqueuePostChatMemory(job: PostChatMemoryExtractionJob, options?: { dedupeKey?: string }) {
|
|
83
110
|
return postChatMemory.enqueue(job, options?.dedupeKey ? { jobId: options.dedupeKey } : undefined)
|
|
84
111
|
}
|
|
85
|
-
export const startPostChatMemoryWorker = postChatMemory.startWorker
|
|
86
112
|
|
|
87
|
-
|
|
88
|
-
|
|
113
|
+
export function startPostChatMemoryWorker(options: {
|
|
114
|
+
registerSignals?: boolean
|
|
115
|
+
connectionProvider: () => IORedis
|
|
116
|
+
deps: PostChatMemoryQueueDeps
|
|
117
|
+
}) {
|
|
118
|
+
_deps = options.deps
|
|
119
|
+
return postChatMemory.startWorker({
|
|
120
|
+
registerSignals: options.registerSignals,
|
|
121
|
+
connectionProvider: options.connectionProvider,
|
|
122
|
+
})
|
|
89
123
|
}
|
|
124
|
+
|
|
125
|
+
runStandaloneQueueWorker((runtime) => {
|
|
126
|
+
const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
|
|
127
|
+
startPostChatMemoryWorker({
|
|
128
|
+
connectionProvider: () => resolve(RedisServiceTag).getConnectionForBullMQ(),
|
|
129
|
+
deps: { databaseService: resolve(DatabaseServiceTag), memoryService: resolve(MemoryServiceTag) },
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
import { Queue, Worker } from 'bullmq'
|
|
2
2
|
import type { Job, JobsOptions, QueueOptions, WorkerOptions } from 'bullmq'
|
|
3
|
+
import { Effect } from 'effect'
|
|
3
4
|
import type IORedis from 'ioredis'
|
|
4
5
|
|
|
6
|
+
import type { LotaLogger } from '../config/logger'
|
|
5
7
|
import { serverLogger } from '../config/logger'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
+
import { getCurrentRuntime } from '../effect/runtime-ref'
|
|
9
|
+
import { RedisServiceTag } from '../effect/services'
|
|
8
10
|
import {
|
|
9
11
|
attachWorkerEvents,
|
|
10
12
|
createTracedWorkerProcessor,
|
|
11
13
|
createWorkerShutdown,
|
|
12
14
|
DEFAULT_JOB_RETENTION,
|
|
13
15
|
registerShutdownSignals,
|
|
16
|
+
getQueueJobService,
|
|
14
17
|
} from '../workers/worker-utils'
|
|
15
18
|
import type { WorkerHandle } from '../workers/worker-utils'
|
|
16
19
|
|
|
20
|
+
function getDefaultQueueConnectionProvider(): () => IORedis {
|
|
21
|
+
const redis = getCurrentRuntime().runSync(Effect.service(RedisServiceTag))
|
|
22
|
+
return () => redis.getConnectionForBullMQ()
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
interface QueueFactoryConfigBase {
|
|
18
26
|
name: string
|
|
19
27
|
displayName: string
|
|
20
28
|
jobName: string
|
|
21
29
|
concurrency: number
|
|
30
|
+
logger?: LotaLogger
|
|
22
31
|
lockDuration?: number
|
|
23
32
|
stalledInterval?: number
|
|
24
33
|
maxStalledCount?: number
|
|
@@ -27,6 +36,7 @@ interface QueueFactoryConfigBase {
|
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
interface QueueFactoryConfigInline<TJob> extends QueueFactoryConfigBase {
|
|
39
|
+
prepare?: (job: Job<TJob>) => Promise<void>
|
|
30
40
|
processor: (job: Job<TJob>) => Promise<unknown>
|
|
31
41
|
processorPath?: never
|
|
32
42
|
}
|
|
@@ -41,70 +51,137 @@ export type QueueFactoryConfig<TJob> = QueueFactoryConfigInline<TJob> | QueueFac
|
|
|
41
51
|
export interface QueueFactory<TJob> {
|
|
42
52
|
getQueue: () => Queue<TJob, unknown, string, TJob, unknown, string>
|
|
43
53
|
enqueue: (job: TJob, options?: JobsOptions) => Promise<void>
|
|
44
|
-
startWorker: (options?: { registerSignals?: boolean }) => WorkerHandle
|
|
54
|
+
startWorker: (options?: { registerSignals?: boolean; connectionProvider?: () => IORedis }) => WorkerHandle
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
export function createQueueFactory<TJob>(config: QueueFactoryConfig<TJob>): QueueFactory<TJob> {
|
|
48
58
|
type QueueShape = Queue<TJob, unknown, string, TJob, unknown, string>
|
|
59
|
+
type QueueMethod = 'add' | 'close' | 'remove' | 'removeDeduplicationKey' | 'removeJobScheduler'
|
|
60
|
+
const queueMethodsThatWaitForClose = new Set<QueueMethod>([
|
|
61
|
+
'add',
|
|
62
|
+
'close',
|
|
63
|
+
'remove',
|
|
64
|
+
'removeDeduplicationKey',
|
|
65
|
+
'removeJobScheduler',
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
let state: {
|
|
69
|
+
queue: QueueShape | null
|
|
70
|
+
rawQueue: QueueShape | null
|
|
71
|
+
connection: IORedis | null
|
|
72
|
+
pendingClose: Promise<void> | null
|
|
73
|
+
} = { queue: null, rawQueue: null, connection: null, pendingClose: null }
|
|
74
|
+
|
|
75
|
+
const resolveConnectionProvider = (): (() => IORedis) =>
|
|
76
|
+
config.connectionProvider ?? getDefaultQueueConnectionProvider()
|
|
77
|
+
|
|
78
|
+
const getConnection = (): IORedis => resolveConnectionProvider()()
|
|
79
|
+
|
|
80
|
+
const waitForPendingClose = (): Promise<void> => {
|
|
81
|
+
const pendingClose = state.pendingClose
|
|
82
|
+
if (!pendingClose) {
|
|
83
|
+
return Promise.resolve()
|
|
84
|
+
}
|
|
49
85
|
|
|
50
|
-
|
|
51
|
-
|
|
86
|
+
return pendingClose.finally(() => {
|
|
87
|
+
if (state.pendingClose === pendingClose) {
|
|
88
|
+
state.pendingClose = null
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
52
92
|
|
|
53
|
-
const
|
|
93
|
+
const wrapQueue = (queue: QueueShape): QueueShape =>
|
|
94
|
+
new Proxy(queue, {
|
|
95
|
+
get(target, property, receiver) {
|
|
96
|
+
if (typeof property !== 'string') {
|
|
97
|
+
return Reflect.get(target, property, receiver) as unknown
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const value = (target as unknown as Record<string, unknown>)[property]
|
|
101
|
+
if (typeof value !== 'function' || !queueMethodsThatWaitForClose.has(property as QueueMethod)) {
|
|
102
|
+
return value
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (...args: Array<unknown>) =>
|
|
106
|
+
waitForPendingClose().then(() =>
|
|
107
|
+
Reflect.apply(value as (...methodArgs: Array<unknown>) => unknown, target, args),
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
})
|
|
54
111
|
|
|
55
112
|
const getQueue = (): QueueShape => {
|
|
56
113
|
const connection = getConnection()
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
serverLogger.warn`Failed to close stale ${config.displayName} queue: ${error}`
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
_queue = new Queue<TJob, unknown, string, TJob, unknown, string>(config.name, {
|
|
72
|
-
connection: connection as QueueOptions['connection'],
|
|
73
|
-
defaultJobOptions: { ...DEFAULT_JOB_RETENTION, ...config.defaultJobOptions },
|
|
74
|
-
})
|
|
75
|
-
_queueConnection = connection
|
|
114
|
+
const isStale =
|
|
115
|
+
state.rawQueue === null ||
|
|
116
|
+
state.queue === null ||
|
|
117
|
+
state.connection === null ||
|
|
118
|
+
state.connection !== connection ||
|
|
119
|
+
state.connection.status === 'close' ||
|
|
120
|
+
state.connection.status === 'end'
|
|
121
|
+
|
|
122
|
+
if (!isStale && state.queue) {
|
|
123
|
+
return state.queue
|
|
76
124
|
}
|
|
77
|
-
|
|
78
|
-
|
|
125
|
+
|
|
126
|
+
if (state.rawQueue) {
|
|
127
|
+
const staleQueue = state.rawQueue
|
|
128
|
+
const previousPendingClose = state.pendingClose ?? Promise.resolve()
|
|
129
|
+
state.pendingClose = previousPendingClose
|
|
130
|
+
.catch(() => undefined)
|
|
131
|
+
.then(() =>
|
|
132
|
+
staleQueue.close().catch((error: unknown) => {
|
|
133
|
+
serverLogger.warn`Failed to close stale ${config.displayName} queue: ${error}`
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
.then(() => undefined)
|
|
79
137
|
}
|
|
80
|
-
|
|
138
|
+
|
|
139
|
+
const queue = new Queue<TJob, unknown, string, TJob, unknown, string>(config.name, {
|
|
140
|
+
connection: connection as QueueOptions['connection'],
|
|
141
|
+
defaultJobOptions: { ...DEFAULT_JOB_RETENTION, ...config.defaultJobOptions },
|
|
142
|
+
})
|
|
143
|
+
const wrappedQueue = wrapQueue(queue)
|
|
144
|
+
|
|
145
|
+
state = { queue: wrappedQueue, rawQueue: queue, connection, pendingClose: state.pendingClose }
|
|
146
|
+
return wrappedQueue
|
|
81
147
|
}
|
|
82
148
|
|
|
83
149
|
const jobName = config.jobName
|
|
84
150
|
const toData = (job: TJob) => job
|
|
85
151
|
|
|
86
|
-
const enqueue =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
152
|
+
const enqueue = (job: TJob, options?: JobsOptions): Promise<void> =>
|
|
153
|
+
Effect.runPromise(
|
|
154
|
+
Effect.gen(function* () {
|
|
155
|
+
const queuedJob = yield* Effect.tryPromise(() => getQueue().add(jobName, toData(job), options))
|
|
156
|
+
yield* getQueueJobService()
|
|
157
|
+
.recordEnqueued({
|
|
158
|
+
queueName: config.name,
|
|
159
|
+
id: queuedJob.id,
|
|
160
|
+
name: queuedJob.name,
|
|
161
|
+
data: queuedJob.data,
|
|
162
|
+
opts: queuedJob.opts,
|
|
163
|
+
attemptsMade: queuedJob.attemptsMade,
|
|
164
|
+
timestamp: queuedJob.timestamp,
|
|
165
|
+
})
|
|
166
|
+
.pipe(
|
|
167
|
+
Effect.tapError((error) =>
|
|
168
|
+
Effect.sync(() => {
|
|
169
|
+
serverLogger.error`Failed to persist queued job metadata (queue=${config.name}, job=${queuedJob.id}): ${error}`
|
|
170
|
+
}),
|
|
171
|
+
),
|
|
172
|
+
Effect.orElseSucceed(() => undefined),
|
|
173
|
+
)
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const startWorker = (
|
|
178
|
+
options: { registerSignals?: boolean; connectionProvider?: () => IORedis } = {},
|
|
179
|
+
): WorkerHandle => {
|
|
180
|
+
const { registerSignals = import.meta.main, connectionProvider } = options
|
|
181
|
+
const logger = config.logger ?? serverLogger
|
|
105
182
|
|
|
106
183
|
const workerOptions: WorkerOptions = {
|
|
107
|
-
connection:
|
|
184
|
+
connection: (connectionProvider ?? resolveConnectionProvider())() as QueueOptions['connection'],
|
|
108
185
|
concurrency: config.concurrency,
|
|
109
186
|
...(config.lockDuration !== undefined ? { lockDuration: config.lockDuration } : {}),
|
|
110
187
|
...(config.stalledInterval !== undefined ? { stalledInterval: config.stalledInterval } : {}),
|
|
@@ -115,16 +192,29 @@ export function createQueueFactory<TJob>(config: QueueFactoryConfig<TJob>): Queu
|
|
|
115
192
|
? new Worker(config.name, config.processorPath, workerOptions)
|
|
116
193
|
: new Worker(
|
|
117
194
|
config.name,
|
|
118
|
-
createTracedWorkerProcessor(config.name, (
|
|
195
|
+
createTracedWorkerProcessor(config.name, (job) =>
|
|
196
|
+
Effect.runPromise(
|
|
197
|
+
Effect.gen(function* () {
|
|
198
|
+
const inlineConfig = config as QueueFactoryConfigInline<TJob>
|
|
199
|
+
const typedJob = job as Job<TJob>
|
|
200
|
+
const prepare = inlineConfig.prepare
|
|
201
|
+
if (prepare) {
|
|
202
|
+
yield* Effect.tryPromise(() => prepare(typedJob))
|
|
203
|
+
}
|
|
204
|
+
const processor = inlineConfig.processor
|
|
205
|
+
return yield* Effect.tryPromise(() => processor(typedJob))
|
|
206
|
+
}),
|
|
207
|
+
),
|
|
208
|
+
),
|
|
119
209
|
workerOptions,
|
|
120
210
|
)
|
|
121
211
|
|
|
122
|
-
attachWorkerEvents(worker, config.displayName,
|
|
212
|
+
attachWorkerEvents(worker, config.displayName, logger)
|
|
123
213
|
|
|
124
|
-
const shutdown = createWorkerShutdown(worker, config.displayName,
|
|
214
|
+
const shutdown = createWorkerShutdown(worker, config.displayName, logger)
|
|
125
215
|
|
|
126
216
|
if (registerSignals) {
|
|
127
|
-
registerShutdownSignals({ name: config.displayName, shutdown, logger
|
|
217
|
+
registerShutdownSignals({ name: config.displayName, shutdown, logger })
|
|
128
218
|
}
|
|
129
219
|
|
|
130
220
|
return { worker, shutdown }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ManagedRuntime } from 'effect'
|
|
2
|
+
import { Schema, Effect } from 'effect'
|
|
3
|
+
|
|
4
|
+
import { serverLogger } from '../config/logger'
|
|
5
|
+
import { initializeSandboxedWorkerRuntime } from '../workers/bootstrap'
|
|
6
|
+
|
|
7
|
+
class StandaloneQueueWorkerError extends Schema.TaggedErrorClass<StandaloneQueueWorkerError>()(
|
|
8
|
+
'StandaloneQueueWorkerError',
|
|
9
|
+
{ message: Schema.String, cause: Schema.Defect },
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
function toStandaloneQueueWorkerError(cause: unknown): StandaloneQueueWorkerError {
|
|
13
|
+
return new StandaloneQueueWorkerError({ message: cause instanceof Error ? cause.message : String(cause), cause })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line typescript-eslint/no-explicit-any -- wildcard for host-provided ManagedRuntime
|
|
17
|
+
export function runStandaloneQueueWorker(start: (runtime: ManagedRuntime.ManagedRuntime<any, any>) => void): void {
|
|
18
|
+
if (!import.meta.main) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
void Effect.runFork(
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const runtime = yield* Effect.tryPromise({
|
|
25
|
+
try: () => initializeSandboxedWorkerRuntime(),
|
|
26
|
+
catch: (cause) => toStandaloneQueueWorkerError(cause),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
yield* Effect.sync(() => start(runtime))
|
|
30
|
+
}).pipe(
|
|
31
|
+
Effect.catchTag('StandaloneQueueWorkerError', (error) =>
|
|
32
|
+
Effect.sync(() => {
|
|
33
|
+
serverLogger.error`Standalone queue worker failed: ${error.message}`
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { Job } from 'bullmq'
|
|
2
|
+
import { Effect } from 'effect'
|
|
3
|
+
import type { Context } from 'effect'
|
|
4
|
+
import type IORedis from 'ioredis'
|
|
2
5
|
|
|
3
6
|
import { ensureRecordId } from '../db/record-id'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
+
import { ConfigurationError } from '../effect/errors'
|
|
8
|
+
import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
|
|
9
|
+
import { RecentActivityTitleServiceTag } from '../services/recent-activity-title.service'
|
|
10
|
+
import { ThreadTitleServiceTag } from '../services/thread/thread-title.service'
|
|
7
11
|
import { createQueueFactory } from './queue-factory'
|
|
12
|
+
import { runStandaloneQueueWorker } from './standalone-worker'
|
|
8
13
|
|
|
9
14
|
export const TITLE_GENERATION_QUEUE = 'title-generation'
|
|
10
15
|
|
|
@@ -25,14 +30,31 @@ interface RecentActivityTitleRefinementJob {
|
|
|
25
30
|
|
|
26
31
|
type TitleGenerationJob = ThreadTitleGenerationJob | RecentActivityTitleRefinementJob
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
interface TitleGenerationQueueDeps {
|
|
34
|
+
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
|
|
35
|
+
threadTitleService: Context.Service.Shape<typeof ThreadTitleServiceTag>
|
|
36
|
+
recentActivityTitleService: Context.Service.Shape<typeof RecentActivityTitleServiceTag>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let _deps: TitleGenerationQueueDeps | null = null
|
|
40
|
+
function getDeps(): TitleGenerationQueueDeps {
|
|
41
|
+
if (!_deps)
|
|
42
|
+
throw new ConfigurationError({
|
|
43
|
+
message: 'Title generation queue is not configured. Initialize the runtime before starting the worker.',
|
|
44
|
+
key: 'queue-deps',
|
|
45
|
+
})
|
|
46
|
+
return _deps
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function processTitleGenerationJob(job: Job<TitleGenerationJob>): Promise<void> {
|
|
50
|
+
const { threadTitleService, recentActivityTitleService } = getDeps()
|
|
30
51
|
if (job.data.kind === 'thread-title') {
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
return Effect.runPromise(
|
|
53
|
+
Effect.asVoid(threadTitleService.generateAndPersistTitle(ensureRecordId(job.data.threadId), job.data.sourceText)),
|
|
54
|
+
)
|
|
33
55
|
}
|
|
34
56
|
|
|
35
|
-
|
|
57
|
+
return Effect.runPromise(Effect.asVoid(recentActivityTitleService.refineRecentActivityTitle(job.data.activityId)))
|
|
36
58
|
}
|
|
37
59
|
|
|
38
60
|
const titleGeneration = createQueueFactory<TitleGenerationJob>({
|
|
@@ -42,6 +64,7 @@ const titleGeneration = createQueueFactory<TitleGenerationJob>({
|
|
|
42
64
|
concurrency: 10,
|
|
43
65
|
lockDuration: 300_000,
|
|
44
66
|
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
|
|
67
|
+
prepare: () => getDeps().databaseService.connect(),
|
|
45
68
|
processor: processTitleGenerationJob,
|
|
46
69
|
})
|
|
47
70
|
|
|
@@ -56,4 +79,26 @@ export function enqueueRecentActivityTitleRefinement(job: Omit<RecentActivityTit
|
|
|
56
79
|
)
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
export
|
|
82
|
+
export function startTitleGenerationWorker(options: {
|
|
83
|
+
registerSignals?: boolean
|
|
84
|
+
connectionProvider: () => IORedis
|
|
85
|
+
deps: TitleGenerationQueueDeps
|
|
86
|
+
}): ReturnType<typeof titleGeneration.startWorker> {
|
|
87
|
+
_deps = options.deps
|
|
88
|
+
return titleGeneration.startWorker({
|
|
89
|
+
registerSignals: options.registerSignals,
|
|
90
|
+
connectionProvider: options.connectionProvider,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
runStandaloneQueueWorker((runtime) => {
|
|
95
|
+
const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
|
|
96
|
+
startTitleGenerationWorker({
|
|
97
|
+
connectionProvider: () => resolve(RedisServiceTag).getConnectionForBullMQ(),
|
|
98
|
+
deps: {
|
|
99
|
+
databaseService: resolve(DatabaseServiceTag),
|
|
100
|
+
threadTitleService: resolve(ThreadTitleServiceTag),
|
|
101
|
+
recentActivityTitleService: resolve(RecentActivityTitleServiceTag),
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
})
|